diff --git a/browser/devtools/shared/inplace-editor.js b/browser/devtools/shared/inplace-editor.js index 670af93b2ad..a583d404b8d 100644 --- a/browser/devtools/shared/inplace-editor.js +++ b/browser/devtools/shared/inplace-editor.js @@ -73,6 +73,12 @@ Cu.import("resource://gre/modules/devtools/event-emitter.js"); * {boolean} stopOnReturn: * If true, the return key will not advance the editor to the next * focusable element. + * {boolean} stopOnTab: + * If true, the tab key will not advance the editor to the next + * focusable element. + * {boolean} stopOnShiftTab: + * If true, shift tab will not advance the editor to the previous + * focusable element. * {string} trigger: The DOM event that should trigger editing, * defaults to "click" */ @@ -171,6 +177,8 @@ function InplaceEditor(aOptions, aEvent) this.destroy = aOptions.destroy; this.initial = aOptions.initial ? aOptions.initial : this.elt.textContent; this.multiline = aOptions.multiline || false; + this.stopOnShiftTab = !!aOptions.stopOnShiftTab; + this.stopOnTab = !!aOptions.stopOnTab; this.stopOnReturn = !!aOptions.stopOnReturn; this.contentType = aOptions.contentType || CONTENT_TYPES.PLAIN_TEXT; this.property = aOptions.property; @@ -899,9 +907,15 @@ InplaceEditor.prototype = { let direction = FOCUS_FORWARD; if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_TAB && aEvent.shiftKey) { - direction = FOCUS_BACKWARD; + if (this.stopOnShiftTab) { + direction = null; + } else { + direction = FOCUS_BACKWARD; + } } - if (this.stopOnReturn && aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN) { + if ((this.stopOnReturn && + aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN) || + (this.stopOnTab && aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_TAB)) { direction = null; } diff --git a/browser/devtools/styleinspector/rule-view.js b/browser/devtools/styleinspector/rule-view.js index ae11bb2175b..ebc87a85e11 100644 --- a/browser/devtools/styleinspector/rule-view.js +++ b/browser/devtools/styleinspector/rule-view.js @@ -1692,14 +1692,23 @@ function RuleEditor(aRuleView, aRule) { this.doc = this.ruleView.doc; this.rule = aRule; this.isEditable = !aRule.isSystem; + // Flag that blocks updates of the selector and properties when it is + // being edited + this.isEditing = false; this._onNewProperty = this._onNewProperty.bind(this); this._newPropertyDestroy = this._newPropertyDestroy.bind(this); + this._onSelectorDone = this._onSelectorDone.bind(this); this._create(); } RuleEditor.prototype = { + get isSelectorEditable() { + let toolbox = this.ruleView.inspector.toolbox; + return toolbox.target.client.traits.selectorEditable; + }, + _create: function() { this.element = this.doc.createElementNS(HTML_NS, "div"); this.element.className = "ruleview-rule theme-separator"; @@ -1741,10 +1750,30 @@ RuleEditor.prototype = { let header = createChild(code, "div", {}); - this.selectorText = createChild(header, "span", { + this.selectorContainer = createChild(header, "span", { + class: "ruleview-selectorcontainer" + }); + + this.selectorText = createChild(this.selectorContainer, "span", { class: "ruleview-selector theme-fg-color3" }); + if (this.isEditable && this.rule.domRule.type !== ELEMENT_STYLE && + this.isSelectorEditable) { + this.selectorContainer.addEventListener("click", aEvent => { + // Clicks within the selector shouldn't propagate any further. + aEvent.stopPropagation(); + }, false); + + editableField({ + element: this.selectorText, + done: this._onSelectorDone, + stopOnShiftTab: true, + stopOnTab: true, + stopOnReturn: true + }); + } + this.openBrace = createChild(header, "span", { class: "ruleview-ruleopen", textContent: " {" @@ -2019,6 +2048,36 @@ RuleEditor.prototype = { if (this.multipleAddedProperties && this.multipleAddedProperties.length) { this.addProperties(this.multipleAddedProperties); } + }, + + /** + * Called when the selector's inplace editor is closed. + * Ignores the change if the user pressed escape, otherwise + * commits it. + * + * @param {string} aValue + * The value contained in the editor. + * @param {boolean} aCommit + * True if the change should be applied. + */ + _onSelectorDone: function(aValue, aCommit) { + if (!aCommit || this.isEditing || aValue === "" || + aValue === this.rule.selectorText) { + return; + } + + this.isEditing = true; + + this.rule.domRule.modifySelector(aValue).then(isModified => { + this.isEditing = false; + + if (isModified) { + this.ruleView.refreshPanel(); + } + }).then(null, err => { + this.isEditing = false; + promiseWarn(err); + }); } }; @@ -2423,7 +2482,7 @@ TextPropertyEditor.prototype = { * True if the change should be applied. */ _onNameDone: function(aValue, aCommit) { - if (aCommit) { + if (aCommit && !this.ruleEditor.isEditing) { // Unlike the value editor, if a name is empty the entire property // should always be removed. if (aValue.trim() === "") { @@ -2472,7 +2531,7 @@ TextPropertyEditor.prototype = { * True if the change should be applied. */ _onValueDone: function(aValue, aCommit) { - if (!aCommit) { + if (!aCommit && !this.ruleEditor.isEditing) { // A new property should be removed when escape is pressed. if (this.removeOnRevert) { this.remove(); @@ -2568,8 +2627,9 @@ TextPropertyEditor.prototype = { * @param {string} aValue The value to set the current property to. */ _previewValue: function(aValue) { - // Since function call is throttled, we need to make sure we are still editing - if (!this.editing) { + // Since function call is throttled, we need to make sure we are still + // editing, and any selector modifications have been completed + if (!this.editing || this.ruleEditor.isEditing) { return; } diff --git a/browser/devtools/styleinspector/ruleview.css b/browser/devtools/styleinspector/ruleview.css index e963cc3e019..a1ef3b148aa 100644 --- a/browser/devtools/styleinspector/ruleview.css +++ b/browser/devtools/styleinspector/ruleview.css @@ -19,7 +19,8 @@ pointer-events: none; } -.ruleview-namecontainer { +.ruleview-namecontainer, +.ruleview-selectorcontainer { cursor: text; } diff --git a/browser/devtools/styleinspector/test/browser.ini b/browser/devtools/styleinspector/test/browser.ini index 870b151c1a7..4c750e76317 100644 --- a/browser/devtools/styleinspector/test/browser.ini +++ b/browser/devtools/styleinspector/test/browser.ini @@ -62,6 +62,9 @@ support-files = [browser_ruleview_edit-property-order.js] [browser_ruleview_edit-property_01.js] [browser_ruleview_edit-property_02.js] +[browser_ruleview_edit-selector-commit.js] +[browser_ruleview_edit-selector_01.js] +[browser_ruleview_edit-selector_02.js] [browser_ruleview_eyedropper.js] skip-if = os == "win" && debug # bug 963492 [browser_ruleview_inherit.js] diff --git a/browser/devtools/styleinspector/test/browser_ruleview_add-property-cancel_01.js b/browser/devtools/styleinspector/test/browser_ruleview_add-property-cancel_01.js index 267131911bc..59328865155 100644 --- a/browser/devtools/styleinspector/test/browser_ruleview_add-property-cancel_01.js +++ b/browser/devtools/styleinspector/test/browser_ruleview_add-property-cancel_01.js @@ -37,7 +37,7 @@ let test = asyncTest(function*() { function* testCancelNew(view) { info("Test adding a new rule to the element's style declaration and leaving it empty."); - let elementRuleEditor = view.element.children[0]._ruleEditor; + let elementRuleEditor = getRuleViewRuleEditor(view, 0); info("Focusing a new property name in the rule-view"); let editor = yield focusEditableField(elementRuleEditor.closeBrace); diff --git a/browser/devtools/styleinspector/test/browser_ruleview_add-property-cancel_02.js b/browser/devtools/styleinspector/test/browser_ruleview_add-property-cancel_02.js index d19a85217ca..588ca549e7b 100644 --- a/browser/devtools/styleinspector/test/browser_ruleview_add-property-cancel_02.js +++ b/browser/devtools/styleinspector/test/browser_ruleview_add-property-cancel_02.js @@ -39,7 +39,7 @@ let test = asyncTest(function*() { function* testCreateNewEscape(view) { info("Test creating a new property and escaping"); - let elementRuleEditor = view.element.children[0]._ruleEditor; + let elementRuleEditor = getRuleViewRuleEditor(view, 0); info("Focusing a new property name in the rule-view"); let editor = yield focusEditableField(elementRuleEditor.closeBrace); diff --git a/browser/devtools/styleinspector/test/browser_ruleview_add-property-cancel_03.js b/browser/devtools/styleinspector/test/browser_ruleview_add-property-cancel_03.js index 5947d3d3562..9b44e9e6e66 100644 --- a/browser/devtools/styleinspector/test/browser_ruleview_add-property-cancel_03.js +++ b/browser/devtools/styleinspector/test/browser_ruleview_add-property-cancel_03.js @@ -31,7 +31,7 @@ function* testCancelNew(inspector, ruleView) { // Start at the beginning: start to add a rule to the element's style // declaration, but leave it empty. - let elementRuleEditor = ruleView.element.children[0]._ruleEditor; + let elementRuleEditor = getRuleViewRuleEditor(ruleView, 0); let editor = yield focusEditableField(elementRuleEditor.closeBrace); is(inplaceEditor(elementRuleEditor.newPropSpan), editor, @@ -50,7 +50,7 @@ function* testCancelNewOnEscape(inspector, ruleView) { // Start at the beginning: start to add a rule to the element's style // declaration, add some text, then press escape. - let elementRuleEditor = ruleView.element.children[0]._ruleEditor; + let elementRuleEditor = getRuleViewRuleEditor(ruleView, 0); let editor = yield focusEditableField(elementRuleEditor.closeBrace); is(inplaceEditor(elementRuleEditor.newPropSpan), editor, "Next focused editor should be the new property editor."); diff --git a/browser/devtools/styleinspector/test/browser_ruleview_add-property_01.js b/browser/devtools/styleinspector/test/browser_ruleview_add-property_01.js index 7f88597bc7c..0e341c193b2 100644 --- a/browser/devtools/styleinspector/test/browser_ruleview_add-property_01.js +++ b/browser/devtools/styleinspector/test/browser_ruleview_add-property_01.js @@ -39,7 +39,7 @@ let test = asyncTest(function*() { function* testCreateNew(view) { info("Test creating a new property"); - let elementRuleEditor = view.element.children[0]._ruleEditor; + let elementRuleEditor = getRuleViewRuleEditor(view, 0); info("Focusing a new property name in the rule-view"); let editor = yield focusEditableField(elementRuleEditor.closeBrace); diff --git a/browser/devtools/styleinspector/test/browser_ruleview_add-property_02.js b/browser/devtools/styleinspector/test/browser_ruleview_add-property_02.js index 3e4ec9c21ff..478d4d2dd89 100644 --- a/browser/devtools/styleinspector/test/browser_ruleview_add-property_02.js +++ b/browser/devtools/styleinspector/test/browser_ruleview_add-property_02.js @@ -29,7 +29,7 @@ let test = asyncTest(function*() { function* testCreateNew(inspector, ruleView) { // Create a new property. - let elementRuleEditor = ruleView.element.children[0]._ruleEditor; + let elementRuleEditor = getRuleViewRuleEditor(ruleView, 0); let editor = yield focusEditableField(elementRuleEditor.closeBrace); is(inplaceEditor(elementRuleEditor.newPropSpan), editor, diff --git a/browser/devtools/styleinspector/test/browser_ruleview_content_01.js b/browser/devtools/styleinspector/test/browser_ruleview_content_01.js index 637bf1c69f5..13fc6b77acb 100644 --- a/browser/devtools/styleinspector/test/browser_ruleview_content_01.js +++ b/browser/devtools/styleinspector/test/browser_ruleview_content_01.js @@ -35,7 +35,7 @@ function* testContentAfterNodeSelection(inspector, ruleView) { "After highlighting null, has a no-results element again."); yield selectNode("#testid", inspector); - let classEditor = ruleView.element.children[2]._ruleEditor; + let classEditor = getRuleViewRuleEditor(ruleView, 2); is(classEditor.selectorText.querySelector(".ruleview-selector-matched").textContent, ".testclass", ".textclass should be matched."); is(classEditor.selectorText.querySelector(".ruleview-selector-unmatched").textContent, diff --git a/browser/devtools/styleinspector/test/browser_ruleview_edit-property-commit.js b/browser/devtools/styleinspector/test/browser_ruleview_edit-property-commit.js index 4b9bfb2c577..ee76293758c 100644 --- a/browser/devtools/styleinspector/test/browser_ruleview_edit-property-commit.js +++ b/browser/devtools/styleinspector/test/browser_ruleview_edit-property-commit.js @@ -56,14 +56,14 @@ function createDocument() { } function* runTestData(view, {value, commitKey, modifiers, expected}) { - let idRuleEditor = view.element.children[1]._ruleEditor; + let idRuleEditor = getRuleViewRuleEditor(view, 1); let propEditor = idRuleEditor.rule.textProps[0].editor; info("Focusing the inplace editor field"); let editor = yield focusEditableField(propEditor.valueSpan); is(inplaceEditor(propEditor.valueSpan), editor, "Focused editor should be the value span."); - info("Entering test data " + value) + info("Entering test data " + value); for (let ch of value) { EventUtils.sendChar(ch, view.doc.defaultView); } diff --git a/browser/devtools/styleinspector/test/browser_ruleview_edit-property-increments.js b/browser/devtools/styleinspector/test/browser_ruleview_edit-property-increments.js index 491de7a4c64..19798fb7bcb 100644 --- a/browser/devtools/styleinspector/test/browser_ruleview_edit-property-increments.js +++ b/browser/devtools/styleinspector/test/browser_ruleview_edit-property-increments.js @@ -34,7 +34,7 @@ function createDocument() { function* testMarginIncrements(view) { info("Testing keyboard increments on the margin property"); - let idRuleEditor = view.element.children[0]._ruleEditor; + let idRuleEditor = getRuleViewRuleEditor(view, 0); let marginPropEditor = idRuleEditor.rule.textProps[0].editor; yield runIncrementTest(marginPropEditor, view, { @@ -52,7 +52,7 @@ function* testMarginIncrements(view) { function* testVariousUnitIncrements(view) { info("Testing keyboard increments on values with various units"); - let idRuleEditor = view.element.children[0]._ruleEditor; + let idRuleEditor = getRuleViewRuleEditor(view, 0); let paddingPropEditor = idRuleEditor.rule.textProps[1].editor; yield runIncrementTest(paddingPropEditor, view, { @@ -71,7 +71,7 @@ function* testVariousUnitIncrements(view) { function* testHexIncrements(view) { info("Testing keyboard increments with hex colors"); - let idRuleEditor = view.element.children[0]._ruleEditor; + let idRuleEditor = getRuleViewRuleEditor(view, 0); let hexColorPropEditor = idRuleEditor.rule.textProps[2].editor; yield runIncrementTest(hexColorPropEditor, view, { @@ -87,7 +87,7 @@ function* testHexIncrements(view) { function* testRgbIncrements(view) { info("Testing keyboard increments with rgb colors"); - let idRuleEditor = view.element.children[0]._ruleEditor; + let idRuleEditor = getRuleViewRuleEditor(view, 0); let rgbColorPropEditor = idRuleEditor.rule.textProps[3].editor; yield runIncrementTest(rgbColorPropEditor, view, { @@ -103,7 +103,7 @@ function* testRgbIncrements(view) { function* testShorthandIncrements(view) { info("Testing keyboard increments within shorthand values"); - let idRuleEditor = view.element.children[0]._ruleEditor; + let idRuleEditor = getRuleViewRuleEditor(view, 0); let paddingPropEditor = idRuleEditor.rule.textProps[1].editor; yield runIncrementTest(paddingPropEditor, view, { @@ -119,7 +119,7 @@ function* testShorthandIncrements(view) { function* testOddCases(view) { info("Testing some more odd cases"); - let idRuleEditor = view.element.children[0]._ruleEditor; + let idRuleEditor = getRuleViewRuleEditor(view, 0); let marginPropEditor = idRuleEditor.rule.textProps[0].editor; yield runIncrementTest(marginPropEditor, view, { diff --git a/browser/devtools/styleinspector/test/browser_ruleview_edit-property_01.js b/browser/devtools/styleinspector/test/browser_ruleview_edit-property_01.js index 6feef57957b..a205df27f7b 100644 --- a/browser/devtools/styleinspector/test/browser_ruleview_edit-property_01.js +++ b/browser/devtools/styleinspector/test/browser_ruleview_edit-property_01.js @@ -40,7 +40,7 @@ let test = asyncTest(function*() { function* testEditProperty(view, name, value) { info("Test editing existing property name/value fields"); - let idRuleEditor = view.element.children[1]._ruleEditor; + let idRuleEditor = getRuleViewRuleEditor(view, 1); let propEditor = idRuleEditor.rule.textProps[0].editor; info("Focusing an existing property name in the rule-view"); diff --git a/browser/devtools/styleinspector/test/browser_ruleview_edit-property_02.js b/browser/devtools/styleinspector/test/browser_ruleview_edit-property_02.js index 995b8f2065d..89e45529b9d 100644 --- a/browser/devtools/styleinspector/test/browser_ruleview_edit-property_02.js +++ b/browser/devtools/styleinspector/test/browser_ruleview_edit-property_02.js @@ -29,7 +29,7 @@ let test = asyncTest(function*() { }); function* testEditProperty(inspector, ruleView) { - let idRuleEditor = ruleView.element.children[1]._ruleEditor; + let idRuleEditor = getRuleViewRuleEditor(ruleView, 1); let propEditor = idRuleEditor.rule.textProps[0].editor; let editor = yield focusEditableField(propEditor.nameSpan); @@ -77,7 +77,7 @@ function* testEditProperty(inspector, ruleView) { } function* testDisableProperty(inspector, ruleView) { - let idRuleEditor = ruleView.element.children[1]._ruleEditor; + let idRuleEditor = getRuleViewRuleEditor(ruleView, 1); let propEditor = idRuleEditor.rule.textProps[0].editor; info("Disabling a property"); diff --git a/browser/devtools/styleinspector/test/browser_ruleview_edit-selector-commit.js b/browser/devtools/styleinspector/test/browser_ruleview_edit-selector-commit.js new file mode 100644 index 00000000000..3362a251c75 --- /dev/null +++ b/browser/devtools/styleinspector/test/browser_ruleview_edit-selector-commit.js @@ -0,0 +1,98 @@ +/* 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"; + +// Test selector value is correctly displayed when committing the inplace editor +// with ENTER, ESC, SHIFT+TAB and TAB + +let PAGE_CONTENT = [ + '', + '