mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 984880 - as-authored styles in the rule view; r=pbrosset,r=bgrins
This commit is contained in:
parent
a821c5000b
commit
86b4213d48
@ -1379,7 +1379,7 @@ pref("devtools.inspector.showAllAnonymousContent", false);
|
||||
pref("devtools.inspector.mdnDocsTooltip.enabled", true);
|
||||
|
||||
// DevTools default color unit
|
||||
pref("devtools.defaultColorUnit", "hex");
|
||||
pref("devtools.defaultColorUnit", "authored");
|
||||
|
||||
// Enable the Responsive UI tool
|
||||
pref("devtools.responsiveUI.no-reload-notification", false);
|
||||
|
@ -69,6 +69,10 @@ values from browser.dtd. -->
|
||||
- inspector. This is visible in the options panel. -->
|
||||
<!ENTITY options.defaultColorUnit.accesskey "U">
|
||||
|
||||
<!-- LOCALIZATION NOTE (options.defaultColorUnit.authored): This is used in the
|
||||
- 'Default color unit' dropdown list and is visible in the options panel. -->
|
||||
<!ENTITY options.defaultColorUnit.authored "As Authored">
|
||||
|
||||
<!-- LOCALIZATION NOTE (options.defaultColorUnit.hex): This is used in the
|
||||
- 'Default color unit' dropdown list and is visible in the options panel. -->
|
||||
<!ENTITY options.defaultColorUnit.hex "Hex">
|
||||
|
@ -55,6 +55,7 @@
|
||||
<menulist id="defaultColorUnitMenuList"
|
||||
data-pref="devtools.defaultColorUnit">
|
||||
<menupopup>
|
||||
<menuitem label="&options.defaultColorUnit.authored;" value="authored"/>
|
||||
<menuitem label="&options.defaultColorUnit.hex;" value="hex"/>
|
||||
<menuitem label="&options.defaultColorUnit.hsl;" value="hsl"/>
|
||||
<menuitem label="&options.defaultColorUnit.rgb;" value="rgb"/>
|
||||
|
@ -95,9 +95,9 @@ EditingSession.prototype = {
|
||||
}
|
||||
|
||||
if (property.value == "") {
|
||||
modifications.removeProperty(property.name);
|
||||
modifications.removeProperty(-1, property.name);
|
||||
} else {
|
||||
modifications.setProperty(property.name, property.value, "");
|
||||
modifications.setProperty(-1, property.name, property.value, "");
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,9 +113,9 @@ EditingSession.prototype = {
|
||||
|
||||
for (let [property, value] of this._modifications) {
|
||||
if (value != "") {
|
||||
modifications.setProperty(property, value, "");
|
||||
modifications.setProperty(-1, property, value, "");
|
||||
} else {
|
||||
modifications.removeProperty(property);
|
||||
modifications.removeProperty(-1, property);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,12 +33,8 @@ var COLOR_TEST_CLASS = "test-class";
|
||||
// property. |value| is the CSS text to use. |segments| is an array
|
||||
// describing the expected result. If an element of |segments| is a
|
||||
// string, it is simply appended to the expected string. Otherwise,
|
||||
// it must be an object with a |value| property and a |name| property.
|
||||
// These describe the color and are both used in the generated
|
||||
// expected output -- |name| is the color name as it appears in the
|
||||
// input (e.g., "red"); and |value| is the hash-style numeric value
|
||||
// for the color, which parseCssProperty emits in some spots (e.g.,
|
||||
// "#F00").
|
||||
// it must be an object with a |value| property, which is the color
|
||||
// name as it appears in the input.
|
||||
//
|
||||
// This approach is taken to reduce boilerplate and to make it simpler
|
||||
// to modify the test when the parseCssProperty output changes.
|
||||
@ -53,10 +49,10 @@ function makeColorTest(name, value, segments) {
|
||||
if (typeof (segment) === "string") {
|
||||
result.expected += segment;
|
||||
} else {
|
||||
result.expected += "<span data-color=\"" + segment.value + "\">" +
|
||||
result.expected += "<span data-color=\"" + segment.name + "\">" +
|
||||
"<span style=\"background-color:" + segment.name +
|
||||
"\" class=\"" + COLOR_TEST_CLASS + "\"></span><span>" +
|
||||
segment.value + "</span></span>";
|
||||
segment.name + "</span></span>";
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,25 +64,25 @@ function makeColorTest(name, value, segments) {
|
||||
function testParseCssProperty(doc, parser) {
|
||||
let tests = [
|
||||
makeColorTest("border", "1px solid red",
|
||||
["1px solid ", {name: "red", value: "#F00"}]),
|
||||
["1px solid ", {name: "red"}]),
|
||||
|
||||
makeColorTest("background-image",
|
||||
"linear-gradient(to right, #F60 10%, rgba(0,0,0,1))",
|
||||
["linear-gradient(to right, ", {name: "#F60", value: "#F60"},
|
||||
" 10%, ", {name: "rgba(0,0,0,1)", value: "#000"},
|
||||
["linear-gradient(to right, ", {name: "#F60"},
|
||||
" 10%, ", {name: "rgba(0,0,0,1)"},
|
||||
")"]),
|
||||
|
||||
// In "arial black", "black" is a font, not a color.
|
||||
makeColorTest("font-family", "arial black", ["arial black"]),
|
||||
|
||||
makeColorTest("box-shadow", "0 0 1em red",
|
||||
["0 0 1em ", {name: "red", value: "#F00"}]),
|
||||
["0 0 1em ", {name: "red"}]),
|
||||
|
||||
makeColorTest("box-shadow",
|
||||
"0 0 1em red, 2px 2px 0 0 rgba(0,0,0,.5)",
|
||||
["0 0 1em ", {name: "red", value: "#F00"},
|
||||
["0 0 1em ", {name: "red"},
|
||||
", 2px 2px 0 0 ",
|
||||
{name: "rgba(0,0,0,.5)", value: "rgba(0,0,0,.5)"}]),
|
||||
{name: "rgba(0,0,0,.5)"}]),
|
||||
|
||||
makeColorTest("content", "\"red\"", ["\"red\""]),
|
||||
|
||||
@ -98,7 +94,7 @@ function testParseCssProperty(doc, parser) {
|
||||
["<span data-filters=\"blur(1px) drop-shadow(0 0 0 blue) ",
|
||||
"url(red.svg#blue)\"><span>",
|
||||
"blur(1px) drop-shadow(0 0 0 ",
|
||||
{name: "blue", value: "#00F"},
|
||||
{name: "blue"},
|
||||
") url(red.svg#blue)</span></span>"]),
|
||||
|
||||
makeColorTest("color", "currentColor", ["currentColor"]),
|
||||
|
@ -1145,6 +1145,8 @@ SwatchColorPickerTooltip.prototype = Heritage.extend(SwatchBasedEditorTooltip.pr
|
||||
// Then set spectrum's color and listen to color changes to preview them
|
||||
if (this.activeSwatch) {
|
||||
this.currentSwatchColor = this.activeSwatch.nextSibling;
|
||||
this._colorUnit =
|
||||
colorUtils.classifyColor(this.currentSwatchColor.textContent);
|
||||
let color = this.activeSwatch.style.backgroundColor;
|
||||
this.spectrum.then(spectrum => {
|
||||
spectrum.off("changed", this._onSpectrumColorChange);
|
||||
@ -1222,6 +1224,7 @@ SwatchColorPickerTooltip.prototype = Heritage.extend(SwatchBasedEditorTooltip.pr
|
||||
|
||||
_toDefaultType: function(color) {
|
||||
let colorObj = new colorUtils.CssColor(color);
|
||||
colorObj.colorUnit = this._colorUnit;
|
||||
return colorObj.toString();
|
||||
},
|
||||
|
||||
|
@ -86,6 +86,12 @@ function StyleSheetEditor(styleSheet, win, file, isNew, walker, highlighter) {
|
||||
this.walker = walker;
|
||||
this.highlighter = highlighter;
|
||||
|
||||
// True when we've called update() on the style sheet.
|
||||
this._isUpdating = false;
|
||||
// True when we've just set the editor text based on a style-applied
|
||||
// event from the StyleSheetActor.
|
||||
this._justSetText = false;
|
||||
|
||||
this._state = { // state to use when inputElement attaches
|
||||
text: "",
|
||||
selection: {
|
||||
@ -103,7 +109,8 @@ function StyleSheetEditor(styleSheet, win, file, isNew, walker, highlighter) {
|
||||
this._onPropertyChange = this._onPropertyChange.bind(this);
|
||||
this._onError = this._onError.bind(this);
|
||||
this._onMediaRuleMatchesChange = this._onMediaRuleMatchesChange.bind(this);
|
||||
this._onMediaRulesChanged = this._onMediaRulesChanged.bind(this)
|
||||
this._onMediaRulesChanged = this._onMediaRulesChanged.bind(this);
|
||||
this._onStyleApplied = this._onStyleApplied.bind(this);
|
||||
this.checkLinkedFileForChanges = this.checkLinkedFileForChanges.bind(this);
|
||||
this.markLinkedFileBroken = this.markLinkedFileBroken.bind(this);
|
||||
this.saveToFile = this.saveToFile.bind(this);
|
||||
@ -119,6 +126,7 @@ function StyleSheetEditor(styleSheet, win, file, isNew, walker, highlighter) {
|
||||
this.cssSheet.getMediaRules().then(this._onMediaRulesChanged, Cu.reportError);
|
||||
}
|
||||
this.cssSheet.on("media-rules-changed", this._onMediaRulesChanged);
|
||||
this.cssSheet.on("style-applied", this._onStyleApplied);
|
||||
this.savedFile = file;
|
||||
this.linkCSSFile();
|
||||
}
|
||||
@ -244,6 +252,27 @@ StyleSheetEditor.prototype = {
|
||||
this.emit("linked-css-file");
|
||||
},
|
||||
|
||||
/**
|
||||
* A helper function that fetches the source text from the style
|
||||
* sheet. The text is possibly prettified using
|
||||
* CssLogic.prettifyCSS. This also sets |this._state.text| to the
|
||||
* new text.
|
||||
*
|
||||
* @return {Promise} a promise that resolves to the new text
|
||||
*/
|
||||
_getSourceTextAndPrettify: function() {
|
||||
return this.styleSheet.getText().then((longStr) => {
|
||||
return longStr.string();
|
||||
}).then((source) => {
|
||||
let ruleCount = this.styleSheet.ruleCount;
|
||||
if (!this.styleSheet.isOriginalSource) {
|
||||
source = CssLogic.prettifyCSS(source, ruleCount);
|
||||
}
|
||||
this._state.text = source;
|
||||
return source;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Start fetching the full text source for this editor's sheet.
|
||||
*
|
||||
@ -251,19 +280,11 @@ StyleSheetEditor.prototype = {
|
||||
* A promise that'll resolve with the source text once the source
|
||||
* has been loaded or reject on unexpected error.
|
||||
*/
|
||||
fetchSource: function () {
|
||||
return Task.spawn(function* () {
|
||||
let longStr = yield this.styleSheet.getText();
|
||||
let source = yield longStr.string();
|
||||
let ruleCount = this.styleSheet.ruleCount;
|
||||
if (!this.styleSheet.isOriginalSource) {
|
||||
source = CssLogic.prettifyCSS(source, ruleCount);
|
||||
}
|
||||
this._state.text = source;
|
||||
fetchSource: function() {
|
||||
return this._getSourceTextAndPrettify().then((source) => {
|
||||
this.sourceLoaded = true;
|
||||
|
||||
return source;
|
||||
}.bind(this)).then(null, e => {
|
||||
}).then(null, e => {
|
||||
if (this._isDestroyed) {
|
||||
console.warn("Could not fetch the source for " +
|
||||
this.styleSheet.href +
|
||||
@ -323,6 +344,26 @@ StyleSheetEditor.prototype = {
|
||||
this.emit("property-change", property, value);
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the stylesheet text changes.
|
||||
*/
|
||||
_onStyleApplied: function() {
|
||||
if (this._isUpdating) {
|
||||
// We just applied an edit in the editor, so we can drop this
|
||||
// notification.
|
||||
this._isUpdating = false;
|
||||
} else if (this.sourceEditor) {
|
||||
this._getSourceTextAndPrettify().then((newText) => {
|
||||
this._justSetText = true;
|
||||
let firstLine = this.sourceEditor.getFirstVisibleLine();
|
||||
let pos = this.sourceEditor.getCursor();
|
||||
this.sourceEditor.setText(newText);
|
||||
this.sourceEditor.setFirstVisibleLine(firstLine);
|
||||
this.sourceEditor.setCursor(pos);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles changes to the list of @media rules in the stylesheet.
|
||||
* Emits 'media-rules-changed' if the list has changed.
|
||||
@ -489,6 +530,11 @@ StyleSheetEditor.prototype = {
|
||||
return; // TODO: do we want to do this?
|
||||
}
|
||||
|
||||
if (this._justSetText) {
|
||||
this._justSetText = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this._updateTask = null; // reset only if we actually perform an update
|
||||
// (stylesheet is enabled) so that 'missed' updates
|
||||
// while the stylesheet is disabled can be performed
|
||||
@ -498,6 +544,7 @@ StyleSheetEditor.prototype = {
|
||||
this._state.text = this.sourceEditor.getText();
|
||||
}
|
||||
|
||||
this._isUpdating = true;
|
||||
this.styleSheet.update(this._state.text, this.transitionsEnabled)
|
||||
.then(null, Cu.reportError);
|
||||
},
|
||||
@ -726,10 +773,11 @@ StyleSheetEditor.prototype = {
|
||||
}
|
||||
this.cssSheet.off("property-change", this._onPropertyChange);
|
||||
this.cssSheet.off("media-rules-changed", this._onMediaRulesChanged);
|
||||
this.cssSheet.off("style-applied", this._onStyleApplied);
|
||||
this.styleSheet.off("error", this._onError);
|
||||
this._isDestroyed = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Find a path on disk for a file given it's hosted uri, the uri of the
|
||||
|
@ -48,6 +48,7 @@ support-files =
|
||||
doc_uncached.css
|
||||
doc_uncached.html
|
||||
doc_xulpage.xul
|
||||
sync.html
|
||||
|
||||
[browser_styleeditor_autocomplete.js]
|
||||
[browser_styleeditor_autocomplete-disabled.js]
|
||||
@ -83,5 +84,10 @@ skip-if = e10s # Bug 1055333 - style editor tests disabled with e10s
|
||||
[browser_styleeditor_sourcemap_large.js]
|
||||
[browser_styleeditor_sourcemap_watching.js]
|
||||
skip-if = e10s # Bug 1055333 - style editor tests disabled with e10s
|
||||
[browser_styleeditor_sync.js]
|
||||
[browser_styleeditor_syncAddRule.js]
|
||||
[browser_styleeditor_syncAlreadyOpen.js]
|
||||
[browser_styleeditor_syncEditSelector.js]
|
||||
[browser_styleeditor_syncIntoRuleView.js]
|
||||
[browser_styleeditor_transition_rule.js]
|
||||
[browser_styleeditor_xul.js]
|
||||
|
74
devtools/client/styleeditor/test/browser_styleeditor_sync.js
Normal file
74
devtools/client/styleeditor/test/browser_styleeditor_sync.js
Normal file
@ -0,0 +1,74 @@
|
||||
/* vim: set 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 that changes in the style inspector are synchronized into the
|
||||
// style editor.
|
||||
|
||||
Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/styleinspector/test/head.js", this);
|
||||
|
||||
const TESTCASE_URI = TEST_BASE_HTTP + "sync.html";
|
||||
|
||||
const expectedText = `
|
||||
body {
|
||||
border-width: 15px;
|
||||
/*! color: red; */
|
||||
}
|
||||
|
||||
#testid {
|
||||
/*! font-size: 4em; */
|
||||
}
|
||||
`;
|
||||
|
||||
function* closeAndReopenToolbox() {
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
yield gDevTools.closeToolbox(target);
|
||||
let { ui: newui } = yield openStyleEditor();
|
||||
return newui;
|
||||
}
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab(TESTCASE_URI);
|
||||
let { inspector, view } = yield openRuleView();
|
||||
yield selectNode("#testid", inspector);
|
||||
let ruleEditor = getRuleViewRuleEditor(view, 1);
|
||||
|
||||
// Disable the "font-size" property.
|
||||
let propEditor = ruleEditor.rule.textProps[0].editor;
|
||||
let onModification = view.once("ruleview-changed");
|
||||
propEditor.enable.click();
|
||||
yield onModification;
|
||||
|
||||
// Disable the "color" property. Note that this property is in a
|
||||
// rule that also contains a non-inherited property -- so this test
|
||||
// is also testing that property editing works properly in this
|
||||
// situation.
|
||||
ruleEditor = getRuleViewRuleEditor(view, 3);
|
||||
propEditor = ruleEditor.rule.textProps[1].editor;
|
||||
onModification = view.once("ruleview-changed");
|
||||
propEditor.enable.click();
|
||||
yield onModification;
|
||||
|
||||
let { ui } = yield openStyleEditor();
|
||||
|
||||
let editor = yield ui.editors[0].getSourceEditor();
|
||||
let text = editor.sourceEditor.getText();
|
||||
is(text, expectedText, "style inspector changes are synced");
|
||||
|
||||
// Close and reopen the toolbox, to see that the edited text remains
|
||||
// available.
|
||||
ui = yield closeAndReopenToolbox();
|
||||
editor = yield ui.editors[0].getSourceEditor();
|
||||
text = editor.sourceEditor.getText();
|
||||
is(text, expectedText, "changes remain after close and reopen");
|
||||
|
||||
// For the time being, the actor does not update the style's owning
|
||||
// node's textContent. See bug 1205380.
|
||||
yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function*() {
|
||||
let style = content.document.querySelector("style");
|
||||
return style.textContent;
|
||||
}).then((textContent) => {
|
||||
isnot(textContent, expectedText, "changes not written back to style node");
|
||||
});
|
||||
});
|
@ -0,0 +1,33 @@
|
||||
/* vim: set 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 that adding a new rule is synced to the style editor.
|
||||
|
||||
Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/styleinspector/test/head.js", this);
|
||||
|
||||
const TESTCASE_URI = TEST_BASE_HTTP + "sync.html";
|
||||
|
||||
const expectedText = `
|
||||
#testid {
|
||||
}`;
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab(TESTCASE_URI);
|
||||
let { inspector, view } = yield openRuleView();
|
||||
yield selectNode("#testid", inspector);
|
||||
|
||||
let onRuleViewChanged = once(view, "ruleview-changed");
|
||||
view.addRuleButton.click();
|
||||
yield onRuleViewChanged;
|
||||
|
||||
let { ui } = yield openStyleEditor();
|
||||
|
||||
info("Selecting the second editor");
|
||||
yield ui.selectStyleSheet(ui.editors[1].styleSheet);
|
||||
|
||||
let editor = ui.editors[1];
|
||||
let text = editor.sourceEditor.getText();
|
||||
is(text, expectedText, "selector edits are synced");
|
||||
});
|
@ -0,0 +1,52 @@
|
||||
/* vim: set 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 that changes in the style inspector are synchronized into the
|
||||
// style editor.
|
||||
|
||||
Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/styleinspector/test/head.js", this);
|
||||
|
||||
const TESTCASE_URI = TEST_BASE_HTTP + "sync.html";
|
||||
|
||||
const expectedText = `
|
||||
body {
|
||||
border-width: 15px;
|
||||
color: red;
|
||||
}
|
||||
|
||||
#testid {
|
||||
/*! font-size: 4em; */
|
||||
}
|
||||
`;
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab(TESTCASE_URI);
|
||||
|
||||
let { inspector, view } = yield openRuleView();
|
||||
|
||||
// In this test, make sure the style editor is open before making
|
||||
// changes in the inspector.
|
||||
let { ui } = yield openStyleEditor();
|
||||
let editor = yield ui.editors[0].getSourceEditor();
|
||||
|
||||
let onEditorChange = promise.defer();
|
||||
editor.sourceEditor.on("change", onEditorChange.resolve);
|
||||
|
||||
yield openRuleView();
|
||||
yield selectNode("#testid", inspector);
|
||||
let ruleEditor = getRuleViewRuleEditor(view, 1);
|
||||
|
||||
// Disable the "font-size" property.
|
||||
let propEditor = ruleEditor.rule.textProps[0].editor;
|
||||
let onModification = view.once("ruleview-changed");
|
||||
propEditor.enable.click();
|
||||
yield onModification;
|
||||
|
||||
yield openStyleEditor();
|
||||
yield onEditorChange.promise;
|
||||
|
||||
let text = editor.sourceEditor.getText();
|
||||
is(text, expectedText, "style inspector changes are synced");
|
||||
});
|
@ -0,0 +1,41 @@
|
||||
/* vim: set 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 that changes in the style inspector are synchronized into the
|
||||
// style editor.
|
||||
|
||||
Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/styleinspector/test/head.js", this);
|
||||
|
||||
const TESTCASE_URI = TEST_BASE_HTTP + "sync.html";
|
||||
|
||||
const expectedText = `
|
||||
body {
|
||||
border-width: 15px;
|
||||
color: red;
|
||||
}
|
||||
|
||||
#testid, span {
|
||||
font-size: 4em;
|
||||
}
|
||||
`;
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab(TESTCASE_URI);
|
||||
let { inspector, view } = yield openRuleView();
|
||||
yield selectNode("#testid", inspector);
|
||||
let ruleEditor = getRuleViewRuleEditor(view, 1);
|
||||
|
||||
let editor = yield focusEditableField(view, ruleEditor.selectorText);
|
||||
editor.input.value = "#testid, span";
|
||||
let onRuleViewChanged = once(view, "ruleview-changed");
|
||||
EventUtils.synthesizeKey("VK_RETURN", {});
|
||||
yield onRuleViewChanged;
|
||||
|
||||
let { ui } = yield openStyleEditor();
|
||||
|
||||
editor = yield ui.editors[0].getSourceEditor();
|
||||
let text = editor.sourceEditor.getText();
|
||||
is(text, expectedText, "selector edits are synced");
|
||||
});
|
@ -0,0 +1,51 @@
|
||||
/* vim: set 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 that changes in the style editor are synchronized into the
|
||||
// style inspector.
|
||||
|
||||
Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/styleinspector/test/head.js", this);
|
||||
|
||||
const TEST_URI = `
|
||||
<style type='text/css'>
|
||||
div { background-color: seagreen; }
|
||||
</style>
|
||||
<div id='testid' class='testclass'>Styled Node</div>
|
||||
`;
|
||||
|
||||
const TESTCASE_CSS_SOURCE = "#testid { color: chartreuse; }";
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||
|
||||
let {inspector, view} = yield openRuleView();
|
||||
yield selectNode("#testid", inspector);
|
||||
|
||||
let { panel, ui } = yield openStyleEditor();
|
||||
|
||||
let editor = yield ui.editors[0].getSourceEditor();
|
||||
|
||||
let waitForRuleView = view.once("ruleview-refreshed");
|
||||
yield typeInEditor(editor, panel.panelWindow);
|
||||
yield waitForRuleView;
|
||||
|
||||
let value = getRuleViewPropertyValue(view, "#testid", "color");
|
||||
is(value, "chartreuse", "check that edits were synced to rule view");
|
||||
});
|
||||
|
||||
function typeInEditor(aEditor, panelWindow) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
waitForFocus(function() {
|
||||
for (let c of TESTCASE_CSS_SOURCE) {
|
||||
EventUtils.synthesizeKey(c, {}, panelWindow);
|
||||
}
|
||||
ok(aEditor.unsaved, "new editor has unsaved flag");
|
||||
|
||||
deferred.resolve();
|
||||
}, panelWindow);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
@ -71,17 +71,29 @@ function* cleanup()
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new tab in specified window navigates it to the given URL and
|
||||
* opens style editor in it.
|
||||
* Open the style editor for the current tab.
|
||||
*/
|
||||
var openStyleEditorForURL = Task.async(function* (url, win) {
|
||||
let tab = yield addTab(url, win);
|
||||
var openStyleEditor = Task.async(function*(tab) {
|
||||
if (!tab) {
|
||||
tab = gBrowser.selectedTab;
|
||||
}
|
||||
let target = TargetFactory.forTab(tab);
|
||||
let toolbox = yield gDevTools.showToolbox(target, "styleeditor");
|
||||
let panel = toolbox.getPanel("styleeditor");
|
||||
let ui = panel.UI;
|
||||
|
||||
return { tab, toolbox, panel, ui };
|
||||
return { toolbox, panel, ui };
|
||||
});
|
||||
|
||||
/**
|
||||
* Creates a new tab in specified window navigates it to the given URL and
|
||||
* opens style editor in it.
|
||||
*/
|
||||
var openStyleEditorForURL = Task.async(function* (url, win) {
|
||||
let tab = yield addTab(url, win);
|
||||
let result = yield openStyleEditor(tab);
|
||||
result.tab = tab;
|
||||
return result;
|
||||
});
|
||||
|
||||
/**
|
||||
|
20
devtools/client/styleeditor/test/sync.html
Normal file
20
devtools/client/styleeditor/test/sync.html
Normal file
@ -0,0 +1,20 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>simple testcase</title>
|
||||
<style type="text/css">
|
||||
body {
|
||||
border-width: 15px;
|
||||
color: red;
|
||||
}
|
||||
|
||||
#testid {
|
||||
font-size: 4em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="testid">simple testcase</div>
|
||||
</body>
|
||||
</html>
|
@ -109,7 +109,7 @@ function createDummyDocument() {
|
||||
* Responsible for applying changes to the properties in a rule.
|
||||
* Maintains a list of TextProperty objects.
|
||||
* TextProperty:
|
||||
* Manages a single property from the cssText attribute of the
|
||||
* Manages a single property from the authoredText attribute of the
|
||||
* relevant declaration.
|
||||
* Maintains a list of computed properties that come from this
|
||||
* property declaration.
|
||||
@ -183,6 +183,12 @@ ElementStyle.prototype = {
|
||||
}
|
||||
this.destroyed = true;
|
||||
|
||||
for (let rule of this.rules) {
|
||||
if (rule.editor) {
|
||||
rule.editor.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
this.dummyElement = null;
|
||||
this.dummyElementPromise.then(dummyElement => {
|
||||
dummyElement.remove();
|
||||
@ -226,12 +232,12 @@ ElementStyle.prototype = {
|
||||
|
||||
// Store the current list of rules (if any) during the population
|
||||
// process. They will be reused if possible.
|
||||
this._refreshRules = this.rules;
|
||||
let existingRules = this.rules;
|
||||
|
||||
this.rules = [];
|
||||
|
||||
for (let entry of entries) {
|
||||
this._maybeAddRule(entry);
|
||||
this._maybeAddRule(entry, existingRules);
|
||||
}
|
||||
|
||||
// Mark overridden computed styles.
|
||||
@ -240,7 +246,11 @@ ElementStyle.prototype = {
|
||||
this._sortRulesForPseudoElement();
|
||||
|
||||
// We're done with the previous list of rules.
|
||||
delete this._refreshRules;
|
||||
for (let r of existingRules) {
|
||||
if (r && r.editor) {
|
||||
r.editor.destroy();
|
||||
}
|
||||
}
|
||||
});
|
||||
}).then(null, e => {
|
||||
// populate is often called after a setTimeout,
|
||||
@ -269,9 +279,12 @@ ElementStyle.prototype = {
|
||||
*
|
||||
* @param {Object} options
|
||||
* Options for creating the Rule, see the Rule constructor.
|
||||
* @param {Array} existingRules
|
||||
* Rules to reuse if possible. If a rule is reused, then it
|
||||
* it will be deleted from this array.
|
||||
* @return {Boolean} true if we added the rule.
|
||||
*/
|
||||
_maybeAddRule: function(options) {
|
||||
_maybeAddRule: function(options, existingRules) {
|
||||
// If we've already included this domRule (for example, when a
|
||||
// common selector is inherited), ignore it.
|
||||
if (options.rule &&
|
||||
@ -287,13 +300,12 @@ ElementStyle.prototype = {
|
||||
|
||||
// If we're refreshing and the rule previously existed, reuse the
|
||||
// Rule object.
|
||||
if (this._refreshRules) {
|
||||
for (let r of this._refreshRules) {
|
||||
if (r.matches(options)) {
|
||||
rule = r;
|
||||
rule.refresh(options);
|
||||
break;
|
||||
}
|
||||
if (existingRules) {
|
||||
let ruleIndex = existingRules.findIndex((r) => r.matches(options));
|
||||
if (ruleIndex >= 0) {
|
||||
rule = existingRules[ruleIndex];
|
||||
rule.refresh(options);
|
||||
existingRules.splice(ruleIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -302,8 +314,8 @@ ElementStyle.prototype = {
|
||||
rule = new Rule(this, options);
|
||||
}
|
||||
|
||||
// Ignore inherited rules with no properties.
|
||||
if (options.inherited && rule.textProps.length === 0) {
|
||||
// Ignore inherited rules with no visible properties.
|
||||
if (options.inherited && !rule.hasAnyVisibleProperties()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -372,6 +384,16 @@ ElementStyle.prototype = {
|
||||
let taken = {};
|
||||
for (let computedProp of computedProps) {
|
||||
let earlier = taken[computedProp.name];
|
||||
|
||||
// Prevent -webkit-gradient from being selected after unchecking
|
||||
// linear-gradient in this case:
|
||||
// -moz-linear-gradient: ...;
|
||||
// -webkit-linear-gradient: ...;
|
||||
// linear-gradient: ...;
|
||||
if (!computedProp.textProp.isValid()) {
|
||||
computedProp.overridden = true;
|
||||
continue;
|
||||
}
|
||||
let overridden;
|
||||
if (earlier &&
|
||||
computedProp.priority === "important" &&
|
||||
@ -463,7 +485,7 @@ function Rule(elementStyle, options) {
|
||||
this.mediaText = this.domRule.mediaText;
|
||||
}
|
||||
|
||||
// Populate the text properties with the style's current cssText
|
||||
// Populate the text properties with the style's current authoredText
|
||||
// value, and add in any disabled properties from the store.
|
||||
this.textProps = this._getTextProperties();
|
||||
this.textProps = this.textProps.concat(this._getDisabledProperties());
|
||||
@ -473,17 +495,12 @@ Rule.prototype = {
|
||||
mediaText: "",
|
||||
|
||||
get title() {
|
||||
if (this._title) {
|
||||
return this._title;
|
||||
}
|
||||
this._title = CssLogic.shortSource(this.sheet);
|
||||
let title = CssLogic.shortSource(this.sheet);
|
||||
if (this.domRule.type !== ELEMENT_STYLE && this.ruleLine > 0) {
|
||||
this._title += ":" + this.ruleLine;
|
||||
title += ":" + this.ruleLine;
|
||||
}
|
||||
|
||||
this._title = this._title +
|
||||
(this.mediaText ? " @media " + this.mediaText : "");
|
||||
return this._title;
|
||||
return title + (this.mediaText ? " @media " + this.mediaText : "");
|
||||
},
|
||||
|
||||
get inheritedSource() {
|
||||
@ -551,10 +568,6 @@ Rule.prototype = {
|
||||
* both the full and short version of the source string.
|
||||
*/
|
||||
getOriginalSourceStrings: function() {
|
||||
if (this._originalSourceStrings) {
|
||||
return promise.resolve(this._originalSourceStrings);
|
||||
}
|
||||
|
||||
return this.domRule.getOriginalLocation().then(({href, line, mediaText}) => {
|
||||
let mediaString = mediaText ? " @" + mediaText : "";
|
||||
|
||||
@ -564,7 +577,6 @@ Rule.prototype = {
|
||||
short: CssLogic.shortSource({href: href}) + ":" + line + mediaString
|
||||
};
|
||||
|
||||
this._originalSourceStrings = sourceStrings;
|
||||
return sourceStrings;
|
||||
});
|
||||
},
|
||||
@ -595,31 +607,35 @@ Rule.prototype = {
|
||||
createProperty: function(name, value, priority, siblingProp) {
|
||||
let prop = new TextProperty(this, name, value, priority);
|
||||
|
||||
let ind;
|
||||
if (siblingProp) {
|
||||
let ind = this.textProps.indexOf(siblingProp);
|
||||
this.textProps.splice(ind + 1, 0, prop);
|
||||
ind = this.textProps.indexOf(siblingProp) + 1;
|
||||
this.textProps.splice(ind, 0, prop);
|
||||
} else {
|
||||
ind = this.textProps.length;
|
||||
this.textProps.push(prop);
|
||||
}
|
||||
|
||||
this.applyProperties();
|
||||
this.applyProperties((modifications) => {
|
||||
modifications.createProperty(ind, name, value, priority);
|
||||
});
|
||||
return prop;
|
||||
},
|
||||
|
||||
/**
|
||||
* Reapply all the properties in this rule, and update their
|
||||
* computed styles. Store disabled properties in the element
|
||||
* style's store. Will re-mark overridden properties.
|
||||
* Helper function for applyProperties that is called when the actor
|
||||
* does not support as-authored styles. Store disabled properties
|
||||
* in the element style's store.
|
||||
*/
|
||||
applyProperties: function(modifications) {
|
||||
_applyPropertiesNoAuthored: function(modifications) {
|
||||
this.elementStyle.markOverriddenAll();
|
||||
|
||||
if (!modifications) {
|
||||
modifications = this.style.startModifyingProperties();
|
||||
}
|
||||
let disabledProps = [];
|
||||
|
||||
for (let prop of this.textProps) {
|
||||
if (prop.invisible) {
|
||||
continue;
|
||||
}
|
||||
if (!prop.enabled) {
|
||||
disabledProps.push({
|
||||
name: prop.name,
|
||||
@ -632,7 +648,7 @@ Rule.prototype = {
|
||||
continue;
|
||||
}
|
||||
|
||||
modifications.setProperty(prop.name, prop.value, prop.priority);
|
||||
modifications.setProperty(-1, prop.name, prop.value, prop.priority);
|
||||
|
||||
prop.updateComputed();
|
||||
}
|
||||
@ -645,9 +661,9 @@ Rule.prototype = {
|
||||
disabled.delete(this.style);
|
||||
}
|
||||
|
||||
let modificationsPromise = modifications.apply().then(() => {
|
||||
return modifications.apply().then(() => {
|
||||
let cssProps = {};
|
||||
for (let cssProp of parseDeclarations(this.style.cssText)) {
|
||||
for (let cssProp of parseDeclarations(this.style.authoredText)) {
|
||||
cssProps[cssProp.name] = cssProp;
|
||||
}
|
||||
|
||||
@ -667,18 +683,67 @@ Rule.prototype = {
|
||||
|
||||
textProp.priority = cssProp.priority;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
this.elementStyle.markOverriddenAll();
|
||||
|
||||
if (modificationsPromise === this._applyingModifications) {
|
||||
this._applyingModifications = null;
|
||||
/**
|
||||
* A helper for applyProperties that applies properties in the "as
|
||||
* authored" case; that is, when the StyleRuleActor supports
|
||||
* setRuleText.
|
||||
*/
|
||||
_applyPropertiesAuthored: function(modifications) {
|
||||
return modifications.apply().then(() => {
|
||||
// The rewriting may have required some other property values to
|
||||
// change, e.g., to insert some needed terminators. Update the
|
||||
// relevant properties here.
|
||||
for (let index in modifications.changedDeclarations) {
|
||||
let newValue = modifications.changedDeclarations[index];
|
||||
this.textProps[index].noticeNewValue(newValue);
|
||||
}
|
||||
// Recompute and redisplay the computed properties.
|
||||
for (let prop of this.textProps) {
|
||||
if (!prop.invisible && prop.enabled) {
|
||||
prop.updateComputed();
|
||||
prop.updateEditor();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
this.elementStyle._changed();
|
||||
}).then(null, promiseWarn);
|
||||
/**
|
||||
* Reapply all the properties in this rule, and update their
|
||||
* computed styles. Will re-mark overridden properties. Sets the
|
||||
* |_applyingModifications| property to a promise which will resolve
|
||||
* when the edit has completed.
|
||||
*
|
||||
* @param {Function} modifier a function that takes a RuleModificationList
|
||||
* (or RuleRewriter) as an argument and that modifies it
|
||||
* to apply the desired edit
|
||||
* @return {Promise} a promise which will resolve when the edit
|
||||
* is complete
|
||||
*/
|
||||
applyProperties: function(modifier) {
|
||||
// If there is already a pending modification, we have to wait
|
||||
// until it settles before applying the next modification.
|
||||
let resultPromise =
|
||||
promise.resolve(this._applyingModifications).then(() => {
|
||||
let modifications = this.style.startModifyingProperties();
|
||||
modifier(modifications);
|
||||
if (this.style.canSetRuleText) {
|
||||
return this._applyPropertiesAuthored(modifications);
|
||||
}
|
||||
return this._applyPropertiesNoAuthored(modifications);
|
||||
}).then(() => {
|
||||
this.elementStyle.markOverriddenAll();
|
||||
|
||||
this._applyingModifications = modificationsPromise;
|
||||
return modificationsPromise;
|
||||
if (resultPromise === this._applyingModifications) {
|
||||
this._applyingModifications = null;
|
||||
this.elementStyle._changed();
|
||||
}
|
||||
}).catch(promiseWarn);
|
||||
|
||||
this._applyingModifications = resultPromise;
|
||||
return resultPromise;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -693,10 +758,12 @@ Rule.prototype = {
|
||||
if (name === property.name) {
|
||||
return;
|
||||
}
|
||||
let modifications = this.style.startModifyingProperties();
|
||||
modifications.removeProperty(property.name);
|
||||
|
||||
property.name = name;
|
||||
this.applyProperties(modifications, name);
|
||||
let index = this.textProps.indexOf(property);
|
||||
this.applyProperties((modifications) => {
|
||||
modifications.renameProperty(index, property.name, name);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@ -716,7 +783,11 @@ Rule.prototype = {
|
||||
|
||||
property.value = value;
|
||||
property.priority = priority;
|
||||
this.applyProperties(null, property.name);
|
||||
|
||||
let index = this.textProps.indexOf(property);
|
||||
this.applyProperties((modifications) => {
|
||||
modifications.setProperty(index, property.name, value, priority);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@ -732,7 +803,8 @@ Rule.prototype = {
|
||||
*/
|
||||
previewPropertyValue: function(property, value, priority) {
|
||||
let modifications = this.style.startModifyingProperties();
|
||||
modifications.setProperty(property.name, value, priority);
|
||||
modifications.setProperty(this.textProps.indexOf(property),
|
||||
property.name, value, priority);
|
||||
modifications.apply().then(() => {
|
||||
// Ensure dispatching a ruleview-changed event
|
||||
// also for previews
|
||||
@ -748,12 +820,14 @@ Rule.prototype = {
|
||||
* @param {Boolean} value
|
||||
*/
|
||||
setPropertyEnabled: function(property, value) {
|
||||
property.enabled = !!value;
|
||||
let modifications = this.style.startModifyingProperties();
|
||||
if (!property.enabled) {
|
||||
modifications.removeProperty(property.name);
|
||||
if (property.enabled === !!value) {
|
||||
return;
|
||||
}
|
||||
this.applyProperties(modifications);
|
||||
property.enabled = !!value;
|
||||
let index = this.textProps.indexOf(property);
|
||||
this.applyProperties((modifications) => {
|
||||
modifications.setPropertyEnabled(index, property.name, property.enabled);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@ -764,30 +838,35 @@ Rule.prototype = {
|
||||
* The property to be removed
|
||||
*/
|
||||
removeProperty: function(property) {
|
||||
this.textProps = this.textProps.filter(prop => prop !== property);
|
||||
let modifications = this.style.startModifyingProperties();
|
||||
modifications.removeProperty(property.name);
|
||||
let index = this.textProps.indexOf(property);
|
||||
this.textProps.splice(index, 1);
|
||||
// Need to re-apply properties in case removing this TextProperty
|
||||
// exposes another one.
|
||||
this.applyProperties(modifications);
|
||||
this.applyProperties((modifications) => {
|
||||
modifications.removeProperty(index, property.name);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the list of TextProperties from the style. Needs
|
||||
* to parse the style's cssText.
|
||||
* to parse the style's authoredText.
|
||||
*/
|
||||
_getTextProperties: function() {
|
||||
let textProps = [];
|
||||
let store = this.elementStyle.store;
|
||||
let props = parseDeclarations(this.style.cssText);
|
||||
let props = parseDeclarations(this.style.authoredText, true);
|
||||
for (let prop of props) {
|
||||
let name = prop.name;
|
||||
if (this.inherited && !domUtils.isInheritedProperty(name)) {
|
||||
continue;
|
||||
}
|
||||
// In an inherited rule, we only show inherited properties.
|
||||
// However, we must keep all properties in order for rule
|
||||
// rewriting to work properly. So, compute the "invisible"
|
||||
// property here.
|
||||
let invisible = this.inherited && !domUtils.isInheritedProperty(name);
|
||||
let value = store.userProperties.getProperty(this.style, name,
|
||||
prop.value);
|
||||
let textProp = new TextProperty(this, name, value, prop.priority);
|
||||
let textProp = new TextProperty(this, name, value, prop.priority,
|
||||
!("commentOffsets" in prop),
|
||||
invisible);
|
||||
textProps.push(textProp);
|
||||
}
|
||||
|
||||
@ -864,7 +943,7 @@ Rule.prototype = {
|
||||
|
||||
/**
|
||||
* Update the current TextProperties that match a given property
|
||||
* from the cssText. Will choose one existing TextProperty to update
|
||||
* from the authoredText. Will choose one existing TextProperty to update
|
||||
* with the new property's value, and will disable all others.
|
||||
*
|
||||
* When choosing the best match to reuse, properties will be chosen
|
||||
@ -880,7 +959,7 @@ Rule.prototype = {
|
||||
*
|
||||
* @param {TextProperty} newProp
|
||||
* The current version of the property, as parsed from the
|
||||
* cssText in Rule._getTextProperties().
|
||||
* authoredText in Rule._getTextProperties().
|
||||
* @return {Boolean} true if a property was updated, false if no properties
|
||||
* were updated.
|
||||
*/
|
||||
@ -953,18 +1032,26 @@ Rule.prototype = {
|
||||
let index = this.textProps.indexOf(textProperty);
|
||||
|
||||
if (direction === Ci.nsIFocusManager.MOVEFOCUS_FORWARD) {
|
||||
if (index === this.textProps.length - 1) {
|
||||
for (++index; index < this.textProps.length; ++index) {
|
||||
if (!this.textProps[index].invisible) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (index === this.textProps.length) {
|
||||
textProperty.rule.editor.closeBrace.click();
|
||||
} else {
|
||||
let nextProp = this.textProps[index + 1];
|
||||
nextProp.editor.nameSpan.click();
|
||||
this.textProps[index].editor.nameSpan.click();
|
||||
}
|
||||
} else if (direction === Ci.nsIFocusManager.MOVEFOCUS_BACKWARD) {
|
||||
if (index === 0) {
|
||||
for (--index; index >= 0; --index) {
|
||||
if (!this.textProps[index].invisible) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (index < 0) {
|
||||
textProperty.editor.ruleEditor.selectorText.click();
|
||||
} else {
|
||||
let prevProp = this.textProps[index - 1];
|
||||
prevProp.editor.valueSpan.click();
|
||||
this.textProps[index].editor.valueSpan.click();
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -978,15 +1065,31 @@ Rule.prototype = {
|
||||
let terminator = osString === "WINNT" ? "\r\n" : "\n";
|
||||
|
||||
for (let textProp of this.textProps) {
|
||||
cssText += "\t" + textProp.stringifyProperty() + terminator;
|
||||
if (!textProp.invisible) {
|
||||
cssText += "\t" + textProp.stringifyProperty() + terminator;
|
||||
}
|
||||
}
|
||||
|
||||
return selectorText + " {" + terminator + cssText + "}";
|
||||
},
|
||||
|
||||
/**
|
||||
* See whether this rule has any non-invisible properties.
|
||||
* @return {Boolean} true if there is any visible property, or false
|
||||
* if all properties are invisible
|
||||
*/
|
||||
hasAnyVisibleProperties: function() {
|
||||
for (let prop of this.textProps) {
|
||||
if (!prop.invisible) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A single property in a rule's cssText.
|
||||
* A single property in a rule's authoredText.
|
||||
*
|
||||
* @param {Rule} rule
|
||||
* The rule this TextProperty came from.
|
||||
@ -996,13 +1099,22 @@ Rule.prototype = {
|
||||
* The property's value (not including priority).
|
||||
* @param {String} priority
|
||||
* The property's priority (either "important" or an empty string).
|
||||
* @param {Boolean} enabled
|
||||
* Whether the property is enabled.
|
||||
* @param {Boolean} invisible
|
||||
* Whether the property is invisible. An invisible property
|
||||
* does not show up in the UI; these are needed so that the
|
||||
* index of a property in Rule.textProps is the same as the index
|
||||
* coming from parseDeclarations.
|
||||
*/
|
||||
function TextProperty(rule, name, value, priority) {
|
||||
function TextProperty(rule, name, value, priority, enabled = true,
|
||||
invisible = false) {
|
||||
this.rule = rule;
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.priority = priority;
|
||||
this.enabled = true;
|
||||
this.enabled = !!enabled;
|
||||
this.invisible = invisible;
|
||||
this.updateComputed();
|
||||
}
|
||||
|
||||
@ -1087,6 +1199,17 @@ TextProperty.prototype = {
|
||||
this.updateEditor();
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the property's value has been updated externally, and
|
||||
* the property and editor should update.
|
||||
*/
|
||||
noticeNewValue: function(value) {
|
||||
if (value !== this.value) {
|
||||
this.value = value;
|
||||
this.updateEditor();
|
||||
}
|
||||
},
|
||||
|
||||
setName: function(name) {
|
||||
let store = this.rule.elementStyle.store;
|
||||
|
||||
@ -1122,6 +1245,33 @@ TextProperty.prototype = {
|
||||
}
|
||||
|
||||
return declaration;
|
||||
},
|
||||
|
||||
/**
|
||||
* See whether this property's name is known.
|
||||
*
|
||||
* @return {Boolean} true if the property name is known, false otherwise.
|
||||
*/
|
||||
isKnownProperty: function() {
|
||||
try {
|
||||
// If the property name is invalid, the cssPropertyIsShorthand
|
||||
// will throw an exception. But if it is valid, no exception will
|
||||
// be thrown; so we just ignore the return value.
|
||||
domUtils.cssPropertyIsShorthand(this.name);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Validate this property. Does it make sense for this value to be assigned
|
||||
* to this property name? This does not apply the property value
|
||||
*
|
||||
* @return {Boolean} true if the property value is valid, false otherwise.
|
||||
*/
|
||||
isValid: function() {
|
||||
return domUtils.cssPropertyIsValid(this.name, this.value);
|
||||
}
|
||||
};
|
||||
|
||||
@ -1485,19 +1635,15 @@ CssRuleView.prototype = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a new rule to the current element.
|
||||
* A helper for _onAddRule that handles the case where the actor
|
||||
* does not support as-authored styles.
|
||||
*/
|
||||
_onAddRule: function() {
|
||||
_onAddNewRuleNonAuthored: function() {
|
||||
let elementStyle = this._elementStyle;
|
||||
let element = elementStyle.element;
|
||||
let rules = elementStyle.rules;
|
||||
let client = this.inspector.toolbox._target.client;
|
||||
let pseudoClasses = element.pseudoClassLocks;
|
||||
|
||||
if (!client.traits.addNewRule) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.pageStyle.addNewRule(element, pseudoClasses).then(options => {
|
||||
let newRule = new Rule(elementStyle, options);
|
||||
rules.push(newRule);
|
||||
@ -1523,6 +1669,44 @@ CssRuleView.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a new rule to the current element.
|
||||
*/
|
||||
_onAddRule: function() {
|
||||
let elementStyle = this._elementStyle;
|
||||
let element = elementStyle.element;
|
||||
let client = this.inspector.toolbox._target.client;
|
||||
let pseudoClasses = element.pseudoClassLocks;
|
||||
|
||||
if (!client.traits.addNewRule) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.pageStyle.supportsAuthoredStyles) {
|
||||
// We're talking to an old server.
|
||||
this._onAddNewRuleNonAuthored();
|
||||
return;
|
||||
}
|
||||
|
||||
// Adding a new rule with authored styles will cause the actor to
|
||||
// emit an event, which will in turn cause the rule view to be
|
||||
// updated. So, we wait for this update and for the rule creation
|
||||
// request to complete, and then focus the new rule's selector.
|
||||
let eventPromise = this.once("ruleview-refreshed");
|
||||
let newRulePromise = this.pageStyle.addNewRule(element, pseudoClasses);
|
||||
promise.all([eventPromise, newRulePromise]).then((values) => {
|
||||
let options = values[1];
|
||||
// Be sure the reference the correct |rules| here.
|
||||
for (let rule of this._elementStyle.rules) {
|
||||
if (options.rule === rule.domRule) {
|
||||
rule.editor.selectorText.click();
|
||||
elementStyle._changed();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Disables add rule button when needed
|
||||
*/
|
||||
@ -2146,7 +2330,7 @@ CssRuleView.prototype = {
|
||||
|
||||
// Highlight search matches in the rule properties
|
||||
for (let textProp of rule.textProps) {
|
||||
if (this._highlightProperty(textProp.editor)) {
|
||||
if (!textProp.invisible && this._highlightProperty(textProp.editor)) {
|
||||
isHighlighted = true;
|
||||
}
|
||||
}
|
||||
@ -2435,11 +2619,18 @@ function RuleEditor(aRuleView, aRule) {
|
||||
this._onNewProperty = this._onNewProperty.bind(this);
|
||||
this._newPropertyDestroy = this._newPropertyDestroy.bind(this);
|
||||
this._onSelectorDone = this._onSelectorDone.bind(this);
|
||||
this._locationChanged = this._locationChanged.bind(this);
|
||||
|
||||
this.rule.domRule.on("location-changed", this._locationChanged);
|
||||
|
||||
this._create();
|
||||
}
|
||||
|
||||
RuleEditor.prototype = {
|
||||
destroy: function() {
|
||||
this.rule.domRule.off("location-changed");
|
||||
},
|
||||
|
||||
get isSelectorEditable() {
|
||||
let toolbox = this.ruleView.inspector.toolbox;
|
||||
let trait = this.isEditable &&
|
||||
@ -2554,17 +2745,26 @@ RuleEditor.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Event handler called when a property changes on the
|
||||
* StyleRuleActor.
|
||||
*/
|
||||
_locationChanged: function(line, column) {
|
||||
this.updateSourceLink();
|
||||
},
|
||||
|
||||
updateSourceLink: function() {
|
||||
let sourceLabel = this.element.querySelector(".ruleview-rule-source-label");
|
||||
let title = this.rule.title;
|
||||
let sourceHref = (this.rule.sheet && this.rule.sheet.href) ?
|
||||
this.rule.sheet.href : this.rule.title;
|
||||
this.rule.sheet.href : title;
|
||||
let sourceLine = this.rule.ruleLine > 0 ? ":" + this.rule.ruleLine : "";
|
||||
|
||||
sourceLabel.setAttribute("tooltiptext", sourceHref + sourceLine);
|
||||
|
||||
if (this.rule.isSystem) {
|
||||
let uaLabel = _strings.GetStringFromName("rule.userAgentStyles");
|
||||
sourceLabel.setAttribute("value", uaLabel + " " + this.rule.title);
|
||||
sourceLabel.setAttribute("value", uaLabel + " " + title);
|
||||
|
||||
// Special case about:PreferenceStyleSheet, as it is generated on the
|
||||
// fly and the URI is not registered with the about: handler.
|
||||
@ -2575,7 +2775,7 @@ RuleEditor.prototype = {
|
||||
sourceLabel.removeAttribute("tooltiptext");
|
||||
}
|
||||
} else {
|
||||
sourceLabel.setAttribute("value", this.rule.title);
|
||||
sourceLabel.setAttribute("value", title);
|
||||
if (this.rule.ruleLine === -1 && this.rule.domRule.parentStyleSheet) {
|
||||
sourceLabel.parentNode.setAttribute("unselectable", "true");
|
||||
}
|
||||
@ -2654,7 +2854,7 @@ RuleEditor.prototype = {
|
||||
}
|
||||
|
||||
for (let prop of this.rule.textProps) {
|
||||
if (!prop.editor) {
|
||||
if (!prop.editor && !prop.invisible) {
|
||||
let editor = new TextPropertyEditor(this, prop);
|
||||
this.propertyList.appendChild(editor.element);
|
||||
}
|
||||
@ -2862,13 +3062,13 @@ RuleEditor.prototype = {
|
||||
rules.splice(rules.indexOf(this.rule), 1);
|
||||
rules.push(newRule);
|
||||
elementStyle._changed();
|
||||
elementStyle.markOverriddenAll();
|
||||
|
||||
editor.element.setAttribute("unmatched", !isMatching);
|
||||
this.element.parentNode.replaceChild(editor.element, this.element);
|
||||
|
||||
// Remove highlight for modified selector
|
||||
if (ruleView.highlightedSelector &&
|
||||
ruleView.highlightedSelector === this.rule.selectorText) {
|
||||
if (ruleView.highlightedSelector) {
|
||||
ruleView.toggleSelectorHighlighter(ruleView.lastSelectorIcon,
|
||||
ruleView.highlightedSelector);
|
||||
}
|
||||
@ -3172,7 +3372,8 @@ TextPropertyEditor.prototype = {
|
||||
!this.isValid() ||
|
||||
!this.prop.overridden;
|
||||
|
||||
if (this.prop.overridden || !this.prop.enabled) {
|
||||
if (this.prop.overridden || !this.prop.enabled ||
|
||||
!this.prop.isKnownProperty()) {
|
||||
this.element.classList.add("ruleview-overridden");
|
||||
} else {
|
||||
this.element.classList.remove("ruleview-overridden");
|
||||
@ -3502,8 +3703,10 @@ TextPropertyEditor.prototype = {
|
||||
this.committed.value === val.value &&
|
||||
this.committed.priority === val.priority;
|
||||
// If the value is not empty and unchanged, revert the property back to
|
||||
// its original enabled or disabled state
|
||||
// its original value and enabled or disabled state
|
||||
if (value.trim() && isValueUnchanged) {
|
||||
this.ruleEditor.rule.previewPropertyValue(this.prop, val.value,
|
||||
val.priority);
|
||||
this.rule.setPropertyEnabled(this.prop, this.prop.enabled);
|
||||
return;
|
||||
}
|
||||
@ -3555,7 +3758,7 @@ TextPropertyEditor.prototype = {
|
||||
* value of this property before editing.
|
||||
*/
|
||||
_onSwatchRevert: function() {
|
||||
this.rule.setPropertyEnabled(this.prop, this.prop.enabled);
|
||||
this._previewValue(this.prop.value);
|
||||
this.update();
|
||||
},
|
||||
|
||||
@ -3631,7 +3834,7 @@ TextPropertyEditor.prototype = {
|
||||
* @return {Boolean} true if the property value is valid, false otherwise.
|
||||
*/
|
||||
isValid: function() {
|
||||
return domUtils.cssPropertyIsValid(this.prop.name, this.prop.value);
|
||||
return this.prop.isValid();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -48,6 +48,7 @@ function RuleViewTool(inspector, window) {
|
||||
this.inspector.selection.on("pseudoclass", this.refresh);
|
||||
this.inspector.target.on("navigate", this.clearUserProperties);
|
||||
this.inspector.sidebar.on("ruleview-selected", this.onPanelSelected);
|
||||
this.inspector.pageStyle.on("stylesheet-updated", this.refresh);
|
||||
|
||||
this.onSelected();
|
||||
}
|
||||
@ -152,6 +153,9 @@ RuleViewTool.prototype = {
|
||||
this.inspector.selection.off("new-node-front", this.onSelected);
|
||||
this.inspector.target.off("navigate", this.clearUserProperties);
|
||||
this.inspector.sidebar.off("ruleview-selected", this.onPanelSelected);
|
||||
if (this.inspector.pageStyle) {
|
||||
this.inspector.pageStyle.off("stylesheet-updated", this.refresh);
|
||||
}
|
||||
|
||||
this.view.off("ruleview-linked-clicked", this.onLinkClicked);
|
||||
this.view.off("ruleview-changed", this.onPropertyChanged);
|
||||
@ -179,6 +183,7 @@ function ComputedViewTool(inspector, window) {
|
||||
this.inspector.on("layout-change", this.refresh);
|
||||
this.inspector.selection.on("pseudoclass", this.refresh);
|
||||
this.inspector.sidebar.on("computedview-selected", this.onPanelSelected);
|
||||
this.inspector.pageStyle.on("stylesheet-updated", this.refresh);
|
||||
|
||||
this.view.selectElement(null);
|
||||
|
||||
@ -244,6 +249,9 @@ ComputedViewTool.prototype = {
|
||||
this.inspector.selection.off("pseudoclass", this.refresh);
|
||||
this.inspector.selection.off("new-node-front", this.onSelected);
|
||||
this.inspector.sidebar.off("computedview-selected", this.onPanelSelected);
|
||||
if (this.inspector.pageStyle) {
|
||||
this.inspector.pageStyle.off("stylesheet-updated", this.refresh);
|
||||
}
|
||||
|
||||
this.view.destroy();
|
||||
|
||||
|
@ -19,6 +19,7 @@ support-files =
|
||||
doc_matched_selectors.html
|
||||
doc_media_queries.html
|
||||
doc_pseudoelement.html
|
||||
doc_ruleLineNumbers.html
|
||||
doc_sourcemaps.css
|
||||
doc_sourcemaps.css.map
|
||||
doc_sourcemaps.html
|
||||
@ -60,6 +61,7 @@ support-files =
|
||||
[browser_ruleview_add-rule_03.js]
|
||||
[browser_ruleview_add-rule_04.js]
|
||||
[browser_ruleview_add-rule_pseudo_class.js]
|
||||
[browser_ruleview_authored.js]
|
||||
[browser_ruleview_colorpicker-and-image-tooltip_01.js]
|
||||
[browser_ruleview_colorpicker-and-image-tooltip_02.js]
|
||||
[browser_ruleview_colorpicker-appears-on-swatch-click.js]
|
||||
@ -118,18 +120,21 @@ skip-if = e10s # Bug 1039528: "inspect element" contextual-menu doesn't work wit
|
||||
[browser_ruleview_filtereditor-commit-on-ENTER.js]
|
||||
[browser_ruleview_filtereditor-revert-on-ESC.js]
|
||||
skip-if = (os == "win" && debug) || e10s # bug 963492: win. bug 1040653: e10s.
|
||||
[browser_ruleview_guessIndentation.js]
|
||||
[browser_ruleview_inherited-properties_01.js]
|
||||
[browser_ruleview_inherited-properties_02.js]
|
||||
[browser_ruleview_inherited-properties_03.js]
|
||||
[browser_ruleview_keybindings.js]
|
||||
[browser_ruleview_keyframes-rule_01.js]
|
||||
[browser_ruleview_keyframes-rule_02.js]
|
||||
[browser_ruleview_lineNumbers.js]
|
||||
[browser_ruleview_livepreview.js]
|
||||
[browser_ruleview_mark_overridden_01.js]
|
||||
[browser_ruleview_mark_overridden_02.js]
|
||||
[browser_ruleview_mark_overridden_03.js]
|
||||
[browser_ruleview_mark_overridden_04.js]
|
||||
[browser_ruleview_mark_overridden_05.js]
|
||||
[browser_ruleview_mark_overridden_06.js]
|
||||
[browser_ruleview_mark_overridden_07.js]
|
||||
[browser_ruleview_mathml-element.js]
|
||||
[browser_ruleview_media-queries.js]
|
||||
|
@ -34,7 +34,13 @@ function checkColorCycling(container, inspector) {
|
||||
let valueNode = container.querySelector(".computedview-color");
|
||||
let win = inspector.sidebar.getWindowForTab("computedview");
|
||||
|
||||
// Hex (default)
|
||||
// "Authored" (default; currently the computed value)
|
||||
is(valueNode.textContent, "rgb(255, 0, 0)",
|
||||
"Color displayed as an RGB value.");
|
||||
|
||||
// Hex
|
||||
EventUtils.synthesizeMouseAtCenter(swatch,
|
||||
{type: "mousedown", shiftKey: true}, win);
|
||||
is(valueNode.textContent, "#F00", "Color displayed as a hex value.");
|
||||
|
||||
// HSL
|
||||
@ -55,15 +61,9 @@ function checkColorCycling(container, inspector) {
|
||||
is(valueNode.textContent, "red",
|
||||
"Color displayed as a color name.");
|
||||
|
||||
// "Authored" (currently the computed value)
|
||||
// Back to "Authored"
|
||||
EventUtils.synthesizeMouseAtCenter(swatch,
|
||||
{type: "mousedown", shiftKey: true}, win);
|
||||
is(valueNode.textContent, "rgb(255, 0, 0)",
|
||||
"Color displayed as an RGB value.");
|
||||
|
||||
// Back to hex
|
||||
EventUtils.synthesizeMouseAtCenter(swatch,
|
||||
{type: "mousedown", shiftKey: true}, win);
|
||||
is(valueNode.textContent, "#F00",
|
||||
"Color displayed as hex again.");
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ const TEST_DATA = [
|
||||
ok("property" in nodeInfo.value);
|
||||
ok("value" in nodeInfo.value);
|
||||
is(nodeInfo.value.property, "color");
|
||||
is(nodeInfo.value.value, "#F00");
|
||||
is(nodeInfo.value.value, "rgb(255, 0, 0)");
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -88,7 +88,7 @@ const TEST_DATA = [
|
||||
ok("property" in nodeInfo.value);
|
||||
ok("value" in nodeInfo.value);
|
||||
is(nodeInfo.value.property, "color");
|
||||
is(nodeInfo.value.value, "#F00");
|
||||
is(nodeInfo.value.value, "rgb(255, 0, 0)");
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -149,7 +149,7 @@ const TEST_DATA = [
|
||||
assertNodeInfo: function(nodeInfo) {
|
||||
is(nodeInfo.type, VIEW_NODE_VALUE_TYPE);
|
||||
is(nodeInfo.value.property, "color");
|
||||
is(nodeInfo.value.value, "#F00");
|
||||
is(nodeInfo.value.value, "red");
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -25,5 +25,5 @@ add_task(function*() {
|
||||
fontSize = getComputedViewPropertyValue(view, "font-size");
|
||||
is(fontSize, "15px", "The computed view shows the updated font-size");
|
||||
let color = getComputedViewPropertyValue(view, "color");
|
||||
is(color, "#F00", "The computed view also shows the color now");
|
||||
is(color, "rgb(255, 0, 0)", "The computed view also shows the color now");
|
||||
});
|
||||
|
@ -81,7 +81,7 @@ function checkSelectAll(view) {
|
||||
info("Checking that _onSelectAll() then copy returns the correct " +
|
||||
"clipboard value");
|
||||
view._contextmenu._onSelectAll();
|
||||
let expectedPattern = "color: #FF0;[\\r\\n]+" +
|
||||
let expectedPattern = "color: rgb\\(255, 255, 0\\);[\\r\\n]+" +
|
||||
"font-family: helvetica,sans-serif;[\\r\\n]+" +
|
||||
"font-size: 16px;[\\r\\n]+" +
|
||||
"font-variant-caps: small-caps;[\\r\\n]*";
|
||||
|
@ -69,6 +69,6 @@ function* testCreateNew(view) {
|
||||
yield onModifications;
|
||||
|
||||
is(textProp.value, "#XYZ", "Text prop should have been changed.");
|
||||
is(textProp.overridden, false, "Property should not be overridden");
|
||||
is(textProp.overridden, true, "Property should be overridden");
|
||||
is(textProp.editor.isValid(), false, "#XYZ should not be a valid entry");
|
||||
}
|
||||
|
127
devtools/client/styleinspector/test/browser_ruleview_authored.js
Normal file
127
devtools/client/styleinspector/test/browser_ruleview_authored.js
Normal file
@ -0,0 +1,127 @@
|
||||
/* 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 for as-authored styles.
|
||||
|
||||
add_task(function*() {
|
||||
yield basicTest();
|
||||
yield overrideTest();
|
||||
yield colorEditingTest();
|
||||
});
|
||||
|
||||
function* createTestContent(style) {
|
||||
let content = `<style type="text/css">
|
||||
${style}
|
||||
</style>
|
||||
<div id="testid" class="testclass">Styled Node</div>`;
|
||||
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(content));
|
||||
|
||||
let {inspector, view} = yield openRuleView();
|
||||
yield selectNode("#testid", inspector);
|
||||
return view;
|
||||
}
|
||||
|
||||
function* basicTest() {
|
||||
let view = yield createTestContent("#testid {" +
|
||||
// Invalid property.
|
||||
" something: random;" +
|
||||
// Invalid value.
|
||||
" color: orang;" +
|
||||
// Override.
|
||||
" background-color: blue;" +
|
||||
" background-color: #f0c;" +
|
||||
"} ");
|
||||
|
||||
let elementStyle = view._elementStyle;
|
||||
|
||||
let expected = [
|
||||
{name: "something", overridden: true},
|
||||
{name: "color", overridden: true},
|
||||
{name: "background-color", overridden: true},
|
||||
{name: "background-color", overridden: false}
|
||||
];
|
||||
|
||||
let rule = elementStyle.rules[1];
|
||||
|
||||
for (let i = 0; i < expected.length; ++i) {
|
||||
let prop = rule.textProps[i];
|
||||
is(prop.name, expected[i].name, "test name for prop " + i);
|
||||
is(prop.overridden, expected[i].overridden,
|
||||
"test overridden for prop " + i);
|
||||
}
|
||||
}
|
||||
|
||||
function* overrideTest() {
|
||||
let gradientText = "(45deg, rgba(255,255,255,0.2) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.2) 50%, rgba(255,255,255,0.2) 75%, transparent 75%, transparent);";
|
||||
|
||||
let view =
|
||||
yield createTestContent("#testid {" +
|
||||
" background-image: -moz-linear-gradient" +
|
||||
gradientText +
|
||||
" background-image: -webkit-linear-gradient" +
|
||||
gradientText +
|
||||
" background-image: linear-gradient" +
|
||||
gradientText +
|
||||
"} ");
|
||||
|
||||
let elementStyle = view._elementStyle;
|
||||
let rule = elementStyle.rules[1];
|
||||
|
||||
// Initially the last property should be active.
|
||||
for (let i = 0; i < 3; ++i) {
|
||||
let prop = rule.textProps[i];
|
||||
is(prop.name, "background-image", "check the property name");
|
||||
is(prop.overridden, i !== 2, "check overridden for " + i);
|
||||
}
|
||||
|
||||
rule.textProps[2].setEnabled(false);
|
||||
yield rule._applyingModifications;
|
||||
|
||||
// Now the first property should be active.
|
||||
for (let i = 0; i < 3; ++i) {
|
||||
let prop = rule.textProps[i];
|
||||
is(prop.overridden || !prop.enabled, i !== 0,
|
||||
"post-change check overridden for " + i);
|
||||
}
|
||||
}
|
||||
|
||||
function* colorEditingTest() {
|
||||
let colors = [
|
||||
{name: "hex", text: "#f0c", result: "#0F0"},
|
||||
{name: "rgb", text: "rgb(0,128,250)", result: "rgb(0, 255, 0)"}
|
||||
];
|
||||
|
||||
Services.prefs.setCharPref("devtools.defaultColorUnit", "authored");
|
||||
|
||||
for (let color of colors) {
|
||||
let view = yield createTestContent("#testid {" +
|
||||
" color: " + color.text + ";" +
|
||||
"} ");
|
||||
|
||||
let cPicker = view.tooltips.colorPicker;
|
||||
let swatch = getRuleViewProperty(view, "#testid", "color").valueSpan
|
||||
.querySelector(".ruleview-colorswatch");
|
||||
let onShown = cPicker.tooltip.once("shown");
|
||||
swatch.click();
|
||||
yield onShown;
|
||||
|
||||
let testNode = yield getNode("#testid");
|
||||
|
||||
yield simulateColorPickerChange(view, cPicker, [0, 255, 0, 1], {
|
||||
element: testNode,
|
||||
name: "color",
|
||||
value: "rgb(0, 255, 0)"
|
||||
});
|
||||
|
||||
let spectrum = yield cPicker.spectrum;
|
||||
let onHidden = cPicker.tooltip.once("hidden");
|
||||
EventUtils.sendKey("RETURN", spectrum.element.ownerDocument.defaultView);
|
||||
yield onHidden;
|
||||
|
||||
is(getRuleViewPropertyValue(view, "#testid", "color"), color.result,
|
||||
"changing the color preserved the unit for " + color.name);
|
||||
}
|
||||
}
|
@ -21,7 +21,7 @@ add_task(function*() {
|
||||
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||
let {view} = yield openRuleView();
|
||||
let value = getRuleViewProperty(view, "body", "background").valueSpan;
|
||||
let swatch = value.querySelectorAll(".ruleview-colorswatch")[1];
|
||||
let swatch = value.querySelectorAll(".ruleview-colorswatch")[0];
|
||||
let url = value.querySelector(".theme-link");
|
||||
yield testImageTooltipAfterColorChange(swatch, url, view);
|
||||
});
|
||||
|
@ -61,7 +61,7 @@ function* testColorChangeIsntRevertedWhenOtherTooltipIsShown(ruleView) {
|
||||
|
||||
info("Image tooltip is shown, verify that the swatch is still correct");
|
||||
swatch = value.querySelector(".ruleview-colorswatch");
|
||||
is(swatch.style.backgroundColor, "rgb(0, 0, 0)",
|
||||
is(swatch.style.backgroundColor, "black",
|
||||
"The swatch's color is correct");
|
||||
is(swatch.nextSibling.textContent, "#000", "The color name is correct");
|
||||
is(swatch.nextSibling.textContent, "black", "The color name is correct");
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ function testColorParsing(view) {
|
||||
ok(colorEls, "The color elements were found");
|
||||
is(colorEls.length, 3, "There are 3 color values");
|
||||
|
||||
let colors = ["#F06", "#333", "#000"];
|
||||
let colors = ["#f06", "#333", "#000"];
|
||||
for (let i = 0; i < colors.length; i++) {
|
||||
is(colorEls[i].textContent, colors[i], "The right color value was found");
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ function checkColorCycling(container, inspector) {
|
||||
let valueNode = container.querySelector(".ruleview-color");
|
||||
let win = inspector.sidebar.getWindowForTab("ruleview");
|
||||
|
||||
// Hex (default)
|
||||
// Hex
|
||||
is(valueNode.textContent, "#F00", "Color displayed as a hex value.");
|
||||
|
||||
// HSL
|
||||
@ -48,15 +48,16 @@ function checkColorCycling(container, inspector) {
|
||||
is(valueNode.textContent, "red",
|
||||
"Color displayed as a color name.");
|
||||
|
||||
// "Authored" (currently the computed value)
|
||||
EventUtils.synthesizeMouseAtCenter(swatch,
|
||||
{type: "mousedown", shiftKey: true}, win);
|
||||
is(valueNode.textContent, "rgb(255, 0, 0)",
|
||||
"Color displayed as an RGB value.");
|
||||
|
||||
// Back to hex
|
||||
// "Authored"
|
||||
EventUtils.synthesizeMouseAtCenter(swatch,
|
||||
{type: "mousedown", shiftKey: true}, win);
|
||||
is(valueNode.textContent, "#F00",
|
||||
"Color displayed as hex again.");
|
||||
"Color displayed as an authored value.");
|
||||
|
||||
// One more click skips hex, because it is the same as authored, and
|
||||
// instead goes back to HSL.
|
||||
EventUtils.synthesizeMouseAtCenter(swatch,
|
||||
{type: "mousedown", shiftKey: true}, win);
|
||||
is(valueNode.textContent, "hsl(0, 100%, 50%)",
|
||||
"Color displayed as an HSL value again.");
|
||||
}
|
||||
|
@ -48,9 +48,10 @@ function* testEditPropertyAndRemove(inspector, view) {
|
||||
"Focus should have moved to the next property name");
|
||||
|
||||
info("Focus the property value and remove the property");
|
||||
let onChanged = view.once("ruleview-changed");
|
||||
yield sendKeysAndWaitForFocus(view, ruleEditor.element,
|
||||
["VK_TAB", "VK_DELETE", "VK_RETURN"]);
|
||||
yield ruleEditor.rule._applyingModifications;
|
||||
yield onChanged;
|
||||
|
||||
newValue = yield executeInContent("Test:GetRulePropertyValue", {
|
||||
styleSheetIndex: 0,
|
||||
|
@ -59,7 +59,7 @@ function* testEditProperty(ruleEditor, name, value, isValid) {
|
||||
info("Entering a new property name, including : to commit and " +
|
||||
"focus the value");
|
||||
let onValueFocus = once(ruleEditor.element, "focus", true);
|
||||
let onModifications = ruleEditor.rule._applyingModifications;
|
||||
let onModifications = ruleEditor.ruleView.once("ruleview-changed");
|
||||
EventUtils.sendString(name + ":", doc.defaultView);
|
||||
yield onValueFocus;
|
||||
yield onModifications;
|
||||
@ -71,10 +71,9 @@ function* testEditProperty(ruleEditor, name, value, isValid) {
|
||||
|
||||
info("Entering a new value, including ; to commit and blur the value");
|
||||
let onBlur = once(input, "blur");
|
||||
onModifications = ruleEditor.rule._applyingModifications;
|
||||
EventUtils.sendString(value + ";", doc.defaultView);
|
||||
yield onBlur;
|
||||
yield onModifications;
|
||||
yield ruleEditor.rule._applyingModifications;
|
||||
|
||||
is(propEditor.isValid(), isValid,
|
||||
value + " is " + isValid ? "valid" : "invalid");
|
||||
|
@ -33,8 +33,10 @@ add_task(function*() {
|
||||
yield focusEditableField(view, propEditor.valueSpan);
|
||||
|
||||
info("Deleting all the text out of a value field");
|
||||
let waitForUpdates = view.once("ruleview-changed");
|
||||
yield sendCharsAndWaitForFocus(view, ruleEditor.element,
|
||||
["VK_DELETE", "VK_RETURN"]);
|
||||
yield waitForUpdates;
|
||||
|
||||
info("Pressing enter a couple times to cycle through editors");
|
||||
yield sendCharsAndWaitForFocus(view, ruleEditor.element, ["VK_RETURN"]);
|
||||
@ -48,9 +50,7 @@ add_task(function*() {
|
||||
function* sendCharsAndWaitForFocus(view, element, chars) {
|
||||
let onFocus = once(element, "focus", true);
|
||||
for (let ch of chars) {
|
||||
let onRuleViewChanged = view.once("ruleview-changed");
|
||||
EventUtils.sendChar(ch, element.ownerDocument.defaultView);
|
||||
yield onRuleViewChanged;
|
||||
}
|
||||
yield onFocus;
|
||||
}
|
||||
|
@ -10,7 +10,7 @@
|
||||
const TEST_URI = `
|
||||
<style type='text/css'>
|
||||
#testid {
|
||||
background-color: red;
|
||||
background-color: #f00;
|
||||
}
|
||||
</style>
|
||||
<div id='testid'>Styled Node</div>
|
||||
|
@ -98,7 +98,7 @@ function* testAddProperty(view) {
|
||||
info("Entering a value and bluring the field to expect a rule change");
|
||||
editor.input.value = "center";
|
||||
let onBlur = once(editor.input, "blur");
|
||||
onRuleViewChanged = view.once("ruleview-changed");
|
||||
onRuleViewChanged = waitForNEvents(view, "ruleview-changed", 2);
|
||||
editor.input.blur();
|
||||
yield onBlur;
|
||||
yield onRuleViewChanged;
|
||||
|
@ -0,0 +1,76 @@
|
||||
/* 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 we can guess indentation from a style sheet, not just a
|
||||
// rule.
|
||||
|
||||
// Needed for openStyleEditor.
|
||||
Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/styleeditor/test/head.js", this);
|
||||
|
||||
// Use a weird indentation depth to avoid accidental success.
|
||||
const TEST_URI = `
|
||||
<style type='text/css'>
|
||||
div {
|
||||
background-color: blue;
|
||||
}
|
||||
|
||||
* {
|
||||
}
|
||||
</style>
|
||||
<div id='testid' class='testclass'>Styled Node</div>
|
||||
`;
|
||||
|
||||
const expectedText = `
|
||||
div {
|
||||
background-color: blue;
|
||||
}
|
||||
|
||||
* {
|
||||
color: chartreuse;
|
||||
}
|
||||
`;
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||
let {inspector, view} = yield openRuleView();
|
||||
yield selectNode("#testid", inspector);
|
||||
yield testIndentation(inspector, view);
|
||||
});
|
||||
|
||||
function* testIndentation(inspector, view) {
|
||||
let ruleEditor = getRuleViewRuleEditor(view, 2);
|
||||
|
||||
info("Focusing a new property name in the rule-view");
|
||||
let editor = yield focusEditableField(view, ruleEditor.closeBrace);
|
||||
|
||||
let input = editor.input;
|
||||
|
||||
info("Entering color in the property name editor");
|
||||
input.value = "color";
|
||||
|
||||
info("Pressing return to commit and focus the new value field");
|
||||
let onValueFocus = once(ruleEditor.element, "focus", true);
|
||||
let onModifications = ruleEditor.rule._applyingModifications;
|
||||
EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
|
||||
yield onValueFocus;
|
||||
yield onModifications;
|
||||
|
||||
// Getting the new value editor after focus
|
||||
editor = inplaceEditor(view.styleDocument.activeElement);
|
||||
info("Entering a value and bluring the field to expect a rule change");
|
||||
editor.input.value = "chartreuse";
|
||||
let onBlur = once(editor.input, "blur");
|
||||
onModifications = ruleEditor.rule._applyingModifications;
|
||||
editor.input.blur();
|
||||
yield onBlur;
|
||||
yield onModifications;
|
||||
|
||||
let { ui } = yield openStyleEditor();
|
||||
|
||||
let styleEditor = yield ui.editors[0].getSourceEditor();
|
||||
let text = styleEditor.sourceEditor.getText();
|
||||
is(text, expectedText, "style inspector changes are synced");
|
||||
}
|
@ -38,8 +38,12 @@ function* simpleInherit(inspector, view) {
|
||||
is(inheritRule.selectorText, "#test2",
|
||||
"Inherited rule should be the one that includes inheritable properties.");
|
||||
ok(!!inheritRule.inherited, "Rule should consider itself inherited.");
|
||||
is(inheritRule.textProps.length, 1,
|
||||
"Should only display one inherited style");
|
||||
let inheritProp = inheritRule.textProps[0];
|
||||
is(inheritRule.textProps.length, 2,
|
||||
"Rule should have two styles");
|
||||
let bgcProp = inheritRule.textProps[0];
|
||||
is(bgcProp.name, "background-color",
|
||||
"background-color property should exist");
|
||||
ok(bgcProp.invisible, "background-color property should be invisible");
|
||||
let inheritProp = inheritRule.textProps[1];
|
||||
is(inheritProp.name, "color", "color should have been inherited.");
|
||||
}
|
||||
|
@ -0,0 +1,52 @@
|
||||
/* 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 that editing a rule will update the line numbers of subsequent
|
||||
// rules in the rule view.
|
||||
|
||||
const TESTCASE_URI = TEST_URL_ROOT + "doc_ruleLineNumbers.html";
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab(TESTCASE_URI);
|
||||
let { inspector, view } = yield openRuleView();
|
||||
yield selectNode("#testid", inspector);
|
||||
let elementRuleEditor = getRuleViewRuleEditor(view, 1);
|
||||
|
||||
let bodyRuleEditor = getRuleViewRuleEditor(view, 3);
|
||||
let value = getRuleViewLinkTextByIndex(view, 2);
|
||||
// Note that this is relative to the <style>.
|
||||
is(value.slice(-2), ":6", "initial rule line number is 6");
|
||||
|
||||
info("Focusing a new property name in the rule-view");
|
||||
let editor = yield focusEditableField(view, elementRuleEditor.closeBrace);
|
||||
|
||||
is(inplaceEditor(elementRuleEditor.newPropSpan), editor,
|
||||
"The new property editor got focused");
|
||||
let input = editor.input;
|
||||
|
||||
info("Entering font-size in the property name editor");
|
||||
input.value = "font-size";
|
||||
|
||||
info("Pressing return to commit and focus the new value field");
|
||||
let onLocationChanged = once(bodyRuleEditor.rule.domRule, "location-changed");
|
||||
let onValueFocus = once(elementRuleEditor.element, "focus", true);
|
||||
EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
|
||||
yield onValueFocus;
|
||||
yield elementRuleEditor.rule._applyingModifications;
|
||||
|
||||
// Getting the new value editor after focus
|
||||
editor = inplaceEditor(view.styleDocument.activeElement);
|
||||
info("Entering a value and bluring the field to expect a rule change");
|
||||
editor.input.value = "23px";
|
||||
editor.input.blur();
|
||||
yield elementRuleEditor.rule._applyingModifications;
|
||||
|
||||
yield onLocationChanged;
|
||||
|
||||
let newBodyTitle = getRuleViewLinkTextByIndex(view, 2);
|
||||
// Note that this is relative to the <style>.
|
||||
is(newBodyTitle.slice(-2), ":7", "updated rule line number is 7");
|
||||
});
|
@ -27,6 +27,7 @@ function* testMarkOverridden(inspector, view) {
|
||||
let ruleEditor = getRuleViewRuleEditor(view, 1);
|
||||
|
||||
yield createNewRuleViewProperty(ruleEditor, "background-color: red;");
|
||||
yield ruleEditor.rule._applyingModifications;
|
||||
|
||||
let firstProp = ruleEditor.rule.textProps[0];
|
||||
let secondProp = ruleEditor.rule.textProps[1];
|
||||
|
@ -0,0 +1,60 @@
|
||||
/* 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 the rule view marks overridden rules correctly after
|
||||
// editing the selector.
|
||||
|
||||
const TEST_URI = `
|
||||
<style type='text/css'>
|
||||
div {
|
||||
background-color: blue;
|
||||
background-color: chartreuse;
|
||||
}
|
||||
</style>
|
||||
<div id='testid' class='testclass'>Styled Node</div>
|
||||
`;
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||
let {inspector, view} = yield openRuleView();
|
||||
yield selectNode("#testid", inspector);
|
||||
yield testMarkOverridden(inspector, view);
|
||||
});
|
||||
|
||||
function* testMarkOverridden(inspector, view) {
|
||||
let elementStyle = view._elementStyle;
|
||||
let rule = elementStyle.rules[1];
|
||||
checkProperties(rule);
|
||||
|
||||
let ruleEditor = getRuleViewRuleEditor(view, 1);
|
||||
info("Focusing an existing selector name in the rule-view");
|
||||
let editor = yield focusEditableField(view, ruleEditor.selectorText);
|
||||
|
||||
info("Entering a new selector name and committing");
|
||||
editor.input.value = "div[class]";
|
||||
|
||||
let onRuleViewChanged = once(view, "ruleview-changed");
|
||||
info("Entering the commit key");
|
||||
EventUtils.synthesizeKey("VK_RETURN", {});
|
||||
yield onRuleViewChanged;
|
||||
|
||||
view.searchField.focus();
|
||||
checkProperties(rule);
|
||||
}
|
||||
|
||||
// A helper to perform a repeated set of checks.
|
||||
function checkProperties(rule) {
|
||||
let prop = rule.textProps[0];
|
||||
is(prop.name, "background-color",
|
||||
"First property should be background-color");
|
||||
is(prop.value, "blue", "First property value should be blue");
|
||||
ok(prop.overridden, "prop should be overridden.");
|
||||
prop = rule.textProps[1];
|
||||
is(prop.name, "background-color",
|
||||
"Second property should be background-color");
|
||||
is(prop.value, "chartreuse", "First property value should be chartreuse");
|
||||
ok(!prop.overridden, "prop should not be overridden.");
|
||||
}
|
@ -51,7 +51,8 @@ function* testMarkOverridden(inspector, view) {
|
||||
[{name: "margin-right", value: "23px", overridden: false},
|
||||
{name: "margin-left", value: "1px", overridden: false}],
|
||||
[{name: "font-size", value: "12px", overridden: false}],
|
||||
[{name: "font-size", value: "79px", overridden: true}]
|
||||
[{name: "margin-right", value: "1px", overridden: true},
|
||||
{name: "font-size", value: "79px", overridden: true}]
|
||||
];
|
||||
|
||||
for (let i = 1; i < RESULTS.length; ++i) {
|
||||
|
@ -24,7 +24,6 @@ add_task(function*() {
|
||||
is(elementStyle.rules.length, 3, "Should have 3 rules.");
|
||||
is(elementStyle.rules[0].title, inline, "check rule 0 title");
|
||||
is(elementStyle.rules[1].title, inline +
|
||||
":15 @media screen and (min-width: 1px)", "check rule 1 title");
|
||||
is(elementStyle.rules[2].title, inline + ":8", "check rule 2 title");
|
||||
":9 @media screen and (min-width: 1px)", "check rule 1 title");
|
||||
is(elementStyle.rules[2].title, inline + ":2", "check rule 2 title");
|
||||
});
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that the rule-view behaves correctly when entering mutliple and/or
|
||||
// Test that the rule-view behaves correctly when entering multiple and/or
|
||||
// unfinished properties/values in inplace-editors
|
||||
|
||||
const TEST_URI = "<div>Test Element</div>";
|
||||
@ -16,24 +16,12 @@ add_task(function*() {
|
||||
yield testCreateNewMultiUnfinished(inspector, view);
|
||||
});
|
||||
|
||||
function waitRuleViewChanged(view, n) {
|
||||
let deferred = promise.defer();
|
||||
let count = 0;
|
||||
let listener = function() {
|
||||
if (++count == n) {
|
||||
view.off("ruleview-changed", listener);
|
||||
deferred.resolve();
|
||||
}
|
||||
};
|
||||
view.on("ruleview-changed", listener);
|
||||
return deferred.promise;
|
||||
}
|
||||
function* testCreateNewMultiUnfinished(inspector, view) {
|
||||
let ruleEditor = getRuleViewRuleEditor(view, 0);
|
||||
let onMutation = inspector.once("markupmutation");
|
||||
// There is 5 rule-view updates, one for the rule view creation,
|
||||
// one for each new property
|
||||
let onRuleViewChanged = waitRuleViewChanged(view, 5);
|
||||
// There are 2 rule-view updates: one for the preview and one for
|
||||
// the final commit.
|
||||
let onRuleViewChanged = waitForNEvents(view, "ruleview-changed", 2);
|
||||
yield createNewRuleViewProperty(ruleEditor,
|
||||
"color:blue;background : orange ; text-align:center; border-color: ");
|
||||
yield onMutation;
|
||||
|
@ -166,7 +166,7 @@ function* testParagraph(inspector, view) {
|
||||
|
||||
let elementFirstLineRule = rules.firstLineRules[0];
|
||||
is(convertTextPropsToString(elementFirstLineRule.textProps),
|
||||
"background: blue none repeat scroll 0% 0%",
|
||||
"background: blue",
|
||||
"Paragraph first-line properties are correct");
|
||||
|
||||
let elementFirstLetterRule = rules.firstLetterRules[0];
|
||||
@ -176,7 +176,7 @@ function* testParagraph(inspector, view) {
|
||||
|
||||
let elementSelectionRule = rules.selectionRules[0];
|
||||
is(convertTextPropsToString(elementSelectionRule.textProps),
|
||||
"color: white; background: black none repeat scroll 0% 0%",
|
||||
"color: white; background: black",
|
||||
"Paragraph first-letter properties are correct");
|
||||
}
|
||||
|
||||
|
@ -131,8 +131,7 @@ function* testPropertyChange6(inspector, ruleView, testElement) {
|
||||
"Added a property");
|
||||
validateTextProp(rule.textProps[4], true, "background",
|
||||
"red url(\"chrome://branding/content/about-logo.png\") repeat scroll 0% 0%",
|
||||
"shortcut property correctly set",
|
||||
"#F00 url(\"chrome://branding/content/about-logo.png\") repeat scroll 0% 0%");
|
||||
"shortcut property correctly set");
|
||||
}
|
||||
|
||||
function* changeElementStyle(testElement, style, inspector) {
|
||||
@ -141,8 +140,7 @@ function* changeElementStyle(testElement, style, inspector) {
|
||||
yield onRefreshed;
|
||||
}
|
||||
|
||||
function validateTextProp(aProp, aEnabled, aName, aValue, aDesc,
|
||||
valueSpanText) {
|
||||
function validateTextProp(aProp, aEnabled, aName, aValue, aDesc) {
|
||||
is(aProp.enabled, aEnabled, aDesc + ": enabled.");
|
||||
is(aProp.name, aName, aDesc + ": name.");
|
||||
is(aProp.value, aValue, aDesc + ": value.");
|
||||
@ -151,5 +149,5 @@ function validateTextProp(aProp, aEnabled, aName, aValue, aDesc,
|
||||
aDesc + ": enabled checkbox.");
|
||||
is(aProp.editor.nameSpan.textContent, aName, aDesc + ": name span.");
|
||||
is(aProp.editor.valueSpan.textContent,
|
||||
valueSpanText || aValue, aDesc + ": value span.");
|
||||
aValue, aDesc + ": value span.");
|
||||
}
|
||||
|
@ -7,12 +7,14 @@
|
||||
// Tests that the rule view search filter works properly in the computed list
|
||||
// for color values.
|
||||
|
||||
const SEARCH = "background-color: #F3F3F3";
|
||||
// The color format here is chosen to match the default returned by
|
||||
// CssColor.toString.
|
||||
const SEARCH = "background-color: rgb(243, 243, 243)";
|
||||
|
||||
const TEST_URI = `
|
||||
<style type="text/css">
|
||||
.testclass {
|
||||
background: #F3F3F3 none repeat scroll 0% 0%;
|
||||
background: rgb(243, 243, 243) none repeat scroll 0% 0%;
|
||||
}
|
||||
</style>
|
||||
<div class="testclass">Styled Node</h1>
|
||||
|
@ -43,7 +43,7 @@ function* testModifyPropertyValueFilter(inspector, view) {
|
||||
"top text property is correctly highlighted.");
|
||||
|
||||
let onBlur = once(editor.input, "blur");
|
||||
let onModification = rule._applyingModifications;
|
||||
let onModification = view.once("ruleview-changed");
|
||||
EventUtils.sendString("4px 0px", view.styleWindow);
|
||||
EventUtils.synthesizeKey("VK_RETURN", {});
|
||||
yield onBlur;
|
||||
|
@ -64,10 +64,10 @@ function* checkCopySelection(view) {
|
||||
let expectedPattern = " margin: 10em;[\\r\\n]+" +
|
||||
" font-size: 14pt;[\\r\\n]+" +
|
||||
" font-family: helvetica,sans-serif;[\\r\\n]+" +
|
||||
" color: #AAA;[\\r\\n]+" +
|
||||
" color: rgb\\(170, 170, 170\\);[\\r\\n]+" +
|
||||
"}[\\r\\n]+" +
|
||||
"html {[\\r\\n]+" +
|
||||
" color: #000;[\\r\\n]*";
|
||||
" color: #000000;[\\r\\n]*";
|
||||
|
||||
let onPopup = once(view._contextmenu._menupopup, "popupshown");
|
||||
EventUtils.synthesizeMouseAtCenter(prop,
|
||||
@ -102,10 +102,10 @@ function* checkSelectAll(view) {
|
||||
" margin: 10em;[\\r\\n]+" +
|
||||
" font-size: 14pt;[\\r\\n]+" +
|
||||
" font-family: helvetica,sans-serif;[\\r\\n]+" +
|
||||
" color: #AAA;[\\r\\n]+" +
|
||||
" color: rgb\\(170, 170, 170\\);[\\r\\n]+" +
|
||||
"}[\\r\\n]+" +
|
||||
"html {[\\r\\n]+" +
|
||||
" color: #000;[\\r\\n]+" +
|
||||
" color: #000000;[\\r\\n]+" +
|
||||
"}[\\r\\n]*";
|
||||
|
||||
let onPopup = once(view._contextmenu._menupopup, "popupshown");
|
||||
|
@ -40,9 +40,11 @@ function* userAgentStylesUneditable(inspector, view) {
|
||||
ok(rule.editor.element.hasAttribute("uneditable"),
|
||||
"UA rules have uneditable attribute");
|
||||
|
||||
ok(!rule.textProps[0].editor.nameSpan._editable,
|
||||
let firstProp = rule.textProps.filter(p => !p.invisible)[0];
|
||||
|
||||
ok(!firstProp.editor.nameSpan._editable,
|
||||
"nameSpan is not editable");
|
||||
ok(!rule.textProps[0].editor.valueSpan._editable,
|
||||
ok(!firstProp.editor.valueSpan._editable,
|
||||
"valueSpan is not editable");
|
||||
ok(!rule.editor.closeBrace._editable, "closeBrace is not editable");
|
||||
|
||||
|
@ -85,7 +85,7 @@ function testIsColorPopupOnNode(view, node) {
|
||||
let correct = isColorValueNode(node);
|
||||
|
||||
is(result, correct, "_isColorPopup returned the expected value " + correct);
|
||||
is(view._contextmenu._colorToCopy, (correct) ? "#123ABC" : "",
|
||||
is(view._contextmenu._colorToCopy, (correct) ? "rgb(18, 58, 188)" : "",
|
||||
"_colorToCopy was set to the expected value");
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ add_task(function*() {
|
||||
let {inspector, view} = yield openRuleView();
|
||||
yield selectNode("#one", inspector);
|
||||
|
||||
is(getRuleViewPropertyValue(view, "element", "color"), "#F00",
|
||||
is(getRuleViewPropertyValue(view, "element", "color"), "red",
|
||||
"The rule-view shows the properties for test node one");
|
||||
|
||||
let cView = inspector.sidebar.getWindowForTab("computedview")
|
||||
@ -38,6 +38,6 @@ add_task(function*() {
|
||||
ok(getComputedViewPropertyValue(cView, "color"), "#00F",
|
||||
"The computed-view shows the properties for test node two");
|
||||
|
||||
is(getRuleViewPropertyValue(view, "element", "color"), "#F00",
|
||||
is(getRuleViewPropertyValue(view, "element", "color"), "red",
|
||||
"The rule-view doesn't the properties for test node two");
|
||||
});
|
||||
|
19
devtools/client/styleinspector/test/doc_ruleLineNumbers.html
Normal file
19
devtools/client/styleinspector/test/doc_ruleLineNumbers.html
Normal file
@ -0,0 +1,19 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>simple testcase</title>
|
||||
<style type="text/css">
|
||||
#testid {
|
||||
background-color: seagreen;
|
||||
}
|
||||
|
||||
body {
|
||||
color: chartreuse;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="testid">simple testcase</div>
|
||||
</body>
|
||||
</html>
|
@ -306,6 +306,44 @@ function openRuleView() {
|
||||
return openInspectorSideBar("ruleview");
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for eventName on target to be delivered a number of times.
|
||||
*
|
||||
* @param {Object} target
|
||||
* An observable object that either supports on/off or
|
||||
* addEventListener/removeEventListener
|
||||
* @param {String} eventName
|
||||
* @param {Number} numTimes
|
||||
* Number of deliveries to wait for.
|
||||
* @param {Boolean} useCapture
|
||||
* Optional, for addEventListener/removeEventListener
|
||||
* @return A promise that resolves when the event has been handled
|
||||
*/
|
||||
function waitForNEvents(target, eventName, numTimes, useCapture = false) {
|
||||
info("Waiting for event: '" + eventName + "' on " + target + ".");
|
||||
|
||||
let deferred = promise.defer();
|
||||
let count = 0;
|
||||
|
||||
for (let [add, remove] of [
|
||||
["addEventListener", "removeEventListener"],
|
||||
["addListener", "removeListener"],
|
||||
["on", "off"]
|
||||
]) {
|
||||
if ((add in target) && (remove in target)) {
|
||||
target[add](eventName, function onEvent(...aArgs) {
|
||||
if (++count == numTimes) {
|
||||
target[remove](eventName, onEvent, useCapture);
|
||||
deferred.resolve.apply(deferred, aArgs);
|
||||
}
|
||||
}, useCapture);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for eventName on target.
|
||||
*
|
||||
@ -318,25 +356,7 @@ function openRuleView() {
|
||||
* @return A promise that resolves when the event has been handled
|
||||
*/
|
||||
function once(target, eventName, useCapture=false) {
|
||||
info("Waiting for event: '" + eventName + "' on " + target + ".");
|
||||
|
||||
let deferred = promise.defer();
|
||||
|
||||
for (let [add, remove] of [
|
||||
["addEventListener", "removeEventListener"],
|
||||
["addListener", "removeListener"],
|
||||
["on", "off"]
|
||||
]) {
|
||||
if ((add in target) && (remove in target)) {
|
||||
target[add](eventName, function onEvent(...aArgs) {
|
||||
target[remove](eventName, onEvent, useCapture);
|
||||
deferred.resolve.apply(deferred, aArgs);
|
||||
}, useCapture);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
return waitForNEvents(target, eventName, 1, useCapture);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -14,8 +14,9 @@ const {Class} = require("sdk/core/heritage");
|
||||
const {LongStringActor} = require("devtools/server/actors/string");
|
||||
const {PSEUDO_ELEMENT_SET} = require("devtools/shared/styleinspector/css-logic");
|
||||
|
||||
// This will add the "stylesheet" actor type for protocol.js to recognize
|
||||
require("devtools/server/actors/stylesheets");
|
||||
// This will also add the "stylesheet" actor type for protocol.js to recognize
|
||||
const {UPDATE_PRESERVING_RULES, UPDATE_GENERAL} =
|
||||
require("devtools/server/actors/stylesheets");
|
||||
|
||||
loader.lazyGetter(this, "CssLogic", () => {
|
||||
return require("devtools/shared/styleinspector/css-logic").CssLogic;
|
||||
@ -24,6 +25,10 @@ loader.lazyGetter(this, "DOMUtils", () => {
|
||||
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
|
||||
});
|
||||
|
||||
loader.lazyGetter(this, "RuleRewriter", () => {
|
||||
return require("devtools/client/styleinspector/css-parsing-utils").RuleRewriter;
|
||||
});
|
||||
|
||||
// The PageStyle actor flattens the DOM CSS objects a little bit, merging
|
||||
// Rules and their Styles into one actor. For elements (which have a style
|
||||
// but no associated rule) we fake a rule with the following style id.
|
||||
@ -128,6 +133,13 @@ types.addDictType("fontface", {
|
||||
var PageStyleActor = protocol.ActorClass({
|
||||
typeName: "pagestyle",
|
||||
|
||||
events: {
|
||||
"stylesheet-updated": {
|
||||
type: "styleSheetUpdated",
|
||||
styleSheet: Arg(0, "stylesheet")
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a PageStyleActor.
|
||||
*
|
||||
@ -151,9 +163,12 @@ var PageStyleActor = protocol.ActorClass({
|
||||
|
||||
this.onFrameUnload = this.onFrameUnload.bind(this);
|
||||
events.on(this.inspector.tabActor, "will-navigate", this.onFrameUnload);
|
||||
|
||||
this._styleApplied = this._styleApplied.bind(this);
|
||||
this._watchedSheets = new Set();
|
||||
},
|
||||
|
||||
destroy: function () {
|
||||
destroy: function() {
|
||||
if (!this.walker) {
|
||||
return;
|
||||
}
|
||||
@ -164,6 +179,11 @@ var PageStyleActor = protocol.ActorClass({
|
||||
this.refMap = null;
|
||||
this.cssLogic = null;
|
||||
this._styleElement = null;
|
||||
|
||||
for (let sheet of this._watchedSheets) {
|
||||
sheet.off("style-applied", this._styleApplied);
|
||||
}
|
||||
this._watchedSheets.clear();
|
||||
},
|
||||
|
||||
get conn() {
|
||||
@ -182,11 +202,22 @@ var PageStyleActor = protocol.ActorClass({
|
||||
// getApplied method calls cssLogic.highlight(node) to recreate the
|
||||
// style cache. Clients requesting getApplied from actors that have not
|
||||
// been fixed must make sure cssLogic.highlight(node) was called before.
|
||||
getAppliedCreatesStyleCache: true
|
||||
getAppliedCreatesStyleCache: true,
|
||||
// Whether addNewRule accepts the editAuthored argument.
|
||||
authoredStyles: true
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when a style sheet is updated.
|
||||
*/
|
||||
_styleApplied: function(kind, styleSheet) {
|
||||
if (kind === UPDATE_GENERAL) {
|
||||
events.emit(this, "stylesheet-updated", styleSheet);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Return or create a StyleRuleActor for the given item.
|
||||
* @param item Either a CSSStyleRule or a DOM element.
|
||||
@ -202,6 +233,21 @@ var PageStyleActor = protocol.ActorClass({
|
||||
return actor;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the association between a StyleRuleActor and its
|
||||
* corresponding item. This is used when a StyleRuleActor updates
|
||||
* as style sheet and starts using a new rule.
|
||||
*
|
||||
* @param oldItem The old association; either a CSSStyleRule or a
|
||||
* DOM element.
|
||||
* @param item Either a CSSStyleRule or a DOM element.
|
||||
* @param actor a StyleRuleActor
|
||||
*/
|
||||
updateStyleRef: function(oldItem, item, actor) {
|
||||
this.refMap.delete(oldItem);
|
||||
this.refMap.set(item, actor);
|
||||
},
|
||||
|
||||
/**
|
||||
* Return or create a StyleSheetActor for the given nsIDOMCSSStyleSheet.
|
||||
* @param {DOMStyleSheet} sheet
|
||||
@ -212,6 +258,10 @@ var PageStyleActor = protocol.ActorClass({
|
||||
_sheetRef: function(sheet) {
|
||||
let tabActor = this.inspector.tabActor;
|
||||
let actor = tabActor.createStyleSheetActor(sheet);
|
||||
if (!this._watchedSheets.has(actor)) {
|
||||
this._watchedSheets.add(actor);
|
||||
actor.on("style-applied", this._styleApplied);
|
||||
}
|
||||
return actor;
|
||||
},
|
||||
|
||||
@ -524,7 +574,7 @@ var PageStyleActor = protocol.ActorClass({
|
||||
* `matchedSelectors`: Include an array of specific selectors that
|
||||
* caused this rule to match its node.
|
||||
*/
|
||||
getApplied: method(function(node, options) {
|
||||
getApplied: method(Task.async(function*(node, options) {
|
||||
if (!node) {
|
||||
return {entries: [], rules: [], sheets: []};
|
||||
}
|
||||
@ -532,8 +582,14 @@ var PageStyleActor = protocol.ActorClass({
|
||||
this.cssLogic.highlight(node.rawNode);
|
||||
let entries = [];
|
||||
entries = entries.concat(this._getAllElementRules(node, undefined, options));
|
||||
return this.getAppliedProps(node, entries, options);
|
||||
}, {
|
||||
|
||||
let result = this.getAppliedProps(node, entries, options);
|
||||
for (let rule of result.rules) {
|
||||
// See the comment in |form| to understand this.
|
||||
yield rule.getAuthoredCssText();
|
||||
}
|
||||
return result;
|
||||
}), {
|
||||
request: {
|
||||
node: Arg(0, "domnode"),
|
||||
inherited: Option(1, "boolean"),
|
||||
@ -905,12 +961,17 @@ var PageStyleActor = protocol.ActorClass({
|
||||
|
||||
/**
|
||||
* Adds a new rule, and returns the new StyleRuleActor.
|
||||
* @param NodeActor node
|
||||
* @param [string] pseudoClasses The list of pseudo classes to append to the
|
||||
* new selector.
|
||||
* @returns StyleRuleActor of the new rule
|
||||
* @param {NodeActor} node
|
||||
* @param {String} pseudoClasses The list of pseudo classes to append to the
|
||||
* new selector.
|
||||
* @param {Boolean} editAuthored
|
||||
* True if the selector should be updated by editing the
|
||||
* authored text; false if the selector should be updated via
|
||||
* CSSOM.
|
||||
* @returns {StyleRuleActor} the new rule
|
||||
*/
|
||||
addNewRule: method(function(node, pseudoClasses) {
|
||||
addNewRule: method(Task.async(function*(node, pseudoClasses,
|
||||
editAuthored = false) {
|
||||
let style = this.styleElement;
|
||||
let sheet = style.sheet;
|
||||
let cssRules = sheet.cssRules;
|
||||
@ -930,11 +991,22 @@ var PageStyleActor = protocol.ActorClass({
|
||||
}
|
||||
|
||||
let index = sheet.insertRule(selector + " {}", cssRules.length);
|
||||
return this.getNewAppliedProps(node, cssRules.item(index));
|
||||
}, {
|
||||
|
||||
// If inserting the rule succeeded, go ahead and edit the source
|
||||
// text if requested.
|
||||
if (editAuthored) {
|
||||
let sheetActor = this._sheetRef(sheet);
|
||||
let {str: authoredText} = yield sheetActor.getText();
|
||||
authoredText += "\n" + selector + " {\n" + "}";
|
||||
yield sheetActor.update(authoredText, false);
|
||||
}
|
||||
|
||||
return this.getNewAppliedProps(node, sheet.cssRules.item(index));
|
||||
}), {
|
||||
request: {
|
||||
node: Arg(0, "domnode"),
|
||||
pseudoClasses: Arg(1, "nullable:array:string")
|
||||
pseudoClasses: Arg(1, "nullable:array:string"),
|
||||
editAuthored: Arg(2, "boolean")
|
||||
},
|
||||
response: RetVal("appliedStylesReturn")
|
||||
}),
|
||||
@ -966,6 +1038,10 @@ var PageStyleFront = protocol.FrontClass(PageStyleActor, {
|
||||
return this.inspector.walker;
|
||||
},
|
||||
|
||||
get supportsAuthoredStyles() {
|
||||
return this._form.traits && this._form.traits.authoredStyles;
|
||||
},
|
||||
|
||||
getMatchedSelectors: protocol.custom(function(node, property, options) {
|
||||
return this._getMatchedSelectors(node, property, options).then(ret => {
|
||||
return ret.matched;
|
||||
@ -989,7 +1065,13 @@ var PageStyleFront = protocol.FrontClass(PageStyleActor, {
|
||||
}),
|
||||
|
||||
addNewRule: protocol.custom(function(node, pseudoClasses) {
|
||||
return this._addNewRule(node, pseudoClasses).then(ret => {
|
||||
let addPromise;
|
||||
if (this.supportsAuthoredStyles) {
|
||||
addPromise = this._addNewRule(node, pseudoClasses, true);
|
||||
} else {
|
||||
addPromise = this._addNewRule(node, pseudoClasses);
|
||||
}
|
||||
return addPromise.then(ret => {
|
||||
return ret.entries[0];
|
||||
});
|
||||
}, {
|
||||
@ -1007,19 +1089,34 @@ var PageStyleFront = protocol.FrontClass(PageStyleActor, {
|
||||
*/
|
||||
var StyleRuleActor = protocol.ActorClass({
|
||||
typeName: "domstylerule",
|
||||
|
||||
events: {
|
||||
"location-changed": {
|
||||
type: "locationChanged",
|
||||
line: Arg(0, "number"),
|
||||
column: Arg(1, "number")
|
||||
},
|
||||
},
|
||||
|
||||
initialize: function(pageStyle, item) {
|
||||
protocol.Actor.prototype.initialize.call(this, null);
|
||||
this.pageStyle = pageStyle;
|
||||
this.rawStyle = item.style;
|
||||
this._parentSheet = null;
|
||||
this._onStyleApplied = this._onStyleApplied.bind(this);
|
||||
|
||||
if (item instanceof (Ci.nsIDOMCSSRule)) {
|
||||
this.type = item.type;
|
||||
this.rawRule = item;
|
||||
if ((this.rawRule instanceof Ci.nsIDOMCSSStyleRule ||
|
||||
this.rawRule instanceof Ci.nsIDOMMozCSSKeyframeRule) &&
|
||||
this.rawRule.parentStyleSheet) {
|
||||
this.line = DOMUtils.getRuleLine(this.rawRule);
|
||||
this.rawRule.parentStyleSheet) {
|
||||
this.line = DOMUtils.getRelativeRuleLine(this.rawRule);
|
||||
this.column = DOMUtils.getRuleColumn(this.rawRule);
|
||||
this._parentSheet = this.rawRule.parentStyleSheet;
|
||||
this._computeRuleIndex();
|
||||
this.sheetActor = this.pageStyle._sheetRef(this._parentSheet);
|
||||
this.sheetActor.on("style-applied", this._onStyleApplied);
|
||||
}
|
||||
} else {
|
||||
// Fake a rule
|
||||
@ -1047,6 +1144,9 @@ var StyleRuleActor = protocol.ActorClass({
|
||||
this.pageStyle = null;
|
||||
this.rawNode = null;
|
||||
this.rawRule = null;
|
||||
if (this.sheetActor) {
|
||||
this.sheetActor.off("style-applied", this._onStyleApplied);
|
||||
}
|
||||
},
|
||||
|
||||
// Objects returned by this actor are owned by the PageStyleActor
|
||||
@ -1055,6 +1155,17 @@ var StyleRuleActor = protocol.ActorClass({
|
||||
return this.pageStyle;
|
||||
},
|
||||
|
||||
// True if this rule supports as-authored styles, meaning that the
|
||||
// rule text can be rewritten using setRuleText.
|
||||
get canSetRuleText() {
|
||||
// Special case about:PreferenceStyleSheet, as it is
|
||||
// generated on the fly and the URI is not registered with the
|
||||
// about: handler.
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=935803#c37
|
||||
return !!(this._parentSheet &&
|
||||
this._parentSheet.href !== "about:PreferenceStyleSheet");
|
||||
},
|
||||
|
||||
getDocument: function(sheet) {
|
||||
let document;
|
||||
|
||||
@ -1085,6 +1196,9 @@ var StyleRuleActor = protocol.ActorClass({
|
||||
// Whether the style rule actor implements the modifySelector2 method
|
||||
// that allows for unmatched rule to be added
|
||||
modifySelectorUnmatched: true,
|
||||
// Whether the style rule actor implements the setRuleText
|
||||
// method.
|
||||
canSetRuleText: this.canSetRuleText,
|
||||
}
|
||||
};
|
||||
|
||||
@ -1102,10 +1216,17 @@ var StyleRuleActor = protocol.ActorClass({
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.rawRule.parentStyleSheet) {
|
||||
form.parentStyleSheet = this.pageStyle._sheetRef(this.rawRule.parentStyleSheet).actorID;
|
||||
if (this._parentSheet) {
|
||||
form.parentStyleSheet = this.pageStyle._sheetRef(this._parentSheet).actorID;
|
||||
}
|
||||
|
||||
// One tricky thing here is that other methods in this actor must
|
||||
// ensure that authoredText has been set before |form| is called.
|
||||
// This has to be treated specially, for now, because we cannot
|
||||
// synchronously compute the authored text, but |form| also cannot
|
||||
// return a promise. See bug 1205868.
|
||||
form.authoredText = this.authoredText;
|
||||
|
||||
switch (this.type) {
|
||||
case Ci.nsIDOMCSSRule.STYLE_RULE:
|
||||
form.selectors = CssLogic.getSelectors(this.rawRule);
|
||||
@ -1139,7 +1260,125 @@ var StyleRuleActor = protocol.ActorClass({
|
||||
},
|
||||
|
||||
/**
|
||||
* Modify a rule's properties. Passed an array of modifications:
|
||||
* Send an event notifying that the location of the rule has
|
||||
* changed.
|
||||
*
|
||||
* @param {Number} line the new line number
|
||||
* @param {Number} column the new column number
|
||||
*/
|
||||
_notifyLocationChanged: function(line, column) {
|
||||
events.emit(this, "location-changed", line, column);
|
||||
},
|
||||
|
||||
/**
|
||||
* Compute the index of this actor's raw rule in its parent style
|
||||
* sheet.
|
||||
*/
|
||||
_computeRuleIndex: function() {
|
||||
let rule = this.rawRule;
|
||||
let cssRules = this._parentSheet.cssRules;
|
||||
this._ruleIndex = -1;
|
||||
for (let i = 0; i < cssRules.length; i++) {
|
||||
if (rule === cssRules.item(i)) {
|
||||
this._ruleIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* This is attached to the parent style sheet actor's
|
||||
* "style-applied" event.
|
||||
*/
|
||||
_onStyleApplied: function(kind) {
|
||||
if (kind === UPDATE_GENERAL) {
|
||||
// A general change means that the rule actors are invalidated,
|
||||
// so stop listening to events now.
|
||||
if (this.sheetActor) {
|
||||
this.sheetActor.off("style-applied", this._onStyleApplied);
|
||||
}
|
||||
} else if (this._ruleIndex >= 0) {
|
||||
// The sheet was updated by this actor, in a way that preserves
|
||||
// the rules. Now, recompute our new rule from the style sheet,
|
||||
// so that we aren't left with a reference to a dangling rule.
|
||||
let oldRule = this.rawRule;
|
||||
this.rawRule = this._parentSheet.cssRules[this._ruleIndex];
|
||||
// Also tell the page style so that future calls to _styleRef
|
||||
// return the same StyleRuleActor.
|
||||
this.pageStyle.updateStyleRef(oldRule, this.rawRule, this);
|
||||
let line = DOMUtils.getRelativeRuleLine(this.rawRule);
|
||||
let column = DOMUtils.getRuleColumn(this.rawRule);
|
||||
if (line !== this.line || column !== this.column) {
|
||||
this._notifyLocationChanged(line, column);
|
||||
}
|
||||
this.line = line;
|
||||
this.column = column;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Return a promise that resolves to the authored form of a rule's
|
||||
* text, if available. If the authored form is not available, the
|
||||
* returned promise simply resolves to the empty string. If the
|
||||
* authored form is available, this also sets |this.authoredText|.
|
||||
* The authored text will include invalid and otherwise ignored
|
||||
* properties.
|
||||
*/
|
||||
getAuthoredCssText: function() {
|
||||
if (!this.canSetRuleText ||
|
||||
(this.type !== Ci.nsIDOMCSSRule.STYLE_RULE &&
|
||||
this.type !== Ci.nsIDOMCSSRule.KEYFRAME_RULE)) {
|
||||
return promise.resolve("");
|
||||
}
|
||||
|
||||
if (typeof this.authoredText === "string") {
|
||||
return promise.resolve(this.authoredText);
|
||||
}
|
||||
|
||||
let parentStyleSheet =
|
||||
this.pageStyle._sheetRef(this._parentSheet);
|
||||
return parentStyleSheet.getText().then((longStr) => {
|
||||
let cssText = longStr.str;
|
||||
let {text} = getRuleText(cssText, this.line, this.column);
|
||||
|
||||
// Cache the result on the rule actor to avoid parsing again next time
|
||||
this.authoredText = text;
|
||||
return this.authoredText;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the contents of the rule. This rewrites the rule in the
|
||||
* stylesheet and causes it to be re-evaluated.
|
||||
*
|
||||
* @param {String} newText the new text of the rule
|
||||
* @returns the rule with updated properties
|
||||
*/
|
||||
setRuleText: method(Task.async(function*(newText) {
|
||||
if (!this.canSetRuleText ||
|
||||
(this.type !== Ci.nsIDOMCSSRule.STYLE_RULE &&
|
||||
this.type !== Ci.nsIDOMCSSRule.KEYFRAME_RULE)) {
|
||||
throw new Error("invalid call to setRuleText");
|
||||
}
|
||||
|
||||
let parentStyleSheet = this.pageStyle._sheetRef(this._parentSheet);
|
||||
let {str: cssText} = yield parentStyleSheet.getText();
|
||||
|
||||
let {offset, text} = getRuleText(cssText, this.line, this.column);
|
||||
cssText = cssText.substring(0, offset) + newText +
|
||||
cssText.substring(offset + text.length);
|
||||
|
||||
this.authoredText = newText;
|
||||
yield parentStyleSheet.update(cssText, false, UPDATE_PRESERVING_RULES);
|
||||
|
||||
return this;
|
||||
}), {
|
||||
request: { modification: Arg(0, "string") },
|
||||
response: { rule: RetVal("domstylerule") }
|
||||
}),
|
||||
|
||||
/**
|
||||
* Modify a rule's properties. Passed an array of modifications:
|
||||
* {
|
||||
* type: "set",
|
||||
* name: <string>,
|
||||
@ -1163,7 +1402,7 @@ var StyleRuleActor = protocol.ActorClass({
|
||||
if (this.rawNode) {
|
||||
document = this.rawNode.ownerDocument;
|
||||
} else {
|
||||
let parentStyleSheet = this.rawRule.parentStyleSheet;
|
||||
let parentStyleSheet = this._parentSheet;
|
||||
while (parentStyleSheet.ownerRule &&
|
||||
parentStyleSheet.ownerRule instanceof Ci.nsIDOMCSSImportRule) {
|
||||
parentStyleSheet = parentStyleSheet.ownerRule.parentStyleSheet;
|
||||
@ -1196,39 +1435,61 @@ var StyleRuleActor = protocol.ActorClass({
|
||||
* current rule. Returns the newly inserted css rule or null if the rule is
|
||||
* unsuccessfully inserted to the parent style sheet.
|
||||
*
|
||||
* @param string value
|
||||
* @param {String} value
|
||||
* The new selector value
|
||||
* @param {Boolean} editAuthored
|
||||
* True if the selector should be updated by editing the
|
||||
* authored text; false if the selector should be updated via
|
||||
* CSSOM.
|
||||
*
|
||||
* @returns CSSRule
|
||||
* @returns {CSSRule}
|
||||
* The new CSS rule added
|
||||
*/
|
||||
_addNewSelector: function(value) {
|
||||
_addNewSelector: Task.async(function*(value, editAuthored) {
|
||||
let rule = this.rawRule;
|
||||
let parentStyleSheet = rule.parentStyleSheet;
|
||||
let cssRules = parentStyleSheet.cssRules;
|
||||
let cssText = rule.cssText;
|
||||
let selectorText = rule.selectorText;
|
||||
let parentStyleSheet = this._parentSheet;
|
||||
|
||||
for (let i = 0; i < cssRules.length; i++) {
|
||||
if (rule === cssRules.item(i)) {
|
||||
try {
|
||||
// Inserts the new style rule into the current style sheet and
|
||||
// delete the current rule
|
||||
let ruleText = cssText.slice(selectorText.length).trim();
|
||||
parentStyleSheet.insertRule(value + " " + ruleText, i);
|
||||
parentStyleSheet.deleteRule(i + 1);
|
||||
return cssRules.item(i);
|
||||
} catch(e) {
|
||||
// The selector could be invalid, or the rule could fail to insert.
|
||||
// If that happens, the method returns null.
|
||||
// We know the selector modification is ok, so if the client asked
|
||||
// for the authored text to be edited, do it now.
|
||||
if (editAuthored) {
|
||||
let document = this.getDocument(this._parentSheet);
|
||||
try {
|
||||
document.querySelector(value);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let sheetActor = this.pageStyle._sheetRef(parentStyleSheet);
|
||||
let {str: authoredText} = yield sheetActor.getText();
|
||||
let [startOffset, endOffset] = getSelectorOffsets(authoredText, this.line,
|
||||
this.column);
|
||||
authoredText = authoredText.substring(0, startOffset) + value +
|
||||
authoredText.substring(endOffset);
|
||||
yield sheetActor.update(authoredText, false, UPDATE_PRESERVING_RULES);
|
||||
} else {
|
||||
let cssRules = parentStyleSheet.cssRules;
|
||||
let cssText = rule.cssText;
|
||||
let selectorText = rule.selectorText;
|
||||
|
||||
for (let i = 0; i < cssRules.length; i++) {
|
||||
if (rule === cssRules.item(i)) {
|
||||
try {
|
||||
// Inserts the new style rule into the current style sheet and
|
||||
// delete the current rule
|
||||
let ruleText = cssText.slice(selectorText.length).trim();
|
||||
parentStyleSheet.insertRule(value + " " + ruleText, i);
|
||||
parentStyleSheet.deleteRule(i + 1);
|
||||
break;
|
||||
} catch(e) {
|
||||
// The selector could be invalid, or the rule could fail to insert.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
return parentStyleSheet.cssRules[this._ruleIndex];
|
||||
}),
|
||||
|
||||
/**
|
||||
* Modify the current rule's selector by inserting a new rule with the new
|
||||
@ -1243,12 +1504,12 @@ var StyleRuleActor = protocol.ActorClass({
|
||||
* Returns a boolean if the selector in the stylesheet was modified,
|
||||
* and false otherwise
|
||||
*/
|
||||
modifySelector: method(function(value) {
|
||||
modifySelector: method(Task.async(function*(value) {
|
||||
if (this.type === ELEMENT_STYLE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let document = this.getDocument(this.rawRule.parentStyleSheet);
|
||||
let document = this.getDocument(this._parentSheet);
|
||||
// Extract the selector, and pseudo elements and classes
|
||||
let [selector, pseudoProp] = value.split(/(:{1,2}.+$)/);
|
||||
let selectorElement;
|
||||
@ -1262,11 +1523,11 @@ var StyleRuleActor = protocol.ActorClass({
|
||||
// Check if the selector is valid and not the same as the original
|
||||
// selector
|
||||
if (selectorElement && this.rawRule.selectorText !== value) {
|
||||
this._addNewSelector(value);
|
||||
yield this._addNewSelector(value, false);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, {
|
||||
}), {
|
||||
request: { selector: Arg(0, "string") },
|
||||
response: { isModified: RetVal("boolean") },
|
||||
}),
|
||||
@ -1281,17 +1542,20 @@ var StyleRuleActor = protocol.ActorClass({
|
||||
* selector matches the current element without having to refresh the whole
|
||||
* list.
|
||||
*
|
||||
* @param DOMNode node
|
||||
* @param {DOMNode} node
|
||||
* The current selected element
|
||||
* @param string value
|
||||
* @param {String} value
|
||||
* The new selector value
|
||||
* @returns Object
|
||||
* @param {Boolean} editAuthored
|
||||
* True if the selector should be updated by editing the
|
||||
* authored text; false if the selector should be updated via
|
||||
* CSSOM.
|
||||
* @returns {Object}
|
||||
* Returns an object that contains the applied style properties of the
|
||||
* new rule and a boolean indicating whether or not the new selector
|
||||
* matches the current selected element
|
||||
*/
|
||||
modifySelector2: method(function(node, value) {
|
||||
let isMatching = false;
|
||||
modifySelector2: method(function(node, value, editAuthored = false) {
|
||||
let ruleProps = null;
|
||||
|
||||
if (this.type === ELEMENT_STYLE ||
|
||||
@ -1299,23 +1563,40 @@ var StyleRuleActor = protocol.ActorClass({
|
||||
return { ruleProps, isMatching: true };
|
||||
}
|
||||
|
||||
let newCssRule = this._addNewSelector(value);
|
||||
if (newCssRule) {
|
||||
ruleProps = this.pageStyle.getNewAppliedProps(node, newCssRule);
|
||||
let selectorPromise = this._addNewSelector(value, editAuthored);
|
||||
|
||||
if (editAuthored) {
|
||||
selectorPromise = selectorPromise.then((newCssRule) => {
|
||||
if (newCssRule) {
|
||||
let style = this.pageStyle._styleRef(newCssRule);
|
||||
// See the comment in |form| to understand this.
|
||||
return style.getAuthoredCssText().then(() => newCssRule);
|
||||
}
|
||||
return newCssRule;
|
||||
});
|
||||
}
|
||||
|
||||
// Determine if the new selector value matches the current selected element
|
||||
try {
|
||||
isMatching = node.rawNode.matches(value);
|
||||
} catch(e) {
|
||||
// This fails when value is an invalid selector.
|
||||
}
|
||||
return selectorPromise.then((newCssRule) => {
|
||||
if (newCssRule) {
|
||||
ruleProps = this.pageStyle.getNewAppliedProps(node, newCssRule);
|
||||
}
|
||||
|
||||
return { ruleProps, isMatching };
|
||||
// Determine if the new selector value matches the current
|
||||
// selected element
|
||||
let isMatching = false;
|
||||
try {
|
||||
isMatching = node.rawNode.matches(value);
|
||||
} catch(e) {
|
||||
// This fails when value is an invalid selector.
|
||||
}
|
||||
|
||||
return { ruleProps, isMatching };
|
||||
});
|
||||
}, {
|
||||
request: {
|
||||
node: Arg(0, "domnode"),
|
||||
value: Arg(1, "string")
|
||||
value: Arg(1, "string"),
|
||||
editAuthored: Arg(2, "boolean")
|
||||
},
|
||||
response: RetVal("modifiedStylesReturn")
|
||||
})
|
||||
@ -1346,9 +1627,25 @@ var StyleRuleFront = protocol.FrontClass(StyleRuleActor, {
|
||||
},
|
||||
|
||||
/**
|
||||
* Return a new RuleModificationList for this node.
|
||||
* Ensure _form is updated when location-changed is emitted.
|
||||
*/
|
||||
_locationChangedPre: protocol.preEvent("location-changed", function(line,
|
||||
column) {
|
||||
this._clearOriginalLocation();
|
||||
this._form.line = line;
|
||||
this._form.column = column;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Return a new RuleModificationList or RuleRewriter for this node.
|
||||
* A RuleRewriter will be returned when the rule's canSetRuleText
|
||||
* trait is true; otherwise a RuleModificationList will be
|
||||
* returned.
|
||||
*/
|
||||
startModifyingProperties: function() {
|
||||
if (this.canSetRuleText) {
|
||||
return new RuleRewriter(this, this.authoredText);
|
||||
}
|
||||
return new RuleModificationList(this);
|
||||
},
|
||||
|
||||
@ -1364,6 +1661,9 @@ var StyleRuleFront = protocol.FrontClass(StyleRuleActor, {
|
||||
get cssText() {
|
||||
return this._form.cssText;
|
||||
},
|
||||
get authoredText() {
|
||||
return this._form.authoredText || this._form.cssText;
|
||||
},
|
||||
get keyText() {
|
||||
return this._form.keyText;
|
||||
},
|
||||
@ -1416,6 +1716,10 @@ var StyleRuleFront = protocol.FrontClass(StyleRuleActor, {
|
||||
return this._form.traits && this._form.traits.modifySelectorUnmatched;
|
||||
},
|
||||
|
||||
get canSetRuleText() {
|
||||
return this._form.traits && this._form.traits.canSetRuleText;
|
||||
},
|
||||
|
||||
get location() {
|
||||
return {
|
||||
source: this.parentStyleSheet,
|
||||
@ -1425,6 +1729,10 @@ var StyleRuleFront = protocol.FrontClass(StyleRuleActor, {
|
||||
};
|
||||
},
|
||||
|
||||
_clearOriginalLocation: function() {
|
||||
this._originalLocation = null;
|
||||
},
|
||||
|
||||
getOriginalLocation: function() {
|
||||
if (this._originalLocation) {
|
||||
return promise.resolve(this._originalLocation);
|
||||
@ -1458,7 +1766,11 @@ var StyleRuleFront = protocol.FrontClass(StyleRuleActor, {
|
||||
let response;
|
||||
if (this.supportsModifySelectorUnmatched) {
|
||||
// If the debugee supports adding unmatched rules (post FF41)
|
||||
response = yield this.modifySelector2(node, value);
|
||||
if (this.canSetRuleText) {
|
||||
response = yield this.modifySelector2(node, value, true);
|
||||
} else {
|
||||
response = yield this.modifySelector2(node, value);
|
||||
}
|
||||
} else {
|
||||
response = yield this._modifySelector(value);
|
||||
}
|
||||
@ -1469,6 +1781,13 @@ var StyleRuleFront = protocol.FrontClass(StyleRuleActor, {
|
||||
return response;
|
||||
}), {
|
||||
impl: "_modifySelector"
|
||||
}),
|
||||
|
||||
setRuleText: protocol.custom(function(newText) {
|
||||
this._form.authoredText = newText;
|
||||
return this._setRuleText(newText);
|
||||
}, {
|
||||
impl: "_setRuleText"
|
||||
})
|
||||
});
|
||||
|
||||
@ -1478,6 +1797,10 @@ var StyleRuleFront = protocol.FrontClass(StyleRuleActor, {
|
||||
* list of modifications that will be applied to a StyleRuleActor.
|
||||
* The modifications are processed in the order in which they are
|
||||
* added to the RuleModificationList.
|
||||
*
|
||||
* Objects of this type expose the same API as @see RuleRewriter.
|
||||
* This lets the inspector use (mostly) the same code, regardless of
|
||||
* whether the server implements setRuleText.
|
||||
*/
|
||||
var RuleModificationList = Class({
|
||||
/**
|
||||
@ -1502,12 +1825,17 @@ var RuleModificationList = Class({
|
||||
/**
|
||||
* Add a "set" entry to the modification list.
|
||||
*
|
||||
* @param {string} name the property's name
|
||||
* @param {string} value the property's value
|
||||
* @param {string} priority the property's priority, either the empty
|
||||
* @param {Number} index index of the property in the rule.
|
||||
* This can be -1 in the case where
|
||||
* the rule does not support setRuleText;
|
||||
* generally for setting properties
|
||||
* on an element's style.
|
||||
* @param {String} name the property's name
|
||||
* @param {String} value the property's value
|
||||
* @param {String} priority the property's priority, either the empty
|
||||
* string or "important"
|
||||
*/
|
||||
setProperty: function(name, value, priority) {
|
||||
setProperty: function(index, name, value, priority) {
|
||||
this.modifications.push({
|
||||
type: "set",
|
||||
name: name,
|
||||
@ -1519,14 +1847,73 @@ var RuleModificationList = Class({
|
||||
/**
|
||||
* Add a "remove" entry to the modification list.
|
||||
*
|
||||
* @param {string} name the name of the property to remove
|
||||
* @param {Number} index index of the property in the rule.
|
||||
* This can be -1 in the case where
|
||||
* the rule does not support setRuleText;
|
||||
* generally for setting properties
|
||||
* on an element's style.
|
||||
* @param {String} name the name of the property to remove
|
||||
*/
|
||||
removeProperty: function(name) {
|
||||
removeProperty: function(index, name) {
|
||||
this.modifications.push({
|
||||
type: "remove",
|
||||
name: name
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Rename a property. This implementation acts like
|
||||
* |removeProperty|, because |setRuleText| is not available.
|
||||
*
|
||||
* @param {Number} index index of the property in the rule.
|
||||
* This can be -1 in the case where
|
||||
* the rule does not support setRuleText;
|
||||
* generally for setting properties
|
||||
* on an element's style.
|
||||
* @param {String} name current name of the property
|
||||
* @param {String} newName new name of the property
|
||||
*/
|
||||
renameProperty: function(index, name, newName) {
|
||||
this.removeProperty(index, name);
|
||||
},
|
||||
|
||||
/**
|
||||
* Enable or disable a property. This implementation acts like
|
||||
* |removeProperty| when disabling, or a no-op when enabling,
|
||||
* because |setRuleText| is not available.
|
||||
*
|
||||
* @param {Number} index index of the property in the rule.
|
||||
* This can be -1 in the case where
|
||||
* the rule does not support setRuleText;
|
||||
* generally for setting properties
|
||||
* on an element's style.
|
||||
* @param {String} name current name of the property
|
||||
* @param {Boolean} isEnabled true if the property should be enabled;
|
||||
* false if it should be disabled
|
||||
*/
|
||||
setPropertyEnabled: function(index, name, isEnabled) {
|
||||
if (!isEnabled) {
|
||||
this.removeProperty(index, name);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new property. This implementation does nothing, because
|
||||
* |setRuleText| is not available.
|
||||
*
|
||||
* @param {Number} index index of the property in the rule.
|
||||
* This can be -1 in the case where
|
||||
* the rule does not support setRuleText;
|
||||
* generally for setting properties
|
||||
* on an element's style.
|
||||
* @param {String} name name of the new property
|
||||
* @param {String} value value of the new property
|
||||
* @param {String} priority priority of the new property; either
|
||||
* the empty string or "important"
|
||||
*/
|
||||
createProperty: function(index, name, value, priority) {
|
||||
// Nothing.
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
@ -1659,6 +2046,47 @@ function getRuleText(initialText, line, column) {
|
||||
|
||||
exports.getRuleText = getRuleText;
|
||||
|
||||
/**
|
||||
* Compute the start and end offsets of a rule's selector text, given
|
||||
* the CSS text and the line and column at which the rule begins.
|
||||
* @param {String} initialText
|
||||
* @param {Number} line (1-indexed)
|
||||
* @param {Number} column (1-indexed)
|
||||
* @return {array} An array with two elements: [startOffset, endOffset].
|
||||
* The elements mark the bounds in |initialText| of
|
||||
* the CSS rule's selector.
|
||||
*/
|
||||
function getSelectorOffsets(initialText, line, column) {
|
||||
if (typeof line === "undefined" || typeof column === "undefined") {
|
||||
throw new Error("Location information is missing");
|
||||
}
|
||||
|
||||
let {offset: textOffset, text} =
|
||||
getTextAtLineColumn(initialText, line, column);
|
||||
let lexer = DOMUtils.getCSSLexer(text);
|
||||
|
||||
// Search forward for the opening brace.
|
||||
let endOffset;
|
||||
while (true) {
|
||||
let token = lexer.nextToken();
|
||||
if (!token) {
|
||||
break;
|
||||
}
|
||||
if (token.tokenType === "symbol" && token.text === "{") {
|
||||
if (endOffset === undefined) {
|
||||
break;
|
||||
}
|
||||
return [textOffset, textOffset + endOffset];
|
||||
}
|
||||
// Preserve comments and whitespace just before the "{".
|
||||
if (token.tokenType !== "comment" && token.tokenType !== "whitespace") {
|
||||
endOffset = token.endOffset;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("could not find bounds of rule");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the offset and substring of |text| that starts at the given
|
||||
* line and column.
|
||||
|
@ -23,6 +23,11 @@ const {SourceMapConsumer} = require("source-map");
|
||||
|
||||
loader.lazyGetter(this, "CssLogic", () => require("devtools/shared/styleinspector/css-logic").CssLogic);
|
||||
|
||||
const {
|
||||
getIndentationFromPrefs,
|
||||
getIndentationFromString
|
||||
} = require("devtools/shared/shared/indentation");
|
||||
|
||||
var TRANSITION_CLASS = "moz-styleeditor-transitioning";
|
||||
var TRANSITION_DURATION_MS = 500;
|
||||
var TRANSITION_BUFFER_MS = 1000;
|
||||
@ -40,6 +45,22 @@ var LOAD_ERROR = "error-load";
|
||||
types.addActorType("stylesheet");
|
||||
types.addActorType("originalsource");
|
||||
|
||||
// The possible kinds of style-applied events.
|
||||
// UPDATE_PRESERVING_RULES means that the update is guaranteed to
|
||||
// preserve the number and order of rules on the style sheet.
|
||||
// UPDATE_GENERAL covers any other kind of change to the style sheet.
|
||||
const UPDATE_PRESERVING_RULES = 0;
|
||||
exports.UPDATE_PRESERVING_RULES = UPDATE_PRESERVING_RULES;
|
||||
const UPDATE_GENERAL = 1;
|
||||
exports.UPDATE_GENERAL = UPDATE_GENERAL;
|
||||
|
||||
// If the user edits a style sheet, we stash a copy of the edited text
|
||||
// here, keyed by the style sheet. This way, if the tools are closed
|
||||
// and then reopened, the edited text will be available. A weak map
|
||||
// is used so that navigation by the user will eventually cause the
|
||||
// edited text to be collected.
|
||||
let modifiedStyleSheets = new WeakMap();
|
||||
|
||||
/**
|
||||
* Creates a StyleSheetsActor. StyleSheetsActor provides remote access to the
|
||||
* stylesheets of a document.
|
||||
@ -385,7 +406,9 @@ var StyleSheetActor = protocol.ActorClass({
|
||||
value: Arg(1, "json")
|
||||
},
|
||||
"style-applied" : {
|
||||
type: "styleApplied"
|
||||
type: "styleApplied",
|
||||
kind: Arg(0, "number"),
|
||||
styleSheet: Arg(1, "stylesheet")
|
||||
},
|
||||
"media-rules-changed" : {
|
||||
type: "mediaRulesChanged",
|
||||
@ -597,6 +620,12 @@ var StyleSheetActor = protocol.ActorClass({
|
||||
return promise.resolve(this.text);
|
||||
}
|
||||
|
||||
let cssText = modifiedStyleSheets.get(this.rawSheet);
|
||||
if (cssText !== undefined) {
|
||||
this.text = cssText;
|
||||
return promise.resolve(cssText);
|
||||
}
|
||||
|
||||
if (!this.href) {
|
||||
// this is an inline <style> sheet
|
||||
let content = this.ownerNode.textContent;
|
||||
@ -872,19 +901,22 @@ var StyleSheetActor = protocol.ActorClass({
|
||||
* @param {object} request
|
||||
* 'text' - new text
|
||||
* 'transition' - whether to do CSS transition for change.
|
||||
* 'kind' - either UPDATE_PRESERVING_RULES or UPDATE_GENERAL
|
||||
*/
|
||||
update: method(function(text, transition) {
|
||||
update: method(function(text, transition, kind = UPDATE_GENERAL) {
|
||||
DOMUtils.parseStyleSheet(this.rawSheet, text);
|
||||
|
||||
modifiedStyleSheets.set(this.rawSheet, text);
|
||||
|
||||
this.text = text;
|
||||
|
||||
this._notifyPropertyChanged("ruleCount");
|
||||
|
||||
if (transition) {
|
||||
this._insertTransistionRule();
|
||||
this._insertTransistionRule(kind);
|
||||
}
|
||||
else {
|
||||
events.emit(this, "style-applied");
|
||||
events.emit(this, "style-applied", kind, this);
|
||||
}
|
||||
|
||||
this._getMediaRules().then((rules) => {
|
||||
@ -901,7 +933,7 @@ var StyleSheetActor = protocol.ActorClass({
|
||||
* Insert a catch-all transition rule into the document. Set a timeout
|
||||
* to remove the rule after a certain time.
|
||||
*/
|
||||
_insertTransistionRule: function() {
|
||||
_insertTransistionRule: function(kind) {
|
||||
this.document.documentElement.classList.add(TRANSITION_CLASS);
|
||||
|
||||
// We always add the rule since we've just reset all the rules
|
||||
@ -910,7 +942,7 @@ var StyleSheetActor = protocol.ActorClass({
|
||||
// Set up clean up and commit after transition duration (+buffer)
|
||||
// @see _onTransitionEnd
|
||||
this.window.clearTimeout(this._transitionTimeout);
|
||||
this._transitionTimeout = this.window.setTimeout(this._onTransitionEnd.bind(this),
|
||||
this._transitionTimeout = this.window.setTimeout(this._onTransitionEnd.bind(this, kind),
|
||||
TRANSITION_DURATION_MS + TRANSITION_BUFFER_MS);
|
||||
},
|
||||
|
||||
@ -918,7 +950,7 @@ var StyleSheetActor = protocol.ActorClass({
|
||||
* This cleans up class and rule added for transition effect and then
|
||||
* notifies that the style has been applied.
|
||||
*/
|
||||
_onTransitionEnd: function()
|
||||
_onTransitionEnd: function(kind)
|
||||
{
|
||||
this.document.documentElement.classList.remove(TRANSITION_CLASS);
|
||||
|
||||
@ -928,7 +960,7 @@ var StyleSheetActor = protocol.ActorClass({
|
||||
this.rawSheet.deleteRule(index);
|
||||
}
|
||||
|
||||
events.emit(this, "style-applied");
|
||||
events.emit(this, "style-applied", kind, this);
|
||||
}
|
||||
})
|
||||
|
||||
@ -981,6 +1013,29 @@ var StyleSheetFront = protocol.FrontClass(StyleSheetActor, {
|
||||
},
|
||||
get ruleCount() {
|
||||
return this._form.ruleCount;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the indentation to use for edits to this style sheet.
|
||||
*
|
||||
* @return {Promise} A promise that will resolve to a string that
|
||||
* should be used to indent a block in this style sheet.
|
||||
*/
|
||||
guessIndentation: function() {
|
||||
let prefIndent = getIndentationFromPrefs();
|
||||
if (prefIndent) {
|
||||
let {indentUnit, indentWithTabs} = prefIndent;
|
||||
return promise.resolve(indentWithTabs ? "\t" : " ".repeat(indentUnit));
|
||||
}
|
||||
|
||||
return Task.spawn(function*() {
|
||||
let longStr = yield this.getText();
|
||||
let source = yield longStr.string();
|
||||
|
||||
let {indentUnit, indentWithTabs} = getIndentationFromString(source);
|
||||
|
||||
return indentWithTabs ? "\t" : " ".repeat(indentUnit);
|
||||
}.bind(this));
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -52,13 +52,13 @@ addTest(function modifyProperties() {
|
||||
let changes = elementStyle.startModifyingProperties();
|
||||
|
||||
// Change an existing property...
|
||||
changes.setProperty("color", "black");
|
||||
changes.setProperty(-1, "color", "black");
|
||||
// Create a new property
|
||||
changes.setProperty("background-color", "green");
|
||||
changes.setProperty(-1, "background-color", "green");
|
||||
|
||||
// Create a new property and then change it immediately.
|
||||
changes.setProperty("border", "1px solid black");
|
||||
changes.setProperty("border", "2px solid black");
|
||||
changes.setProperty(-1, "border", "1px solid black");
|
||||
changes.setProperty(-1, "border", "2px solid black");
|
||||
|
||||
return changes.apply();
|
||||
}).then(() => {
|
||||
@ -67,9 +67,9 @@ addTest(function modifyProperties() {
|
||||
|
||||
// Remove all the properties
|
||||
let changes = elementStyle.startModifyingProperties();
|
||||
changes.removeProperty("color");
|
||||
changes.removeProperty("background-color");
|
||||
changes.removeProperty("border");
|
||||
changes.removeProperty(-1, "color");
|
||||
changes.removeProperty(-1, "background-color");
|
||||
changes.removeProperty(-1, "border");
|
||||
|
||||
return changes.apply();
|
||||
}).then(() => {
|
||||
|
@ -853,7 +853,7 @@ pref("devtools.debugger.forbid-certified-apps", true);
|
||||
pref("devtools.apps.forbidden-permissions", "embed-apps,engineering-mode,embed-widgets");
|
||||
|
||||
// DevTools default color unit
|
||||
pref("devtools.defaultColorUnit", "hex");
|
||||
pref("devtools.defaultColorUnit", "authored");
|
||||
|
||||
// Used for devtools debugging
|
||||
pref("devtools.dump.emit", false);
|
||||
|
Loading…
Reference in New Issue
Block a user