new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_gridline-names-autocomplete.js
@@ -0,0 +1,169 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that CSS property values are autocompleted and cycled
+// correctly when editing an existing property in the rule view.
+
+// format :
+// [
+// what key to press,
+// modifers,
+// expected input box value after keypress,
+// is the popup open,
+// is a suggestion selected in the popup,
+// expect ruleview-changed,
+// ]
+
+const OPEN = true, SELECTED = true, CHANGE = true;
+const changeTestData = [
+ ["c", {}, "col1-start", OPEN, SELECTED, CHANGE],
+ ["o", {}, "col1-start", OPEN, SELECTED, CHANGE],
+ ["l", {}, "col1-start", OPEN, SELECTED, CHANGE],
+ ["VK_DOWN", {}, "col2-start", OPEN, SELECTED, CHANGE],
+ ["VK_RIGHT", {}, "col2-start", !OPEN, !SELECTED, !CHANGE],
+];
+
+// Creates a new CSS property value.
+// Checks that grid-area autocompletes column and row names.
+const newAreaTestData = [
+ ["g", {}, "grid", OPEN, SELECTED, !CHANGE],
+ ["VK_DOWN", {}, "grid-area", OPEN, SELECTED, !CHANGE],
+ ["VK_TAB", {}, "", !OPEN, !SELECTED, !CHANGE],
+ "grid-line-names-updated",
+ ["c", {}, "col1-start", OPEN, SELECTED, CHANGE],
+ ["VK_BACK_SPACE", {}, "c", !OPEN, !SELECTED, CHANGE],
+ ["VK_BACK_SPACE", {}, "", !OPEN, !SELECTED, CHANGE],
+ ["r", {}, "row1-start", OPEN, SELECTED, CHANGE],
+ ["r", {}, "rr", !OPEN, !SELECTED, CHANGE],
+ ["VK_BACK_SPACE", {}, "r", !OPEN, !SELECTED, CHANGE],
+ ["o", {}, "row1-start", OPEN, SELECTED, CHANGE],
+ ["VK_RETURN", {}, "", !OPEN, !SELECTED, CHANGE],
+];
+
+// Creates a new CSS property value.
+// Checks that grid-row only autocompletes row names.
+const newRowTestData = [
+ ["g", {}, "grid", OPEN, SELECTED, !CHANGE],
+ ["r", {}, "grid", OPEN, SELECTED, !CHANGE],
+ ["i", {}, "grid", OPEN, SELECTED, !CHANGE],
+ ["d", {}, "grid", OPEN, SELECTED, !CHANGE],
+ ["-", {}, "grid-area", OPEN, SELECTED, !CHANGE],
+ ["r", {}, "grid-row", OPEN, SELECTED, !CHANGE],
+ ["VK_RETURN", {}, "", !OPEN, !SELECTED, !CHANGE],
+ "grid-line-names-updated",
+ ["c", {}, "c", !OPEN, !SELECTED, CHANGE],
+ ["VK_BACK_SPACE", {}, "", !OPEN, !SELECTED, CHANGE],
+ ["r", {}, "row1-start", OPEN, SELECTED, CHANGE],
+ ["VK_TAB", {}, "", !OPEN, !SELECTED, CHANGE],
+];
+
+const TEST_URL = URL_ROOT + "doc_grid_names.html";
+
+add_task(async function() {
+ await addTab(TEST_URL);
+ let {toolbox, inspector, view} = await openRuleView();
+
+ info("Test autocompletion changing a preexisting property");
+ await runChangePropertyAutocompletionTest(toolbox, inspector, view, changeTestData);
+
+ info("Test autocompletion creating a new property");
+ await runNewPropertyAutocompletionTest(toolbox, inspector, view, newAreaTestData);
+
+ info("Test autocompletion creating a new property");
+ await runNewPropertyAutocompletionTest(toolbox, inspector, view, newRowTestData);
+});
+
+async function runNewPropertyAutocompletionTest(toolbox, inspector, view, testData) {
+ info("Selecting the test node");
+ await selectNode("#cell2", inspector);
+
+ info("Focusing the css property editable field");
+ let ruleEditor = getRuleViewRuleEditor(view, 0);
+ let editor = await focusNewRuleViewProperty(ruleEditor);
+ let gridLineNamesUpdated = inspector.once("grid-line-names-updated");
+
+ info("Starting to test for css property completion");
+ for (let data of testData) {
+ if (data == "grid-line-names-updated") {
+ await gridLineNamesUpdated;
+ continue;
+ }
+ await testCompletion(data, editor, view);
+ }
+}
+
+async function runChangePropertyAutocompletionTest(toolbox, inspector, view, testData) {
+ info("Selecting the test node");
+ await selectNode("#cell3", inspector);
+
+ let ruleEditor = getRuleViewRuleEditor(view, 1).rule;
+ let prop = ruleEditor.textProps[0];
+
+ info("Focusing the css property editable value");
+ let gridLineNamesUpdated = inspector.once("grid-line-names-updated");
+ let editor = await focusEditableField(view, prop.editor.valueSpan);
+ await gridLineNamesUpdated;
+
+ info("Starting to test for css property completion");
+ for (let data of testData) {
+ // Re-define the editor at each iteration, because the focus may have moved
+ // from property to value and back
+ editor = inplaceEditor(view.styleDocument.activeElement);
+ await testCompletion(data, editor, view);
+ }
+}
+
+async function testCompletion([key, modifiers, completion, open, selected, change],
+ editor, view) {
+ info("Pressing key " + key);
+ info("Expecting " + completion);
+ info("Is popup opened: " + open);
+ info("Is item selected: " + selected);
+
+ let onDone;
+ if (change) {
+ // If the key triggers a ruleview-changed, wait for that event, it will
+ // always be the last to be triggered and tells us when the preview has
+ // been done.
+ onDone = view.once("ruleview-changed");
+ } else {
+ // Otherwise, expect an after-suggest event (except if the popup gets
+ // closed).
+ onDone = key !== "VK_RIGHT" && key !== "VK_BACK_SPACE"
+ ? editor.once("after-suggest")
+ : null;
+ }
+
+ // Also listening for popup opened/closed events if needed.
+ let popupEvent = open ? "popup-opened" : "popup-closed";
+ let onPopupEvent = editor.popup.isOpen !== open ? once(editor.popup, popupEvent) : null;
+
+ info("Synthesizing key " + key + ", modifiers: " + Object.keys(modifiers));
+
+ EventUtils.synthesizeKey(key, modifiers, view.styleWindow);
+
+ // Flush the debounce for the preview text.
+ view.debounce.flush();
+
+ await onDone;
+ await onPopupEvent;
+
+ // The key might have been a TAB or shift-TAB, in which case the editor will
+ // be a new one
+ editor = inplaceEditor(view.styleDocument.activeElement);
+
+ info("Checking the state");
+ if (completion !== null) {
+ is(editor.input.value, completion, "Correct value is autocompleted");
+ }
+
+ if (!open) {
+ ok(!(editor.popup && editor.popup.isOpen), "Popup is closed");
+ } else {
+ ok(editor.popup.isOpen, "Popup is open");
+ is(editor.popup.selectedIndex !== -1, selected, "An item is selected");
+ }
+}
--- a/devtools/client/shared/inplace-editor.js
+++ b/devtools/client/shared/inplace-editor.js
@@ -46,16 +46,24 @@ const MAX_POPUP_ENTRIES = 500;
const FOCUS_FORWARD = focusManager.MOVEFOCUS_FORWARD;
const FOCUS_BACKWARD = focusManager.MOVEFOCUS_BACKWARD;
const WORD_REGEXP = /\w/;
const isWordChar = function(str) {
return str && WORD_REGEXP.test(str);
};
+const GRID_PROPERTY_NAMES = ["grid-area", "grid-row", "grid-row-start",
+ "grid-row-end", "grid-column", "grid-column-start",
+ "grid-column-end"];
+const GRID_ROW_PROPERTY_NAMES = ["grid-area", "grid-row", "grid-row-start",
+ "grid-row-end"];
+const GRID_COL_PROPERTY_NAMES = ["grid-area", "grid-column", "grid-column-start",
+ "grid-column-end"];
+
/**
* Helper to check if the provided key matches one of the expected keys.
* Keys will be prefixed with DOM_VK_ and should match a key in KeyCodes.
*
* @param {String} key
* the key to check (can be a keyCode).
* @param {...String} keys
* list of possible keys allowed.
@@ -126,16 +134,19 @@ function isKeyIn(key, ...keys) {
* defaults to true
* {Boolean} preserveTextStyles: If true, do not copy text-related styles
* from `element` to the new input.
* defaults to false
* {Object} cssProperties: An instance of CSSProperties.
* {Object} cssVariables: A Map object containing all CSS variables.
* {Number} defaultIncrement: The value by which the input is incremented
* or decremented by default (0.1 for properties like opacity and 1 by default)
+ * {Function} getGridLineNames:
+ * Will be called before offering autocomplete sugestions, if the property is
+ * a member of GRID_PROPERTY_NAMES.
*/
function editableField(options) {
return editableItem(options, function(element, event) {
if (!options.element.inplaceEditor) {
new InplaceEditor(options, event);
}
});
}
@@ -290,20 +301,16 @@ function InplaceEditor(options, event) {
}
this.input.focus();
if (typeof options.selectAll == "undefined" || options.selectAll) {
this.input.select();
}
- if (this.contentType == CONTENT_TYPES.CSS_VALUE && this.input.value == "") {
- this._maybeSuggestCompletion(false);
- }
-
this.input.addEventListener("blur", this._onBlur);
this.input.addEventListener("keypress", this._onKeyPress);
this.input.addEventListener("input", this._onInput);
this.input.addEventListener("dblclick", this._stopEventPropagation);
this.input.addEventListener("click", this._stopEventPropagation);
this.input.addEventListener("mousedown", this._stopEventPropagation);
this.input.addEventListener("contextmenu", this._onContextMenu);
this.doc.defaultView.addEventListener("blur", this._onWindowBlur);
@@ -316,16 +323,18 @@ function InplaceEditor(options, event) {
this._updateSize();
EventEmitter.decorate(this);
if (options.start) {
options.start(this, event);
}
+
+ this._getGridNamesBeforeCompletion(options.getGridLineNames);
}
exports.InplaceEditor = InplaceEditor;
InplaceEditor.CONTENT_TYPES = CONTENT_TYPES;
InplaceEditor.prototype = {
@@ -988,16 +997,35 @@ InplaceEditor.prototype = {
this._acceptPopupSuggestion();
} else {
this._apply();
this._clear();
}
},
/**
+ * Before offering autocomplete, set this.gridLineNames as the line names
+ * of the current grid, if they exist.
+ *
+ * @param {Function} getGridLineNames
+ * A function which gets the line names of the current grid.
+ */
+ _getGridNamesBeforeCompletion: async function(getGridLineNames) {
+ if (getGridLineNames && this.property &&
+ GRID_PROPERTY_NAMES.includes(this.property.name)) {
+ this.gridLineNames = await getGridLineNames();
+ }
+
+ if (this.contentType == CONTENT_TYPES.CSS_VALUE && this.input &&
+ this.input.value == "") {
+ this._maybeSuggestCompletion(false);
+ }
+ },
+
+ /**
* Event handler called by the autocomplete popup when receiving a click
* event.
*/
_onAutocompletePopupClick: function() {
this._acceptPopupSuggestion();
},
_acceptPopupSuggestion: function() {
@@ -1562,17 +1590,28 @@ InplaceEditor.prototype = {
* Returns a list of CSS values valid for a provided property name to use for
* the autocompletion. This method is overridden by tests in order to use
* mocked suggestion lists.
*
* @param {String} propertyName
* @return {Array} array of CSS property values (Strings)
*/
_getCSSValuesForPropertyName: function(propertyName) {
- return this.cssProperties.getValues(propertyName);
+ let gridLineList = [];
+ if (this.gridLineNames) {
+ if (GRID_ROW_PROPERTY_NAMES.includes(this.property.name)) {
+ gridLineList.push(...this.gridLineNames.rows);
+ }
+ if (GRID_COL_PROPERTY_NAMES.includes(this.property.name)) {
+ gridLineList.push(...this.gridLineNames.cols);
+ }
+ }
+ // Must be alphabetically sorted before comparing the results with
+ // the user input, otherwise we will lose some results.
+ return gridLineList.concat(this.cssProperties.getValues(propertyName)).sort();
},
/**
* Returns the list of all CSS variables to use for the autocompletion.
*
* @return {Array} array of CSS variable names (Strings)
*/
_getCSSVariableNames: function() {