Bug 893965 - Autocomplete CSS properties in the rule view, r=mratcliffe, msucan
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
const Cu = Components.utils;
|
const Cu = Components.utils;
|
||||||
|
const Ci = Components.interfaces;
|
||||||
|
|
||||||
// The XUL and XHTML namespace.
|
// The XUL and XHTML namespace.
|
||||||
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||||
@@ -11,6 +12,10 @@ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
|||||||
Cu.import("resource://gre/modules/Services.jsm");
|
Cu.import("resource://gre/modules/Services.jsm");
|
||||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyGetter(this, "gDevTools", function() {
|
||||||
|
return Cu.import("resource:///modules/devtools/gDevTools.jsm", {}).gDevTools;
|
||||||
|
});
|
||||||
|
|
||||||
this.EXPORTED_SYMBOLS = ["AutocompletePopup"];
|
this.EXPORTED_SYMBOLS = ["AutocompletePopup"];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,6 +54,14 @@ function AutocompletePopup(aDocument, aOptions = {})
|
|||||||
|
|
||||||
let id = aOptions.panelId || "devtools_autoCompletePopup";
|
let id = aOptions.panelId || "devtools_autoCompletePopup";
|
||||||
let theme = aOptions.theme || "dark";
|
let theme = aOptions.theme || "dark";
|
||||||
|
// If theme is auto, use the devtools.theme pref
|
||||||
|
if (theme == "auto") {
|
||||||
|
theme = Services.prefs.getCharPref("devtools.theme");
|
||||||
|
this.autoThemeEnabled = true;
|
||||||
|
// Setup theme change listener.
|
||||||
|
this._handleThemeChange = this._handleThemeChange.bind(this);
|
||||||
|
gDevTools.on("pref-changed", this._handleThemeChange);
|
||||||
|
}
|
||||||
// Reuse the existing popup elements.
|
// Reuse the existing popup elements.
|
||||||
this._panel = this._document.getElementById(id);
|
this._panel = this._document.getElementById(id);
|
||||||
if (!this._panel) {
|
if (!this._panel) {
|
||||||
@@ -173,6 +186,10 @@ AutocompletePopup.prototype = {
|
|||||||
this._list.removeEventListener("keypress", this.onKeypress, false);
|
this._list.removeEventListener("keypress", this.onKeypress, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.autoThemeEnabled) {
|
||||||
|
gDevTools.off("pref-changed", this._handleThemeChange);
|
||||||
|
}
|
||||||
|
|
||||||
this._document = null;
|
this._document = null;
|
||||||
this._list = null;
|
this._list = null;
|
||||||
this._panel = null;
|
this._panel = null;
|
||||||
@@ -440,7 +457,7 @@ AutocompletePopup.prototype = {
|
|||||||
this.selectedIndex++;
|
this.selectedIndex++;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.selectedIndex = -1;
|
this.selectedIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.selectedItem;
|
return this.selectedItem;
|
||||||
@@ -454,7 +471,7 @@ AutocompletePopup.prototype = {
|
|||||||
*/
|
*/
|
||||||
selectPreviousItem: function AP_selectPreviousItem()
|
selectPreviousItem: function AP_selectPreviousItem()
|
||||||
{
|
{
|
||||||
if (this.selectedIndex > -1) {
|
if (this.selectedIndex > 0) {
|
||||||
this.selectedIndex--;
|
this.selectedIndex--;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -496,5 +513,28 @@ AutocompletePopup.prototype = {
|
|||||||
|
|
||||||
return this.__scrollbarWidth;
|
return this.__scrollbarWidth;
|
||||||
},
|
},
|
||||||
};
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages theme switching for the popup based on the devtools.theme pref.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* @param String aEvent
|
||||||
|
* The name of the event. In this case, "pref-changed".
|
||||||
|
* @param Object aData
|
||||||
|
* An object passed by the emitter of the event. In this case, the
|
||||||
|
* object consists of three properties:
|
||||||
|
* - pref {String} The name of the preference that was modified.
|
||||||
|
* - newValue {Object} The new value of the preference.
|
||||||
|
* - oldValue {Object} The old value of the preference.
|
||||||
|
*/
|
||||||
|
_handleThemeChange: function AP__handleThemeChange(aEvent, aData)
|
||||||
|
{
|
||||||
|
if (aData.pref == "devtools.theme") {
|
||||||
|
this._panel.classList.toggle(aData.oldValue + "-theme", false);
|
||||||
|
this._panel.classList.toggle(aData.newValue + "-theme", true);
|
||||||
|
this._list.classList.toggle(aData.oldValue + "-theme", false);
|
||||||
|
this._list.classList.toggle(aData.newValue + "-theme", true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
@@ -24,9 +24,16 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const {Ci, Cu} = require("chrome");
|
const {Ci, Cu, Cc} = require("chrome");
|
||||||
|
|
||||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||||
|
const CONTENT_TYPES = {
|
||||||
|
PLAIN_TEXT: 0,
|
||||||
|
CSS_VALUE: 1,
|
||||||
|
CSS_MIXED: 2,
|
||||||
|
CSS_PROPERTY: 3,
|
||||||
|
};
|
||||||
|
const MAX_POPUP_ENTRIES = 10;
|
||||||
|
|
||||||
const FOCUS_FORWARD = Ci.nsIFocusManager.MOVEFOCUS_FORWARD;
|
const FOCUS_FORWARD = Ci.nsIFocusManager.MOVEFOCUS_FORWARD;
|
||||||
const FOCUS_BACKWARD = Ci.nsIFocusManager.MOVEFOCUS_BACKWARD;
|
const FOCUS_BACKWARD = Ci.nsIFocusManager.MOVEFOCUS_BACKWARD;
|
||||||
@@ -160,6 +167,8 @@ function InplaceEditor(aOptions, aEvent)
|
|||||||
this.initial = aOptions.initial ? aOptions.initial : this.elt.textContent;
|
this.initial = aOptions.initial ? aOptions.initial : this.elt.textContent;
|
||||||
this.multiline = aOptions.multiline || false;
|
this.multiline = aOptions.multiline || false;
|
||||||
this.stopOnReturn = !!aOptions.stopOnReturn;
|
this.stopOnReturn = !!aOptions.stopOnReturn;
|
||||||
|
this.contentType = aOptions.contentType || CONTENT_TYPES.PLAIN_TEXT;
|
||||||
|
this.popup = aOptions.popup;
|
||||||
|
|
||||||
this._onBlur = this._onBlur.bind(this);
|
this._onBlur = this._onBlur.bind(this);
|
||||||
this._onKeyPress = this._onKeyPress.bind(this);
|
this._onKeyPress = this._onKeyPress.bind(this);
|
||||||
@@ -208,6 +217,8 @@ function InplaceEditor(aOptions, aEvent)
|
|||||||
|
|
||||||
exports.InplaceEditor = InplaceEditor;
|
exports.InplaceEditor = InplaceEditor;
|
||||||
|
|
||||||
|
InplaceEditor.CONTENT_TYPES = CONTENT_TYPES;
|
||||||
|
|
||||||
InplaceEditor.prototype = {
|
InplaceEditor.prototype = {
|
||||||
_createInput: function InplaceEditor_createEditor()
|
_createInput: function InplaceEditor_createEditor()
|
||||||
{
|
{
|
||||||
@@ -679,6 +690,10 @@ InplaceEditor.prototype = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.popup) {
|
||||||
|
this.popup.hidePopup();
|
||||||
|
}
|
||||||
|
|
||||||
this._applied = true;
|
this._applied = true;
|
||||||
|
|
||||||
if (this.done) {
|
if (this.done) {
|
||||||
@@ -732,9 +747,31 @@ InplaceEditor.prototype = {
|
|||||||
increment *= smallIncrement;
|
increment *= smallIncrement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let cycling = false;
|
||||||
if (increment && this._incrementValue(increment) ) {
|
if (increment && this._incrementValue(increment) ) {
|
||||||
this._updateSize();
|
this._updateSize();
|
||||||
prevent = true;
|
prevent = true;
|
||||||
|
} else if (increment && this.popup && this.popup.isOpen) {
|
||||||
|
cycling = true;
|
||||||
|
prevent = true;
|
||||||
|
if (increment > 0) {
|
||||||
|
this.popup.selectPreviousItem();
|
||||||
|
} else {
|
||||||
|
this.popup.selectNextItem();
|
||||||
|
}
|
||||||
|
this.input.value = this.popup.selectedItem.label;
|
||||||
|
this._updateSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_BACK_SPACE ||
|
||||||
|
aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_DELETE ||
|
||||||
|
aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_LEFT ||
|
||||||
|
aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RIGHT) {
|
||||||
|
if (this.popup && this.popup.isOpen) {
|
||||||
|
this.popup.hidePopup();
|
||||||
|
}
|
||||||
|
} else if (!cycling) {
|
||||||
|
this._maybeSuggestCompletion();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.multiline &&
|
if (this.multiline &&
|
||||||
@@ -756,6 +793,9 @@ InplaceEditor.prototype = {
|
|||||||
direction = null;
|
direction = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Now we don't want to suggest anything as we are moving out.
|
||||||
|
this._preventSuggestions = true;
|
||||||
|
|
||||||
let input = this.input;
|
let input = this.input;
|
||||||
|
|
||||||
this._apply();
|
this._apply();
|
||||||
@@ -775,6 +815,8 @@ InplaceEditor.prototype = {
|
|||||||
this._clear();
|
this._clear();
|
||||||
} else if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE) {
|
} else if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE) {
|
||||||
// Cancel and blur ourselves.
|
// Cancel and blur ourselves.
|
||||||
|
// Now we don't want to suggest anything as we are moving out.
|
||||||
|
this._preventSuggestions = true;
|
||||||
prevent = true;
|
prevent = true;
|
||||||
this.cancelled = true;
|
this.cancelled = true;
|
||||||
this._apply();
|
this._apply();
|
||||||
@@ -821,6 +863,79 @@ InplaceEditor.prototype = {
|
|||||||
if (this.change) {
|
if (this.change) {
|
||||||
this.change(this.input.value.trim());
|
this.change(this.input.value.trim());
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles displaying suggestions based on the current input.
|
||||||
|
*/
|
||||||
|
_maybeSuggestCompletion: function() {
|
||||||
|
// Since we are calling this method from a keypress event handler, the
|
||||||
|
// |input.value| does not include currently typed character. Thus we perform
|
||||||
|
// this method async.
|
||||||
|
this.doc.defaultView.setTimeout(() => {
|
||||||
|
if (this._preventSuggestions) {
|
||||||
|
this._preventSuggestions = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.contentType == CONTENT_TYPES.PLAIN_TEXT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let input = this.input;
|
||||||
|
// Input can be null in cases when you intantaneously switch out of it.
|
||||||
|
if (!input) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let query = input.value.slice(0, input.selectionStart);
|
||||||
|
if (!query) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let list = [];
|
||||||
|
if (this.contentType == CONTENT_TYPES.CSS_PROPERTY) {
|
||||||
|
list = CSSPropertyList;
|
||||||
|
}
|
||||||
|
|
||||||
|
list.some(item => {
|
||||||
|
if (item.startsWith(query)) {
|
||||||
|
input.value = item;
|
||||||
|
input.setSelectionRange(query.length, item.length);
|
||||||
|
this._updateSize();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!this.popup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let finalList = [];
|
||||||
|
let length = list.length;
|
||||||
|
for (let i = 0, count = 0; i < length && count < MAX_POPUP_ENTRIES; i++) {
|
||||||
|
if (list[i].startsWith(query)) {
|
||||||
|
count++;
|
||||||
|
finalList.push({
|
||||||
|
preLabel: query,
|
||||||
|
label: list[i]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (count > 0) {
|
||||||
|
// Since count was incremented, we had already crossed the entries
|
||||||
|
// which would have started with query, assuming that list is sorted.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (list[i][0] > query[0]) {
|
||||||
|
// We have crossed all possible matches alphabetically.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (finalList.length > 1) {
|
||||||
|
this.popup.setItems(finalList);
|
||||||
|
this.popup.openPopup(this.input);
|
||||||
|
} else {
|
||||||
|
this.popup.hidePopup();
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -849,3 +964,8 @@ function moveFocus(aWin, aDirection)
|
|||||||
XPCOMUtils.defineLazyGetter(this, "focusManager", function() {
|
XPCOMUtils.defineLazyGetter(this, "focusManager", function() {
|
||||||
return Services.focus;
|
return Services.focus;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyGetter(this, "CSSPropertyList", function() {
|
||||||
|
let domUtils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
|
||||||
|
return domUtils.getCSSPropertyNames(domUtils.INCLUDE_ALIASES).sort();
|
||||||
|
});
|
||||||
|
|||||||
@@ -876,6 +876,13 @@ function CssRuleView(aDoc, aStore)
|
|||||||
this._boundCopy = this._onCopy.bind(this);
|
this._boundCopy = this._onCopy.bind(this);
|
||||||
this.element.addEventListener("copy", this._boundCopy);
|
this.element.addEventListener("copy", this._boundCopy);
|
||||||
|
|
||||||
|
let options = {
|
||||||
|
fixedWidth: true,
|
||||||
|
autoSelect: true,
|
||||||
|
theme: "auto"
|
||||||
|
};
|
||||||
|
this.popup = new AutocompletePopup(aDoc.defaultView.parent.document, options);
|
||||||
|
|
||||||
this._showEmpty();
|
this._showEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -902,6 +909,8 @@ CssRuleView.prototype = {
|
|||||||
if (this.element.parentNode) {
|
if (this.element.parentNode) {
|
||||||
this.element.parentNode.removeChild(this.element);
|
this.element.parentNode.removeChild(this.element);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.popup.destroy();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1270,7 +1279,9 @@ RuleEditor.prototype = {
|
|||||||
element: this.newPropSpan,
|
element: this.newPropSpan,
|
||||||
done: this._onNewProperty,
|
done: this._onNewProperty,
|
||||||
destroy: this._newPropertyDestroy,
|
destroy: this._newPropertyDestroy,
|
||||||
advanceChars: ":"
|
advanceChars: ":",
|
||||||
|
contentType: InplaceEditor.CONTENT_TYPES.CSS_PROPERTY,
|
||||||
|
popup: this.ruleView.popup
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1322,6 +1333,7 @@ RuleEditor.prototype = {
|
|||||||
function TextPropertyEditor(aRuleEditor, aProperty)
|
function TextPropertyEditor(aRuleEditor, aProperty)
|
||||||
{
|
{
|
||||||
this.doc = aRuleEditor.doc;
|
this.doc = aRuleEditor.doc;
|
||||||
|
this.popup = aRuleEditor.ruleView.popup;
|
||||||
this.prop = aProperty;
|
this.prop = aProperty;
|
||||||
this.prop.editor = this;
|
this.prop.editor = this;
|
||||||
this.browserWindow = this.doc.defaultView.top;
|
this.browserWindow = this.doc.defaultView.top;
|
||||||
@@ -1390,7 +1402,9 @@ TextPropertyEditor.prototype = {
|
|||||||
start: this._onStartEditing,
|
start: this._onStartEditing,
|
||||||
element: this.nameSpan,
|
element: this.nameSpan,
|
||||||
done: this._onNameDone,
|
done: this._onNameDone,
|
||||||
advanceChars: ':'
|
advanceChars: ':',
|
||||||
|
contentType: InplaceEditor.CONTENT_TYPES.CSS_PROPERTY,
|
||||||
|
popup: this.popup
|
||||||
});
|
});
|
||||||
|
|
||||||
appendText(this.nameContainer, ": ");
|
appendText(this.nameContainer, ": ");
|
||||||
@@ -1873,3 +1887,7 @@ XPCOMUtils.defineLazyGetter(this, "_strings", function() {
|
|||||||
XPCOMUtils.defineLazyGetter(this, "domUtils", function() {
|
XPCOMUtils.defineLazyGetter(this, "domUtils", function() {
|
||||||
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
|
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyGetter(this, "AutocompletePopup", function() {
|
||||||
|
return Cu.import("resource:///modules/devtools/AutocompletePopup.jsm", {}).AutocompletePopup;
|
||||||
|
});
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ MOCHITEST_BROWSER_FILES = \
|
|||||||
browser_computedview_734259_style_editor_link.js \
|
browser_computedview_734259_style_editor_link.js \
|
||||||
browser_computedview_copy.js\
|
browser_computedview_copy.js\
|
||||||
browser_styleinspector_bug_677930_urls_clickable.js \
|
browser_styleinspector_bug_677930_urls_clickable.js \
|
||||||
|
browser_bug893965_css_property_completion_new_property.js \
|
||||||
|
browser_bug893965_css_property_completion_existing_property.js \
|
||||||
head.js \
|
head.js \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
/* vim: set ts=2 et sw=2 tw=80: */
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
// Test that CSS property names are autocompleted and cycled correctly.
|
||||||
|
|
||||||
|
const MAX_ENTRIES = 10;
|
||||||
|
|
||||||
|
let doc;
|
||||||
|
let inspector;
|
||||||
|
let ruleViewWindow;
|
||||||
|
let editor;
|
||||||
|
let state;
|
||||||
|
// format :
|
||||||
|
// [
|
||||||
|
// what key to press,
|
||||||
|
// expected input box value after keypress,
|
||||||
|
// selectedIndex of the popup,
|
||||||
|
// total items in the popup
|
||||||
|
// ]
|
||||||
|
let testData = [
|
||||||
|
["VK_RIGHT", "border", -1, 0],
|
||||||
|
["-","border-bottom", 0, 10],
|
||||||
|
["b","border-bottom", 0, 6],
|
||||||
|
["VK_BACK_SPACE", "border-b", -1, 0],
|
||||||
|
["VK_BACK_SPACE", "border-", -1, 0],
|
||||||
|
["VK_BACK_SPACE", "border", -1, 0],
|
||||||
|
["VK_BACK_SPACE", "borde", -1, 0],
|
||||||
|
["VK_BACK_SPACE", "bord", -1, 0],
|
||||||
|
["VK_BACK_SPACE", "bor", -1, 0],
|
||||||
|
["VK_BACK_SPACE", "bo", -1, 0],
|
||||||
|
["VK_BACK_SPACE", "b", -1, 0],
|
||||||
|
["VK_BACK_SPACE", "", -1, 0],
|
||||||
|
["d", "direction", 0, 3],
|
||||||
|
["VK_DOWN", "display", 1, 3],
|
||||||
|
["VK_DOWN", "dominant-baseline", 2, 3],
|
||||||
|
["VK_DOWN", "direction", 0, 3],
|
||||||
|
["VK_DOWN", "display", 1, 3],
|
||||||
|
["VK_UP", "direction", 0, 3],
|
||||||
|
["VK_UP", "dominant-baseline", 2, 3],
|
||||||
|
["VK_UP", "display", 1, 3],
|
||||||
|
["VK_BACK_SPACE", "displa", -1, 0],
|
||||||
|
["VK_BACK_SPACE", "displ", -1, 0],
|
||||||
|
["VK_BACK_SPACE", "disp", -1, 0],
|
||||||
|
["VK_BACK_SPACE", "dis", -1, 0],
|
||||||
|
["VK_BACK_SPACE", "di", -1, 0],
|
||||||
|
["VK_BACK_SPACE", "d", -1, 0],
|
||||||
|
["i", "direction", 0, 2],
|
||||||
|
["s", "display", -1, 0],
|
||||||
|
["VK_BACK_SPACE", "dis", -1, 0],
|
||||||
|
["VK_BACK_SPACE", "di", -1, 0],
|
||||||
|
["VK_BACK_SPACE", "d", -1, 0],
|
||||||
|
["VK_BACK_SPACE", "", -1, 0],
|
||||||
|
["f", "fill", 0, MAX_ENTRIES],
|
||||||
|
["i", "fill", 0, 4],
|
||||||
|
["VK_ESCAPE", null, -1, 0],
|
||||||
|
];
|
||||||
|
|
||||||
|
function openRuleView()
|
||||||
|
{
|
||||||
|
var target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||||
|
gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
|
||||||
|
inspector = toolbox.getCurrentPanel();
|
||||||
|
inspector.sidebar.select("ruleview");
|
||||||
|
|
||||||
|
// Highlight a node.
|
||||||
|
let node = content.document.getElementsByTagName("h1")[0];
|
||||||
|
inspector.selection.setNode(node);
|
||||||
|
|
||||||
|
inspector.sidebar.once("ruleview-ready", testCompletion);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function testCompletion()
|
||||||
|
{
|
||||||
|
ruleViewWindow = inspector.sidebar.getWindowForTab("ruleview");
|
||||||
|
let brace = ruleViewWindow.document.querySelector(".ruleview-propertyname");
|
||||||
|
|
||||||
|
waitForEditorFocus(brace.parentNode, function onNewElement(aEditor) {
|
||||||
|
editor = aEditor;
|
||||||
|
editor.input.addEventListener("keypress", checkState, false);
|
||||||
|
checkStateAndMoveOn(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
brace.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkStateAndMoveOn(index) {
|
||||||
|
if (index == testData.length) {
|
||||||
|
finishUp();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let [key] = testData[index];
|
||||||
|
state = index;
|
||||||
|
|
||||||
|
info("pressing key " + key + " to get result: [" + testData[index].slice(1) +
|
||||||
|
"] for state " + state);
|
||||||
|
EventUtils.synthesizeKey(key, {}, ruleViewWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkState(event) {
|
||||||
|
// The keypress handler is async and can take some time to compute, filter and
|
||||||
|
// display the popup and autocompletion so we need to use setTimeout here.
|
||||||
|
window.setTimeout(function() {
|
||||||
|
info("After keypress for state " + state);
|
||||||
|
let [key, completion, index, total] = testData[state];
|
||||||
|
if (completion != null) {
|
||||||
|
is(editor.input.value, completion,
|
||||||
|
"Correct value is autocompleted for state " + state);
|
||||||
|
}
|
||||||
|
if (total == 0) {
|
||||||
|
ok(!(editor.popup && editor.popup.isOpen), "Popup is closed for state " +
|
||||||
|
state);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ok(editor.popup._panel.state == "open" ||
|
||||||
|
editor.popup._panel.state == "showing",
|
||||||
|
"Popup is open for state " + state);
|
||||||
|
is(editor.popup.getItems().length, total,
|
||||||
|
"Number of suggestions match for state " + state);
|
||||||
|
is(editor.popup.selectedIndex, index,
|
||||||
|
"Correct item is selected for state " + state);
|
||||||
|
}
|
||||||
|
checkStateAndMoveOn(state + 1);
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
function finishUp()
|
||||||
|
{
|
||||||
|
doc = inspector = editor = ruleViewWindow = state = null;
|
||||||
|
gBrowser.removeCurrentTab();
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test()
|
||||||
|
{
|
||||||
|
waitForExplicitFinish();
|
||||||
|
gBrowser.selectedTab = gBrowser.addTab();
|
||||||
|
gBrowser.selectedBrowser.addEventListener("load", function(evt) {
|
||||||
|
gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
|
||||||
|
doc = content.document;
|
||||||
|
doc.title = "Rule View Test";
|
||||||
|
waitForFocus(openRuleView, content);
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
content.location = "data:text/html,<h1 style='border: 1px solid red'>Filename" +
|
||||||
|
": browser_bug893965_css_property_completion_existing_property.js</h1>";
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
/* vim: set ts=2 et sw=2 tw=80: */
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
// Test that CSS property names are autocompleted and cycled correctly.
|
||||||
|
|
||||||
|
const MAX_ENTRIES = 10;
|
||||||
|
|
||||||
|
let doc;
|
||||||
|
let inspector;
|
||||||
|
let ruleViewWindow;
|
||||||
|
let editor;
|
||||||
|
let state;
|
||||||
|
// format :
|
||||||
|
// [
|
||||||
|
// what key to press,
|
||||||
|
// expected input box value after keypress,
|
||||||
|
// selectedIndex of the popup,
|
||||||
|
// total items in the popup
|
||||||
|
// ]
|
||||||
|
let testData = [
|
||||||
|
["d", "direction", 0, 3],
|
||||||
|
["VK_DOWN", "display", 1, 3],
|
||||||
|
["VK_DOWN", "dominant-baseline", 2, 3],
|
||||||
|
["VK_DOWN", "direction", 0, 3],
|
||||||
|
["VK_DOWN", "display", 1, 3],
|
||||||
|
["VK_UP", "direction", 0, 3],
|
||||||
|
["VK_UP", "dominant-baseline", 2, 3],
|
||||||
|
["VK_UP", "display", 1, 3],
|
||||||
|
["VK_BACK_SPACE", "displa", -1, 0],
|
||||||
|
["VK_BACK_SPACE", "displ", -1, 0],
|
||||||
|
["VK_BACK_SPACE", "disp", -1, 0],
|
||||||
|
["VK_BACK_SPACE", "dis", -1, 0],
|
||||||
|
["VK_BACK_SPACE", "di", -1, 0],
|
||||||
|
["VK_BACK_SPACE", "d", -1, 0],
|
||||||
|
["i", "direction", 0, 2],
|
||||||
|
["s", "display", -1, 0],
|
||||||
|
["VK_BACK_SPACE", "dis", -1, 0],
|
||||||
|
["VK_BACK_SPACE", "di", -1, 0],
|
||||||
|
["VK_BACK_SPACE", "d", -1, 0],
|
||||||
|
["VK_BACK_SPACE", "", -1, 0],
|
||||||
|
["f", "fill", 0, MAX_ENTRIES],
|
||||||
|
["i", "fill", 0, 4],
|
||||||
|
["VK_ESCAPE", null, -1, 0],
|
||||||
|
];
|
||||||
|
|
||||||
|
function openRuleView()
|
||||||
|
{
|
||||||
|
var target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||||
|
gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
|
||||||
|
inspector = toolbox.getCurrentPanel();
|
||||||
|
inspector.sidebar.select("ruleview");
|
||||||
|
|
||||||
|
// Highlight a node.
|
||||||
|
let node = content.document.getElementsByTagName("h1")[0];
|
||||||
|
inspector.selection.setNode(node);
|
||||||
|
|
||||||
|
inspector.sidebar.once("ruleview-ready", testCompletion);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function testCompletion()
|
||||||
|
{
|
||||||
|
ruleViewWindow = inspector.sidebar.getWindowForTab("ruleview");
|
||||||
|
let brace = ruleViewWindow.document.querySelector(".ruleview-ruleclose");
|
||||||
|
|
||||||
|
waitForEditorFocus(brace.parentNode, function onNewElement(aEditor) {
|
||||||
|
editor = aEditor;
|
||||||
|
editor.input.addEventListener("keypress", checkState, false);
|
||||||
|
checkStateAndMoveOn(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
brace.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkStateAndMoveOn(index) {
|
||||||
|
if (index == testData.length) {
|
||||||
|
finishUp();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let [key] = testData[index];
|
||||||
|
state = index;
|
||||||
|
|
||||||
|
info("pressing key " + key + " to get result: [" + testData[index].slice(1) +
|
||||||
|
"] for state " + state);
|
||||||
|
EventUtils.synthesizeKey(key, {}, ruleViewWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkState(event) {
|
||||||
|
// The keypress handler is async and can take some time to compute, filter and
|
||||||
|
// display the popup and autocompletion so we need to use setTimeout here.
|
||||||
|
window.setTimeout(function() {
|
||||||
|
info("After keypress for state " + state);
|
||||||
|
let [key, completion, index, total] = testData[state];
|
||||||
|
if (completion != null) {
|
||||||
|
is(editor.input.value, completion,
|
||||||
|
"Correct value is autocompleted for state " + state);
|
||||||
|
}
|
||||||
|
if (total == 0) {
|
||||||
|
ok(!(editor.popup && editor.popup.isOpen), "Popup is closed for state " +
|
||||||
|
state);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ok(editor.popup._panel.state == "open" ||
|
||||||
|
editor.popup._panel.state == "showing",
|
||||||
|
"Popup is open for state " + state);
|
||||||
|
is(editor.popup.getItems().length, total,
|
||||||
|
"Number of suggestions match for state " + state);
|
||||||
|
is(editor.popup.selectedIndex, index,
|
||||||
|
"Correct item is selected for state " + state);
|
||||||
|
}
|
||||||
|
checkStateAndMoveOn(state + 1);
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
function finishUp()
|
||||||
|
{
|
||||||
|
doc = inspector = editor = ruleViewWindow = state = null;
|
||||||
|
gBrowser.removeCurrentTab();
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test()
|
||||||
|
{
|
||||||
|
waitForExplicitFinish();
|
||||||
|
gBrowser.selectedTab = gBrowser.addTab();
|
||||||
|
gBrowser.selectedBrowser.addEventListener("load", function(evt) {
|
||||||
|
gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
|
||||||
|
doc = content.document;
|
||||||
|
doc.title = "Rule View Test";
|
||||||
|
waitForFocus(openRuleView, content);
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
content.location = "data:text/html,<h1 style='border: 1px solid red'>Filename:" +
|
||||||
|
"browser_bug893965_css_property_completion_new_property.js</h1>";
|
||||||
|
}
|
||||||
@@ -69,7 +69,6 @@ function consoleOpened(aHud) {
|
|||||||
is(popup.selectedIndex, 17,
|
is(popup.selectedIndex, 17,
|
||||||
"Index of the first item from bottom is selected.");
|
"Index of the first item from bottom is selected.");
|
||||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
|
||||||
|
|
||||||
let prefix = jsterm.inputNode.value.replace(/[\S]/g, " ");
|
let prefix = jsterm.inputNode.value.replace(/[\S]/g, " ");
|
||||||
|
|
||||||
@@ -123,7 +122,6 @@ function popupHideAfterTab()
|
|||||||
|
|
||||||
is(popup.selectedIndex, 17, "First index from bottom is selected");
|
is(popup.selectedIndex, 17, "First index from bottom is selected");
|
||||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
|
||||||
|
|
||||||
let prefix = jsterm.inputNode.value.replace(/[\S]/g, " ");
|
let prefix = jsterm.inputNode.value.replace(/[\S]/g, " ");
|
||||||
|
|
||||||
@@ -169,7 +167,6 @@ function testReturnKey()
|
|||||||
|
|
||||||
is(popup.selectedIndex, 17, "First index from bottom is selected");
|
is(popup.selectedIndex, 17, "First index from bottom is selected");
|
||||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
|
||||||
|
|
||||||
let prefix = jsterm.inputNode.value.replace(/[\S]/g, " ");
|
let prefix = jsterm.inputNode.value.replace(/[\S]/g, " ");
|
||||||
|
|
||||||
@@ -292,7 +289,6 @@ function testCompletionInText()
|
|||||||
ok(popup.isOpen, "popup is open");
|
ok(popup.isOpen, "popup is open");
|
||||||
is(popup.itemCount, 2, "popup.itemCount is correct");
|
is(popup.itemCount, 2, "popup.itemCount is correct");
|
||||||
|
|
||||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
|
||||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||||
is(popup.selectedIndex, 0, "popup.selectedIndex is correct");
|
is(popup.selectedIndex, 0, "popup.selectedIndex is correct");
|
||||||
ok(!completeNode.value, "completeNode.value is empty");
|
ok(!completeNode.value, "completeNode.value is empty");
|
||||||
|
|||||||
@@ -64,10 +64,10 @@ function consoleOpened(HUD) {
|
|||||||
is(popup.selectedIndex, 2, "index 2 is selected");
|
is(popup.selectedIndex, 2, "index 2 is selected");
|
||||||
is(popup.selectedItem, items[2], "item2 is selected");
|
is(popup.selectedItem, items[2], "item2 is selected");
|
||||||
|
|
||||||
ok(!popup.selectNextItem(), "selectPreviousItem() works");
|
ok(popup.selectNextItem(), "selectPreviousItem() works");
|
||||||
|
|
||||||
is(popup.selectedIndex, -1, "no index is selected");
|
is(popup.selectedIndex, 0, "index 0 is selected");
|
||||||
ok(!popup.selectedItem, "no item is selected");
|
is(popup.selectedItem, items[0], "item0 is selected");
|
||||||
|
|
||||||
items.push({label: "label3", value: "value3"});
|
items.push({label: "label3", value: "value3"});
|
||||||
popup.appendItem(items[3]);
|
popup.appendItem(items[3]);
|
||||||
|
|||||||
@@ -66,21 +66,21 @@ function testCompletion(hud) {
|
|||||||
yield undefined;
|
yield undefined;
|
||||||
|
|
||||||
is(input.value, "document.getElem", "'document.getElem' completion");
|
is(input.value, "document.getElem", "'document.getElem' completion");
|
||||||
is(jsterm.completeNode.value, "", "'document.getElem' completion");
|
is(jsterm.completeNode.value, " entsByTagNameNS", "'document.getElem' completion");
|
||||||
|
|
||||||
// Test pressing tab another time.
|
// Test pressing tab another time.
|
||||||
jsterm.complete(jsterm.COMPLETE_FORWARD, testNext);
|
jsterm.complete(jsterm.COMPLETE_FORWARD, testNext);
|
||||||
yield undefined;
|
yield undefined;
|
||||||
|
|
||||||
is(input.value, "document.getElem", "'document.getElem' completion");
|
is(input.value, "document.getElem", "'document.getElem' completion");
|
||||||
is(jsterm.completeNode.value, " entsByTagNameNS", "'document.getElem' another tab completion");
|
is(jsterm.completeNode.value, " entsByTagName", "'document.getElem' another tab completion");
|
||||||
|
|
||||||
// Test pressing shift_tab.
|
// Test pressing shift_tab.
|
||||||
jsterm.complete(jsterm.COMPLETE_BACKWARD, testNext);
|
jsterm.complete(jsterm.COMPLETE_BACKWARD, testNext);
|
||||||
yield undefined;
|
yield undefined;
|
||||||
|
|
||||||
is(input.value, "document.getElem", "'document.getElem' untab completion");
|
is(input.value, "document.getElem", "'document.getElem' untab completion");
|
||||||
is(jsterm.completeNode.value, "", "'document.getElem' completion");
|
is(jsterm.completeNode.value, " entsByTagNameNS", "'document.getElem' completion");
|
||||||
|
|
||||||
jsterm.clearOutput();
|
jsterm.clearOutput();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user