Bug 966896 - [rule view] Editing rules' selectors for the current selection in the CSS rule-view r=pbrosset

This commit is contained in:
Gabriel Luong 2014-06-24 10:02:16 +02:00
parent d87fd67865
commit f73fe0c96b
28 changed files with 441 additions and 42 deletions

View File

@ -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;
}

View File

@ -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;
}

View File

@ -19,7 +19,8 @@
pointer-events: none;
}
.ruleview-namecontainer {
.ruleview-namecontainer,
.ruleview-selectorcontainer {
cursor: text;
}

View File

@ -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]

View File

@ -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);

View File

@ -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);

View File

@ -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.");

View File

@ -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);

View File

@ -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,

View File

@ -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,

View File

@ -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);
}

View File

@ -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, {

View File

@ -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");

View File

@ -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");

View File

@ -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 = [
'<style type="text/css">',
' #testid {',
' text-align: center;',
' }',
'</style>',
'<div id="testid" class="testclass">Styled Node</div>',
].join("\n");
const TEST_DATA = [
{
node: "#testid",
value: ".testclass",
commitKey: "VK_ESCAPE",
modifiers: {},
expected: "#testid"
},
{
node: "#testid",
value: ".testclass",
commitKey: "VK_RETURN",
modifiers: {},
expected: ".testclass"
},
{
node: "#testid",
value: ".testclass",
commitKey: "VK_TAB",
modifiers: {},
expected: ".testclass"
},
{
node: "#testid",
value: ".testclass",
commitKey: "VK_TAB",
modifiers: {shiftKey: true},
expected: ".testclass"
}
];
let test = asyncTest(function*() {
yield addTab("data:text/html,test escaping selector change reverts back to original value");
info("Creating the test document");
content.document.body.innerHTML = PAGE_CONTENT;
info("Opening the rule-view");
let {toolbox, inspector, view} = yield openRuleView();
info("Iterating over the test data");
for (let data of TEST_DATA) {
yield runTestData(inspector, view, data);
}
});
function* runTestData(inspector, view, data) {
let {node, value, commitKey, modifiers, expected} = data;
info("Updating " + node + " to " + value + " and committing with " + commitKey + ". Expecting: " + expected);
info("Selecting the test element");
yield selectNode(node, inspector);
let idRuleEditor = getRuleViewRuleEditor(view, 1);
info("Focusing an existing selector name in the rule-view");
let editor = yield focusEditableField(idRuleEditor.selectorText);
is(inplaceEditor(idRuleEditor.selectorText), editor,
"The selector editor got focused");
info("Enter the new selector value: " + value);
editor.input.value = value;
info("Entering the commit key " + commitKey + " " + modifiers);
EventUtils.synthesizeKey(commitKey, modifiers);
if (commitKey === "VK_ESCAPE") {
is(idRuleEditor.rule.selectorText, expected,
"Value is as expected: " + expected);
is(idRuleEditor.isEditing, false, "Selector is not being edited.")
} else {
yield once(view.element, "CssRuleViewRefreshed");
ok(getRuleViewRule(view, expected),
"Rule with " + name + " selector exists.");
}
info("Resetting page content");
content.document.body.innerHTML = PAGE_CONTENT;
}

View File

@ -0,0 +1,66 @@
/* 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";
// Testing selector inplace-editor behaviors in the rule-view
let PAGE_CONTENT = [
'<style type="text/css">',
' .testclass {',
' text-align: center;',
' }',
'</style>',
'<div id="testid" class="testclass">Styled Node</div>',
'<span id="testid2">This is a span</span>'
].join("\n");
let test = asyncTest(function*() {
yield addTab("data:text/html,test rule view selector changes");
info("Creating the test document");
content.document.body.innerHTML = PAGE_CONTENT;
info("Opening the rule-view");
let {toolbox, inspector, view} = yield openRuleView();
info("Selecting the test element");
yield selectNode("#testid", inspector);
yield testEditSelector(view, "span");
info("Selecting the modified element");
yield selectNode("#testid2", inspector);
yield checkModifiedElement(view, "span");
});
function* testEditSelector(view, name) {
info("Test editing existing selector fields");
let idRuleEditor = getRuleViewRuleEditor(view, 1);
info("Focusing an existing selector name in the rule-view");
let editor = yield focusEditableField(idRuleEditor.selectorText);
is(inplaceEditor(idRuleEditor.selectorText), editor,
"The selector editor got focused");
info("Entering a new selector name and committing");
editor.input.value = name;
info("Waiting for rule view to refresh");
let onRuleViewRefresh = once(view.element, "CssRuleViewRefreshed");
info("Entering the commit key");
EventUtils.synthesizeKey("VK_RETURN", {});
yield onRuleViewRefresh;
is(view._elementStyle.rules.length, 1, "Should have 1 rule.");
is(getRuleViewRule(view, name), undefined,
name + " selector has been removed.");
}
function* checkModifiedElement(view, name) {
is(view._elementStyle.rules.length, 2, "Should have 2 rules.");
ok(getRuleViewRule(view, name), "Rule with " + name + " selector exists.");
}

View File

@ -0,0 +1,80 @@
/* 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";
// Testing selector inplace-editor behaviors in the rule-view with pseudo
// classes and elements
let PAGE_CONTENT = [
'<style type="text/css">',
' .testclass {',
' text-align: center;',
' }',
' #testid3:after {',
' content: "+"',
' }',
'</style>',
'<div id="testid">Styled Node</div>',
'<span class="testclass">This is a span</span>',
'<div class="testclass2">A</div>',
'<div id="testid3">B</div>'
].join("\n");
let test = asyncTest(function*() {
yield addTab("data:text/html,test rule view selector changes");
info("Creating the test document");
content.document.body.innerHTML = PAGE_CONTENT;
info("Opening the rule-view");
let {toolbox, inspector, view} = yield openRuleView();
info("Selecting the test element");
yield selectNode(".testclass", inspector);
yield testEditSelector(view, "div:nth-child(2)");
info("Selecting the modified element");
yield selectNode("#testid", inspector);
yield checkModifiedElement(view, "div:nth-child(2)");
info("Selecting the test element");
yield selectNode("#testid3", inspector);
yield testEditSelector(view, ".testclass2:after");
info("Selecting the modified element");
yield selectNode(".testclass2", inspector);
yield checkModifiedElement(view, ".testclass2:after");
});
function* testEditSelector(view, name) {
info("Test editing existing selector fields");
let idRuleEditor = getRuleViewRuleEditor(view, 1);
info("Focusing an existing selector name in the rule-view");
let editor = yield focusEditableField(idRuleEditor.selectorText);
is(inplaceEditor(idRuleEditor.selectorText), editor,
"The selector editor got focused");
info("Entering a new selector name: " + name);
editor.input.value = name;
info("Waiting for rule view to refresh");
let onRuleViewRefresh = once(view.element, "CssRuleViewRefreshed");
info("Entering the commit key");
EventUtils.synthesizeKey("VK_RETURN", {});
yield onRuleViewRefresh;
is(view._elementStyle.rules.length, 1, "Should have 1 rule.");
is(getRuleViewRule(view, name), undefined,
name + " selector has been removed.");
}
function* checkModifiedElement(view, name) {
is(view._elementStyle.rules.length, 2, "Should have 2 rules.");
ok(getRuleViewRule(view, name), "Rule with " + name + " selector exists.");
}

View File

@ -40,7 +40,7 @@ let test = asyncTest(function*() {
function* testLivePreviewData(data, ruleView, testElement) {
let idRuleEditor = ruleView.element.children[1]._ruleEditor;
let idRuleEditor = getRuleViewRuleEditor(ruleView, 1);
let propEditor = idRuleEditor.rule.textProps[0].editor;
info("Focusing the property value inplace-editor");

View File

@ -17,7 +17,7 @@ let test = asyncTest(function*() {
newElement.textContent = "Test Element";
content.document.body.appendChild(newElement);
yield selectNode(newElement, inspector);
let ruleEditor = view.element.children[0]._ruleEditor;
let ruleEditor = getRuleViewRuleEditor(view, 0);
yield testCreateNewMultiDuplicates(inspector, ruleEditor);
});
@ -51,4 +51,4 @@ function* testCreateNewMultiDuplicates(inspector, ruleEditor) {
is(ruleEditor.rule.textProps[6].value, "violet", "Should have correct property value");
yield inspector.once("inspector-updated");
}
}

View File

@ -17,7 +17,7 @@ let test = asyncTest(function*() {
newElement.textContent = "Test Element";
content.document.body.appendChild(newElement);
yield selectNode(newElement, inspector);
let ruleEditor = view.element.children[0]._ruleEditor;
let ruleEditor = getRuleViewRuleEditor(view, 0);
yield testCreateNewMultiPriority(inspector, ruleEditor);
});

View File

@ -17,7 +17,7 @@ let test = asyncTest(function*() {
newElement.textContent = "Test Element";
content.document.body.appendChild(newElement);
yield selectNode(newElement, inspector);
let ruleEditor = view.element.children[0]._ruleEditor;
let ruleEditor = getRuleViewRuleEditor(view, 0);
yield testCreateNewMultiUnfinished(inspector, ruleEditor, view);
});

View File

@ -17,7 +17,7 @@ let test = asyncTest(function*() {
newElement.textContent = "Test Element";
content.document.body.appendChild(newElement);
yield selectNode(newElement, inspector);
let ruleEditor = view.element.children[0]._ruleEditor;
let ruleEditor = getRuleViewRuleEditor(view, 0);
yield testCreateNewMultiPartialUnfinished(inspector, ruleEditor, view);
});

View File

@ -17,7 +17,7 @@ let test = asyncTest(function*() {
newElement.textContent = "Test Element";
content.document.body.appendChild(newElement);
yield selectNode(newElement, inspector);
let ruleEditor = view.element.children[0]._ruleEditor;
let ruleEditor = getRuleViewRuleEditor(view, 0);
yield testCreateNewMulti(inspector, ruleEditor);
});

View File

@ -17,7 +17,7 @@ let test = asyncTest(function*() {
newElement.textContent = "Test Element";
content.document.body.appendChild(newElement);
yield selectNode(newElement, inspector);
let ruleEditor = view.element.children[0]._ruleEditor;
let ruleEditor = getRuleViewRuleEditor(view, 0);
yield testMultiValues(inspector, ruleEditor, view);
});

View File

@ -42,7 +42,7 @@ let test = asyncTest(function*() {
ok(!hs.promises[TYPE], "And no highlighter is being initialized either");
info("Disabling the applied property");
let classRuleEditor = rView.element.children[1]._ruleEditor;
let classRuleEditor = getRuleViewRuleEditor(rView, 1);
let propEditor = classRuleEditor.rule.textProps[0].editor;
propEditor.enable.click();
yield classRuleEditor.rule._applyingModifications;

View File

@ -595,6 +595,16 @@ function getRuleViewLinkByIndex(view, index) {
return links[index];
}
/**
* Get the rule editor from the rule-view given its index
* @param {CssRuleView} view The instance of the rule-view panel
* @param {Number} index The index of the link to get
* @return {DOMNode} The rule editor if any at this index
*/
function getRuleViewRuleEditor(view, index) {
return view.element.children[index]._ruleEditor;
}
/**
* Click on a rule-view's close brace to focus a new property name editor
* @param {RuleEditor} ruleEditor An instance of RuleEditor that will receive

View File

@ -121,7 +121,10 @@ RootActor.prototype = {
storageInspectorReadOnly: true,
// Whether conditional breakpoints are supported
conditionalBreakpoints: true,
bulk: true
bulk: true,
// Whether the style rule actor implements the modifySelector method
// that modifies the rule's selector
selectorEditable: true
},
/**

View File

@ -595,6 +595,18 @@ var StyleRuleActor = protocol.ActorClass({
// to which this rule belongs.
get marshallPool() this.pageStyle,
getDocument: function(sheet) {
let document;
if (sheet.ownerNode instanceof Ci.nsIDOMHTMLDocument) {
document = sheet.ownerNode;
} else {
document = sheet.ownerNode.ownerDocument;
}
return document;
},
toString: function() "[StyleRuleActor for " + this.rawRule + "]",
form: function(detail) {
@ -677,11 +689,7 @@ var StyleRuleActor = protocol.ActorClass({
parentStyleSheet = parentStyleSheet.ownerRule.parentStyleSheet;
}
if (parentStyleSheet.ownerNode instanceof Ci.nsIDOMHTMLDocument) {
document = parentStyleSheet.ownerNode;
} else {
document = parentStyleSheet.ownerNode.ownerDocument;
}
document = this.getDocument(parentStyleSheet);
}
let tempElement = document.createElement("div");
@ -700,7 +708,63 @@ var StyleRuleActor = protocol.ActorClass({
}, {
request: { modifications: Arg(0, "array:json") },
response: { rule: RetVal("domstylerule") }
})
}),
/**
* Removes the current rule and inserts a new rule with the new selector
* into the parent style sheet.
* @param string value
* The new selector value
* @returns boolean
* Returns a boolean if the selector in the stylesheet was modified,
* and false otherwise
*/
modifySelector: method(function(value) {
if (this.type === ELEMENT_STYLE) {
return false;
}
let rule = this.rawRule;
let parentStyleSheet = rule.parentStyleSheet;
let document = this.getDocument(parentStyleSheet);
// Extract the selector, and pseudo elements and classes
let [selector, pseudoProp] = value.split(/(:{1,2}.+$)/);
let selectorElement;
try {
selectorElement = document.querySelector(selector);
} catch (e) {
return false;
}
// Check if the selector is valid and not the same as the original
// selector
if (selectorElement && rule.selectorText !== value) {
let cssRules = parentStyleSheet.cssRules;
// Delete the currently selected rule
let i = 0;
for (let cssRule of cssRules) {
if (rule === cssRule) {
parentStyleSheet.deleteRule(i);
break;
}
i++;
}
// Inserts the new style rule into the current style sheet
let ruleText = rule.cssText.slice(rule.selectorText.length).trim();
parentStyleSheet.insertRule(value + " " + ruleText, i);
return true;
} else {
return false;
}
}, {
request: { selector: Arg(0, "string") },
response: { isModified: RetVal("boolean") },
}),
});
/**