mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 918716 - Add color swatches to devtools output parser. r=jwalker
This commit is contained in:
parent
296761dbce
commit
053e5a3bfa
@ -18,6 +18,7 @@ const CONTAINER_FLASHING_DURATION = 500;
|
||||
const {UndoStack} = require("devtools/shared/undo");
|
||||
const {editableField, InplaceEditor} = require("devtools/shared/inplace-editor");
|
||||
const {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
|
||||
const {OutputParser} = require("devtools/output-parser");
|
||||
const promise = require("sdk/core/promise");
|
||||
|
||||
Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm");
|
||||
@ -55,6 +56,7 @@ function MarkupView(aInspector, aFrame, aControllerWindow) {
|
||||
this._frame = aFrame;
|
||||
this.doc = this._frame.contentDocument;
|
||||
this._elt = this.doc.querySelector("#root");
|
||||
this._outputParser = new OutputParser();
|
||||
|
||||
this.layoutHelpers = new LayoutHelpers(this.doc.defaultView);
|
||||
|
||||
@ -786,6 +788,8 @@ MarkupView.prototype = {
|
||||
this._frame.removeEventListener("focus", this._boundFocus, false);
|
||||
delete this._boundFocus;
|
||||
|
||||
delete this._outputParser;
|
||||
|
||||
if (this._boundUpdatePreview) {
|
||||
this._frame.contentWindow.removeEventListener("scroll",
|
||||
this._boundUpdatePreview, true);
|
||||
@ -1537,20 +1541,90 @@ ElementEditor.prototype = {
|
||||
|
||||
this.attrs[aAttr.name] = attr;
|
||||
|
||||
let collapsedValue;
|
||||
if (aAttr.value.match(COLLAPSE_DATA_URL_REGEX)) {
|
||||
collapsedValue = truncateString(aAttr.value, COLLAPSE_DATA_URL_LENGTH);
|
||||
}
|
||||
else {
|
||||
collapsedValue = truncateString(aAttr.value, COLLAPSE_ATTRIBUTE_LENGTH);
|
||||
}
|
||||
|
||||
name.textContent = aAttr.name;
|
||||
val.textContent = collapsedValue;
|
||||
|
||||
if (typeof aAttr.value !== "undefined") {
|
||||
let outputParser = this.markup._outputParser;
|
||||
let frag = outputParser.parseHTMLAttribute(aAttr.value);
|
||||
frag = this._truncateFrag(frag);
|
||||
val.appendChild(frag);
|
||||
}
|
||||
|
||||
return attr;
|
||||
},
|
||||
|
||||
/**
|
||||
* We truncate HTML attributes to a text length defined by
|
||||
* COLLAPSE_DATA_URL_LENGTH and COLLAPSE_ATTRIBUTE_LENGTH. Because we parse
|
||||
* text into document fragments we need to process each fragment and truncate
|
||||
* according to the fragment's textContent length.
|
||||
*
|
||||
* @param {DocumentFragment} frag
|
||||
* The fragment to truncate.
|
||||
* @return {[DocumentFragment]}
|
||||
* Truncated fragment.
|
||||
*/
|
||||
_truncateFrag: function(frag) {
|
||||
let chars = 0;
|
||||
let text = frag.textContent;
|
||||
let maxWidth = text.match(COLLAPSE_DATA_URL_REGEX) ?
|
||||
COLLAPSE_DATA_URL_LENGTH : COLLAPSE_ATTRIBUTE_LENGTH;
|
||||
let overBy = text.length - maxWidth;
|
||||
let children = frag.childNodes;
|
||||
let croppedNode = null;
|
||||
|
||||
if (overBy <= 0) {
|
||||
return frag;
|
||||
}
|
||||
|
||||
// For fragments containing only one single node we just need to truncate
|
||||
// frag.textContent.
|
||||
if (children.length === 1) {
|
||||
let length = text.length;
|
||||
let start = text.substr(0, maxWidth / 2);
|
||||
let end = text.substr(length - maxWidth / 2, length - 1);
|
||||
|
||||
frag.textContent = start + "…" + end;
|
||||
return frag;
|
||||
}
|
||||
|
||||
// First maxWidth / 2 chars plus …
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
let node = children[i];
|
||||
let text = node.textContent;
|
||||
|
||||
let numChars = text.length;
|
||||
if (chars + numChars > maxWidth / 2) {
|
||||
node.textContent = text.substr(0, chars + numChars - maxWidth / 2) + "…";
|
||||
croppedNode = node;
|
||||
break;
|
||||
} else {
|
||||
chars += numChars;
|
||||
}
|
||||
}
|
||||
|
||||
// Last maxWidth / two chars.
|
||||
chars = 0;
|
||||
for (let i = children.length - 1; i >= 0; i--) {
|
||||
let node = children[i];
|
||||
let text = node.textContent;
|
||||
|
||||
let numChars = text.length;
|
||||
if (chars + numChars > maxWidth / 2) {
|
||||
if (node !== croppedNode) {
|
||||
node.parentNode.removeChild(node);
|
||||
chars += numChars;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
chars += numChars;
|
||||
}
|
||||
}
|
||||
|
||||
return frag;
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse a user-entered attribute string and apply the resulting
|
||||
* attributes to the node. This operation is undoable.
|
||||
|
@ -139,7 +139,7 @@ function testProcessCSSString() {
|
||||
}
|
||||
|
||||
function finishUp() {
|
||||
Services = colorUtils.CssColor = Loader = null;
|
||||
Services = colorUtils = Loader = null;
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
}
|
||||
|
@ -11,7 +11,8 @@ let {CssLogic} = require("devtools/styleinspector/css-logic");
|
||||
let {ELEMENT_STYLE} = require("devtools/server/actors/styles");
|
||||
let promise = require("sdk/core/promise");
|
||||
let {EventEmitter} = require("devtools/shared/event-emitter");
|
||||
let {colorUtils} = require("devtools/css-color");
|
||||
|
||||
const {OutputParser} = require("devtools/output-parser");
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/PluralForm.jsm");
|
||||
@ -134,6 +135,8 @@ function CssHtmlTree(aStyleInspector, aPageStyle)
|
||||
this.pageStyle = aPageStyle;
|
||||
this.propertyViews = [];
|
||||
|
||||
this._outputParser = new OutputParser();
|
||||
|
||||
let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].
|
||||
getService(Ci.nsIXULChromeRegistry);
|
||||
this.getRTLAttr = chromeReg.isLocaleRTL("global") ? "rtl" : "ltr";
|
||||
@ -613,6 +616,8 @@ CssHtmlTree.prototype = {
|
||||
{
|
||||
delete this.viewedElement;
|
||||
|
||||
delete this._outputParser;
|
||||
|
||||
// Remove event listeners
|
||||
this.includeBrowserStylesCheckbox.removeEventListener("command",
|
||||
this.includeBrowserStylesChanged);
|
||||
@ -676,7 +681,7 @@ PropertyInfo.prototype = {
|
||||
get value() {
|
||||
if (this.tree._computed) {
|
||||
let value = this.tree._computed[this.name].value;
|
||||
return colorUtils.processCSSString(value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -872,7 +877,6 @@ PropertyView.prototype = {
|
||||
// it will be reachable via TABing
|
||||
this.valueNode.setAttribute("tabindex", "");
|
||||
this.valueNode.setAttribute("dir", "ltr");
|
||||
this.valueNode.textContent = this.valueNode.title = this.value;
|
||||
// Make it hand over the focus to the container
|
||||
this.valueNode.addEventListener("click", this.onFocus, false);
|
||||
this.element.appendChild(this.valueNode);
|
||||
@ -914,7 +918,16 @@ PropertyView.prototype = {
|
||||
}
|
||||
|
||||
this.tree.numVisibleProperties++;
|
||||
this.valueNode.textContent = this.valueNode.title = this.propertyInfo.value;
|
||||
|
||||
let outputParser = this.tree._outputParser;
|
||||
let frag = outputParser.parseCssProperty(this.propertyInfo.name,
|
||||
this.propertyInfo.value,
|
||||
{
|
||||
colorSwatchClass: "computedview-colorswatch"
|
||||
});
|
||||
this.valueNode.innerHTML = "";
|
||||
this.valueNode.appendChild(frag);
|
||||
|
||||
this.refreshMatchedSelectors();
|
||||
},
|
||||
|
||||
@ -1114,8 +1127,18 @@ SelectorView.prototype = {
|
||||
|
||||
get value()
|
||||
{
|
||||
let val = this.selectorInfo.value;
|
||||
return colorUtils.processCSSString(val);
|
||||
return this.selectorInfo.value;
|
||||
},
|
||||
|
||||
get outputFragment()
|
||||
{
|
||||
let outputParser = this.tree._outputParser;
|
||||
let frag = outputParser.parseCssProperty(
|
||||
this.selectorInfo.name,
|
||||
this.selectorInfo.value, {
|
||||
colorSwatchClass: "computedview-colorswatch"
|
||||
});
|
||||
return frag;
|
||||
},
|
||||
|
||||
maybeOpenStyleEditor: function(aEvent)
|
||||
|
@ -103,7 +103,7 @@
|
||||
</span>
|
||||
<span dir="ltr" class="rule-text ${selector.statusClass} theme-fg-color3" title="${selector.statusText}">
|
||||
${selector.sourceText}
|
||||
<span class="other-property-value theme-fg-color1">${selector.value}</span>
|
||||
<span class="other-property-value theme-fg-color1">${selector.outputFragment}</span>
|
||||
</span>
|
||||
</p>
|
||||
</loop>
|
||||
|
@ -12,9 +12,10 @@ const promise = require("sdk/core/promise");
|
||||
let {CssLogic} = require("devtools/styleinspector/css-logic");
|
||||
let {InplaceEditor, editableField, editableItem} = require("devtools/shared/inplace-editor");
|
||||
let {ELEMENT_STYLE, PSEUDO_ELEMENTS} = require("devtools/server/actors/styles");
|
||||
let {colorUtils} = require("devtools/css-color");
|
||||
let {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
|
||||
|
||||
const {OutputParser} = require("devtools/output-parser");
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
@ -912,7 +913,7 @@ function TextProperty(aRule, aName, aValue, aPriority)
|
||||
{
|
||||
this.rule = aRule;
|
||||
this.name = aName;
|
||||
this.value = colorUtils.processCSSString(aValue);
|
||||
this.value = aValue;
|
||||
this.priority = aPriority;
|
||||
this.enabled = true;
|
||||
this.updateComputed();
|
||||
@ -1047,6 +1048,8 @@ function CssRuleView(aDoc, aStore, aPageStyle)
|
||||
this.element.className = "ruleview devtools-monospace";
|
||||
this.element.flex = 1;
|
||||
|
||||
this._outputParser = new OutputParser();
|
||||
|
||||
this._buildContextMenu = this._buildContextMenu.bind(this);
|
||||
this._contextMenuUpdate = this._contextMenuUpdate.bind(this);
|
||||
this._onSelectAll = this._onSelectAll.bind(this);
|
||||
@ -1168,7 +1171,9 @@ CssRuleView.prototype = {
|
||||
text = target.value.substr(start, count);
|
||||
} else {
|
||||
let win = this.doc.defaultView;
|
||||
text = win.getSelection().toString();
|
||||
let selection = win.getSelection();
|
||||
debugger;
|
||||
text = selection.toString();
|
||||
|
||||
// Remove any double newlines.
|
||||
text = text.replace(/(\r?\n)\r?\n/g, "$1");
|
||||
@ -1214,6 +1219,8 @@ CssRuleView.prototype = {
|
||||
this.element.removeEventListener("copy", this._onCopy);
|
||||
delete this._onCopy;
|
||||
|
||||
delete this._outputParser;
|
||||
|
||||
// Remove context menu
|
||||
if (this._contextmenu) {
|
||||
// Destroy the Select All menuitem.
|
||||
@ -1939,6 +1946,15 @@ TextPropertyEditor.prototype = {
|
||||
if (this.prop.priority) {
|
||||
val += " !" + this.prop.priority;
|
||||
}
|
||||
|
||||
let store = this.prop.rule.elementStyle.store;
|
||||
let propDirty = store.userProperties.contains(this.prop.rule.style, name);
|
||||
if (propDirty) {
|
||||
this.element.setAttribute("dirty", "");
|
||||
} else {
|
||||
this.element.removeAttribute("dirty");
|
||||
}
|
||||
|
||||
// Treat URLs differently than other properties.
|
||||
// Allow the user to click a link to the resource and open it.
|
||||
let resourceURI = this.getResourceURI();
|
||||
@ -1966,15 +1982,13 @@ TextPropertyEditor.prototype = {
|
||||
|
||||
appendText(this.valueSpan, val.split(resourceURI)[1]);
|
||||
} else {
|
||||
this.valueSpan.textContent = val;
|
||||
}
|
||||
|
||||
let store = this.prop.rule.elementStyle.store;
|
||||
let propDirty = store.userProperties.contains(this.prop.rule.style, name);
|
||||
if (propDirty) {
|
||||
this.element.setAttribute("dirty", "");
|
||||
} else {
|
||||
this.element.removeAttribute("dirty");
|
||||
let outputParser = this.ruleEditor.ruleView._outputParser;
|
||||
let frag = outputParser.parseCssProperty(name, val, {
|
||||
colorSwatchClass: "ruleview-colorswatch",
|
||||
defaultColorType: !propDirty
|
||||
});
|
||||
this.valueSpan.innerHTML = "";
|
||||
this.valueSpan.appendChild(frag);
|
||||
}
|
||||
|
||||
// Populate the computed styles.
|
||||
@ -2021,10 +2035,18 @@ TextPropertyEditor.prototype = {
|
||||
});
|
||||
appendText(li, ": ");
|
||||
|
||||
let outputParser = this.ruleEditor.ruleView._outputParser;
|
||||
let frag = outputParser.parseCssProperty(
|
||||
computed.name, computed.value, {
|
||||
colorSwatchClass: "ruleview-colorswatch"
|
||||
}
|
||||
);
|
||||
|
||||
createChild(li, "span", {
|
||||
class: "ruleview-propertyvalue theme-fg-color1",
|
||||
textContent: computed.value
|
||||
child: frag
|
||||
});
|
||||
|
||||
appendText(li, ";");
|
||||
}
|
||||
|
||||
@ -2301,6 +2323,8 @@ function createChild(aParent, aTag, aAttributes)
|
||||
if (aAttributes.hasOwnProperty(attr)) {
|
||||
if (attr === "textContent") {
|
||||
elt.textContent = aAttributes[attr];
|
||||
} else if(attr === "child") {
|
||||
elt.appendChild(aAttributes[attr]);
|
||||
} else {
|
||||
elt.setAttribute(attr, aAttributes[attr]);
|
||||
}
|
||||
|
@ -58,3 +58,7 @@
|
||||
.ruleview-header.ruleview-expandable-header {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ruleview-colorswatch {
|
||||
display: inline-block;
|
||||
}
|
||||
|
@ -20,8 +20,8 @@ let originalValue = "#00F";
|
||||
// }
|
||||
let testData = [
|
||||
{value: "red", commitKey: "VK_ESCAPE", modifiers: {}, expected: originalValue},
|
||||
{value: "red", commitKey: "VK_RETURN", modifiers: {}, expected: "red"},
|
||||
{value: "blue", commitKey: "VK_TAB", modifiers: {shiftKey: true}, expected: "blue"}
|
||||
{value: "red", commitKey: "VK_RETURN", modifiers: {}, expected: "#F00"},
|
||||
{value: "blue", commitKey: "VK_TAB", modifiers: {shiftKey: true}, expected: "#00F"}
|
||||
];
|
||||
|
||||
function startTests()
|
||||
@ -61,7 +61,7 @@ function runTestData(index)
|
||||
}
|
||||
EventUtils.synthesizeKey(testData[index].commitKey, testData[index].modifiers);
|
||||
|
||||
is(propEditor.valueSpan.innerHTML, testData[index].expected);
|
||||
is(propEditor.valueSpan.textContent, testData[index].expected);
|
||||
|
||||
runTestData(index + 1);
|
||||
});
|
||||
|
@ -20,17 +20,17 @@ function simpleOverride(aInspector, aRuleView)
|
||||
is(elementRule.textProps[1], secondProp, "Rules should be in addition order.");
|
||||
|
||||
promiseDone(elementRule._applyingModifications.then(() => {
|
||||
is(element.style.getPropertyValue("background-color"), "rgb(0, 0, 255)", "Second property should have been used.");
|
||||
is(element.style.getPropertyValue("background-color"), "blue", "Second property should have been used.");
|
||||
|
||||
secondProp.remove();
|
||||
return elementRule._applyingModifications;
|
||||
}).then(() => {
|
||||
is(element.style.getPropertyValue("background-color"), "rgb(0, 128, 0)", "After deleting second property, first should be used.");
|
||||
is(element.style.getPropertyValue("background-color"), "green", "After deleting second property, first should be used.");
|
||||
|
||||
secondProp = elementRule.createProperty("background-color", "blue", "");
|
||||
return elementRule._applyingModifications;
|
||||
}).then(() => {
|
||||
is(element.style.getPropertyValue("background-color"), "rgb(0, 0, 255)", "New property should be used.");
|
||||
is(element.style.getPropertyValue("background-color"), "blue", "New property should be used.");
|
||||
|
||||
is(elementRule.textProps[0], firstProp, "Rules shouldn't have switched places.");
|
||||
is(elementRule.textProps[1], secondProp, "Rules shouldn't have switched places.");
|
||||
@ -38,7 +38,7 @@ function simpleOverride(aInspector, aRuleView)
|
||||
secondProp.setEnabled(false);
|
||||
return elementRule._applyingModifications;
|
||||
}).then(() => {
|
||||
is(element.style.getPropertyValue("background-color"), "rgb(0, 128, 0)", "After disabling second property, first value should be used");
|
||||
is(element.style.getPropertyValue("background-color"), "green", "After disabling second property, first value should be used");
|
||||
|
||||
firstProp.setEnabled(false);
|
||||
return elementRule._applyingModifications;
|
||||
@ -48,19 +48,19 @@ function simpleOverride(aInspector, aRuleView)
|
||||
secondProp.setEnabled(true);
|
||||
return elementRule._applyingModifications;
|
||||
}).then(() => {
|
||||
is(element.style.getPropertyValue("background-color"), "rgb(0, 0, 255)", "Value should be set correctly after re-enabling");
|
||||
is(element.style.getPropertyValue("background-color"), "blue", "Value should be set correctly after re-enabling");
|
||||
|
||||
firstProp.setEnabled(true);
|
||||
return elementRule._applyingModifications;
|
||||
}).then(() => {
|
||||
is(element.style.getPropertyValue("background-color"), "rgb(0, 0, 255)", "Re-enabling an earlier property shouldn't make it override a later property.");
|
||||
is(element.style.getPropertyValue("background-color"), "blue", "Re-enabling an earlier property shouldn't make it override a later property.");
|
||||
is(elementRule.textProps[0], firstProp, "Rules shouldn't have switched places.");
|
||||
is(elementRule.textProps[1], secondProp, "Rules shouldn't have switched places.");
|
||||
|
||||
firstProp.setValue("purple", "");
|
||||
return elementRule._applyingModifications;
|
||||
}).then(() => {
|
||||
is(element.style.getPropertyValue("background-color"), "rgb(0, 0, 255)", "Modifying an earlier property shouldn't override a later property.");
|
||||
is(element.style.getPropertyValue("background-color"), "blue", "Modifying an earlier property shouldn't override a later property.");
|
||||
finishTest();
|
||||
}));
|
||||
});
|
||||
|
@ -67,7 +67,7 @@ function testTopLeft()
|
||||
is
|
||||
(
|
||||
convertTextPropsToString(elementAfterRule.textProps),
|
||||
"background: none repeat scroll 0% 0% #F00; content: \" \"; position: absolute; " +
|
||||
"background: none repeat scroll 0% 0% red; content: \" \"; position: absolute; " +
|
||||
"border-radius: 50%; height: 32px; width: 32px; top: 50%; left: 50%; margin-top: -16px; margin-left: -16px",
|
||||
"TopLeft after properties are correct"
|
||||
);
|
||||
@ -242,7 +242,7 @@ function testParagraph()
|
||||
is
|
||||
(
|
||||
convertTextPropsToString(elementFirstLineRule.textProps),
|
||||
"background: none repeat scroll 0% 0% #00F",
|
||||
"background: none repeat scroll 0% 0% blue",
|
||||
"Paragraph first-line properties are correct"
|
||||
);
|
||||
|
||||
@ -254,7 +254,7 @@ function testParagraph()
|
||||
is
|
||||
(
|
||||
convertTextPropsToString(elementFirstLetterRule.textProps),
|
||||
"color: #F00; font-size: 130%",
|
||||
"color: red; font-size: 130%",
|
||||
"Paragraph first-letter properties are correct"
|
||||
);
|
||||
|
||||
@ -266,7 +266,7 @@ function testParagraph()
|
||||
is
|
||||
(
|
||||
convertTextPropsToString(elementSelectionRule.textProps),
|
||||
"color: #FFF; background: none repeat scroll 0% 0% #000",
|
||||
"color: white; background: none repeat scroll 0% 0% black",
|
||||
"Paragraph first-letter properties are correct"
|
||||
);
|
||||
|
||||
|
@ -147,3 +147,12 @@ body {
|
||||
cursor: pointer;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.computedview-colorswatch {
|
||||
display: inline-block;
|
||||
border-radius: 50%;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: text-top;
|
||||
-moz-margin-end: 5px;
|
||||
}
|
||||
|
@ -96,6 +96,14 @@
|
||||
-moz-margin-start: 35px;
|
||||
}
|
||||
|
||||
.ruleview-colorswatch {
|
||||
border-radius: 50%;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: text-top;
|
||||
-moz-margin-end: 5px;
|
||||
}
|
||||
|
||||
.ruleview-overridden {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
@ -165,3 +165,12 @@ body {
|
||||
cursor: pointer;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.computedview-colorswatch {
|
||||
display: inline-block;
|
||||
border-radius: 50%;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: text-top;
|
||||
-moz-margin-end: 5px;
|
||||
}
|
||||
|
@ -100,6 +100,14 @@
|
||||
-moz-margin-start: 35px;
|
||||
}
|
||||
|
||||
.ruleview-colorswatch {
|
||||
border-radius: 50%;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: text-top;
|
||||
-moz-margin-end: 5px;
|
||||
}
|
||||
|
||||
.ruleview-overridden {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
@ -104,3 +104,9 @@
|
||||
.theme-fg-contrast { /* To be used for text on theme-bg-contrast */
|
||||
color: black;
|
||||
}
|
||||
|
||||
.ruleview-colorswatch,
|
||||
.computedview-colorswatch,
|
||||
.markupview-colorswatch {
|
||||
box-shadow: 0 0 0 1px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
@ -104,3 +104,9 @@
|
||||
.theme-fg-contrast { /* To be used for text on theme-bg-contrast */
|
||||
color: black;
|
||||
}
|
||||
|
||||
.ruleview-colorswatch,
|
||||
.computedview-colorswatch,
|
||||
.markupview-colorswatch {
|
||||
box-shadow: 0 0 0 1px #EFEFEF;
|
||||
}
|
||||
|
@ -165,3 +165,12 @@ body {
|
||||
cursor: pointer;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.computedview-colorswatch {
|
||||
display: inline-block;
|
||||
border-radius: 50%;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: text-top;
|
||||
-moz-margin-end: 5px;
|
||||
}
|
||||
|
@ -96,6 +96,14 @@
|
||||
-moz-margin-start: 35px;
|
||||
}
|
||||
|
||||
.ruleview-colorswatch {
|
||||
border-radius: 50%;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: text-top;
|
||||
-moz-margin-end: 5px;
|
||||
}
|
||||
|
||||
.ruleview-overridden {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
@ -60,6 +60,7 @@ var BuiltinProvider = {
|
||||
"devtools/app-actor-front": "resource://gre/modules/devtools/app-actor-front.js",
|
||||
"devtools/styleinspector/css-logic": "resource://gre/modules/devtools/styleinspector/css-logic",
|
||||
"devtools/css-color": "resource://gre/modules/devtools/css-color",
|
||||
"devtools/output-parser": "resource://gre/modules/devtools/output-parser",
|
||||
"devtools/touch-events": "resource://gre/modules/devtools/touch-events",
|
||||
"devtools/client": "resource://gre/modules/devtools/client",
|
||||
|
||||
|
@ -4,8 +4,10 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const COLOR_UNIT_PREF = "devtools.defaultColorUnit";
|
||||
const {Cc, Ci, Cu} = require("chrome");
|
||||
const {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||
|
||||
const COLOR_UNIT_PREF = "devtools.defaultColorUnit";
|
||||
|
||||
const REGEX_JUST_QUOTES = /^""$/;
|
||||
const REGEX_RGB_3_TUPLE = /^rgb\(([\d.]+),\s*([\d.]+),\s*([\d.]+)\)$/i;
|
||||
@ -36,14 +38,13 @@ const SPECIALVALUES = new Set([
|
||||
"unset"
|
||||
]);
|
||||
|
||||
let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||
|
||||
/**
|
||||
* This module is used to convert between various color types.
|
||||
*
|
||||
* Usage:
|
||||
* let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
* let {colorUtils} = devtools.require("devtools/css-color");
|
||||
* let color = new colorUtils.CssColor("red");
|
||||
*
|
||||
* color.authored === "red"
|
||||
* color.hasAlpha === false
|
||||
|
385
toolkit/devtools/output-parser.js
Normal file
385
toolkit/devtools/output-parser.js
Normal file
@ -0,0 +1,385 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {Cc, Ci, Cu} = require("chrome");
|
||||
const {colorUtils} = require("devtools/css-color");
|
||||
const {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||
|
||||
const REGEX_QUOTES = /^".*?"|^".*/;
|
||||
const REGEX_URL = /^url\(["']?(.+?)(?::(\d+))?["']?\)/;
|
||||
const REGEX_WHITESPACE = /^\s+/;
|
||||
const REGEX_FIRST_WORD_OR_CHAR = /^\w+|^./;
|
||||
const REGEX_CSS_PROPERTY_VALUE = /(^[^;]+)/;
|
||||
|
||||
/**
|
||||
* This regex matches:
|
||||
* - #F00
|
||||
* - #FF0000
|
||||
* - hsl()
|
||||
* - hsla()
|
||||
* - rgb()
|
||||
* - rgba()
|
||||
* - color names
|
||||
*/
|
||||
const REGEX_ALL_COLORS = /^#[0-9a-fA-F]{3}\b|^#[0-9a-fA-F]{6}\b|^hsl\(.*?\)|^hsla\(.*?\)|^rgba?\(.*?\)|^[a-zA-Z-]+/;
|
||||
|
||||
loader.lazyGetter(this, "DOMUtils", function () {
|
||||
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
|
||||
});
|
||||
|
||||
/**
|
||||
* This regular expression catches all css property names with their trailing
|
||||
* spaces and semicolon. This is used to ensure a value is valid for a property
|
||||
* name within style="" attributes.
|
||||
*/
|
||||
loader.lazyGetter(this, "REGEX_ALL_CSS_PROPERTIES", function () {
|
||||
let names = DOMUtils.getCSSPropertyNames();
|
||||
let pattern = "^(";
|
||||
|
||||
for (let i = 0; i < names.length; i++) {
|
||||
if (i > 0) {
|
||||
pattern += "|";
|
||||
}
|
||||
pattern += names[i];
|
||||
}
|
||||
pattern += ")\\s*:\\s*";
|
||||
|
||||
return new RegExp(pattern);
|
||||
});
|
||||
|
||||
/**
|
||||
* This module is used to process text for output by developer tools. This means
|
||||
* linking JS files with the debugger, CSS files with the style editor, JS
|
||||
* functions with the debugger, placing color swatches next to colors and
|
||||
* adding doorhanger previews where possible (images, angles, lengths,
|
||||
* border radius, cubic-bezier etc.).
|
||||
*
|
||||
* Usage:
|
||||
* const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
* const {OutputParser} = devtools.require("devtools/output-parser");
|
||||
*
|
||||
* let parser = new OutputParser();
|
||||
*
|
||||
* parser.parseCssProperty("color", "red"); // Returns document fragment.
|
||||
* parser.parseHTMLAttribute("color:red; font-size: 12px;"); // Returns document
|
||||
* // fragment.
|
||||
*/
|
||||
function OutputParser() {
|
||||
this.parsed = [];
|
||||
}
|
||||
|
||||
exports.OutputParser = OutputParser;
|
||||
|
||||
OutputParser.prototype = {
|
||||
/**
|
||||
* Parse a CSS property value given a property name.
|
||||
*
|
||||
* @param {String} name
|
||||
* CSS Property Name
|
||||
* @param {String} value
|
||||
* CSS Property value
|
||||
* @param {Object} [options]
|
||||
* Options object. For valid options and default values see
|
||||
* _mergeOptions().
|
||||
* @return {DocumentFragment}
|
||||
* A document fragment containing color swatches etc.
|
||||
*/
|
||||
parseCssProperty: function(name, value, options={}) {
|
||||
options = this._mergeOptions(options);
|
||||
options.cssPropertyName = name;
|
||||
|
||||
// Detect if "name" supports colors by checking if "papayawhip" is a valid
|
||||
// value.
|
||||
options.colors = this._cssPropertySupportsValue(name, "papayawhip");
|
||||
|
||||
return this._parse(value, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse a string.
|
||||
*
|
||||
* @param {String} value
|
||||
* Text to parse.
|
||||
* @param {Object} [options]
|
||||
* Options object. For valid options and default values see
|
||||
* _mergeOptions().
|
||||
* @return {DocumentFragment}
|
||||
* A document fragment containing events etc. Colors will not be
|
||||
* parsed.
|
||||
*/
|
||||
parseHTMLAttribute: function(value, options={}) {
|
||||
options = this._mergeOptions(options);
|
||||
options.colors = false;
|
||||
|
||||
return this._parse(value, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse a string.
|
||||
*
|
||||
* @param {String} text
|
||||
* Text to parse.
|
||||
* @param {Object} [options]
|
||||
* Options object. For valid options and default values see
|
||||
* _mergeOptions().
|
||||
* @return {DocumentFragment}
|
||||
* A document fragment containing events etc. Colors will not be
|
||||
* parsed.
|
||||
*/
|
||||
_parse: function(text, options={}) {
|
||||
text = text.trim();
|
||||
this.parsed.length = 0;
|
||||
let dirty = false;
|
||||
let matched = null;
|
||||
let nameValueSupported = false;
|
||||
|
||||
let trimMatchFromStart = function(match) {
|
||||
text = text.substr(match.length);
|
||||
dirty = true;
|
||||
matched = null;
|
||||
};
|
||||
|
||||
while (text.length > 0) {
|
||||
matched = text.match(REGEX_QUOTES);
|
||||
if (matched) {
|
||||
let match = matched[0];
|
||||
trimMatchFromStart(match);
|
||||
this._appendTextNode(match);
|
||||
}
|
||||
|
||||
matched = text.match(REGEX_WHITESPACE);
|
||||
if (matched) {
|
||||
let match = matched[0];
|
||||
trimMatchFromStart(match);
|
||||
this._appendTextNode(match);
|
||||
}
|
||||
|
||||
matched = text.match(REGEX_URL);
|
||||
if (matched) {
|
||||
let [match, url, line] = matched;
|
||||
trimMatchFromStart(match);
|
||||
this._appendURL(match, url, line, options);
|
||||
}
|
||||
|
||||
// This block checks for valid name and value combinations setting
|
||||
// nameValueSupported to true as appropriate.
|
||||
matched = text.match(REGEX_ALL_CSS_PROPERTIES);
|
||||
if (matched) {
|
||||
let [match, propertyName] = matched;
|
||||
trimMatchFromStart(match);
|
||||
this._appendTextNode(match);
|
||||
|
||||
matched = text.match(REGEX_CSS_PROPERTY_VALUE);
|
||||
if (matched) {
|
||||
let [, value] = matched;
|
||||
nameValueSupported = this._cssPropertySupportsValue(propertyName, value);
|
||||
}
|
||||
}
|
||||
|
||||
// This block should only be used for CSS properties.
|
||||
// options.cssPropertyName is only set if the parse call comes from a CSS
|
||||
// tool containing either a name and value or a string with valid name and
|
||||
// value combinations.
|
||||
if (options.cssPropertyName || (!options.cssPropertyName && nameValueSupported)) {
|
||||
matched = text.match(REGEX_ALL_COLORS);
|
||||
if (matched) {
|
||||
let match = matched[0];
|
||||
trimMatchFromStart(match);
|
||||
this._appendColor(match, options);
|
||||
}
|
||||
|
||||
nameValueSupported = false;
|
||||
}
|
||||
|
||||
if (!dirty) {
|
||||
// This test must always be last as it indicates use of an unknown
|
||||
// character that needs to be removed to prevent infinite loops.
|
||||
matched = text.match(REGEX_FIRST_WORD_OR_CHAR);
|
||||
if (matched) {
|
||||
let match = matched[0];
|
||||
trimMatchFromStart(match);
|
||||
this._appendTextNode(match);
|
||||
nameValueSupported = false;
|
||||
}
|
||||
}
|
||||
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
return this._toDOM();
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if a CSS property supports a specific value.
|
||||
*
|
||||
* @param {String} propertyName
|
||||
* CSS Property name to check
|
||||
* @param {String} propertyValue
|
||||
* CSS Property value to check
|
||||
*/
|
||||
_cssPropertySupportsValue: function(propertyName, propertyValue) {
|
||||
let autoCompleteValues = DOMUtils.getCSSValuesForProperty(propertyName);
|
||||
|
||||
// Detect if propertyName supports colors by checking if papayawhip is a
|
||||
// valid value.
|
||||
if (autoCompleteValues.indexOf("papayawhip") !== -1) {
|
||||
return this._isColorValid(propertyValue);
|
||||
}
|
||||
|
||||
// For the rest we can trust autocomplete value matches.
|
||||
return autoCompleteValues.indexOf(propertyValue) !== -1;
|
||||
},
|
||||
|
||||
/**
|
||||
* Append a color to the output.
|
||||
*
|
||||
* @param {String} color
|
||||
* Color to append
|
||||
* @param {Object} [options]
|
||||
* Options object. For valid options and default values see
|
||||
* _mergeOptions().
|
||||
*/
|
||||
_appendColor: function(color, options={}) {
|
||||
if (options.colors && this._isColorValid(color)) {
|
||||
if (options.colorSwatchClass) {
|
||||
this._appendNode("span", {
|
||||
class: options.colorSwatchClass,
|
||||
style: "background-color:" + color
|
||||
});
|
||||
}
|
||||
if (options.defaultColorType) {
|
||||
color = new colorUtils.CssColor(color).toString();
|
||||
}
|
||||
}
|
||||
this._appendTextNode(color);
|
||||
},
|
||||
|
||||
/**
|
||||
* Append a URL to the output.
|
||||
*
|
||||
* @param {String} match
|
||||
* Complete match that may include "url(xxx)""
|
||||
* @param {String} url
|
||||
* Actual URL
|
||||
* @param {Number} line
|
||||
* Line number from URL e.g. http://blah:42
|
||||
* @param {Object} [options]
|
||||
* Options object. For valid options and default values see
|
||||
* _mergeOptions().
|
||||
*/
|
||||
_appendURL: function(match, url, line, options={}) {
|
||||
this._appendTextNode(match);
|
||||
},
|
||||
|
||||
/**
|
||||
* Append a node to the output.
|
||||
*
|
||||
* @param {String} tagName
|
||||
* Tag type e.g. "div"
|
||||
* @param {Object} attributes
|
||||
* e.g. {class: "someClass", style: "cursor:pointer"};
|
||||
* @param {String} [value]
|
||||
* If a value is included it will be appended as a text node inside
|
||||
* the tag. This is useful e.g. for span tags.
|
||||
*/
|
||||
_appendNode: function(tagName, attributes, value="") {
|
||||
let win = Services.appShell.hiddenDOMWindow;
|
||||
let doc = win.document;
|
||||
let node = doc.createElement(tagName);
|
||||
let attrs = Object.getOwnPropertyNames(attributes);
|
||||
|
||||
for (let attr of attrs) {
|
||||
node.setAttribute(attr, attributes[attr]);
|
||||
}
|
||||
|
||||
if (value) {
|
||||
let textNode = content.document.createTextNode(value);
|
||||
node.appendChild(textNode);
|
||||
}
|
||||
|
||||
this.parsed.push(node);
|
||||
},
|
||||
|
||||
/**
|
||||
* Append a text node to the output. If the previously output item was a text
|
||||
* node then we append the text to that node.
|
||||
*
|
||||
* @param {String} text
|
||||
* Text to append
|
||||
*/
|
||||
_appendTextNode: function(text) {
|
||||
let lastItem = this.parsed[this.parsed.length - 1];
|
||||
|
||||
if (typeof lastItem !== "undefined" && lastItem.nodeName === "#text") {
|
||||
lastItem.nodeValue += text;
|
||||
} else {
|
||||
let win = Services.appShell.hiddenDOMWindow;
|
||||
let doc = win.document;
|
||||
let textNode = doc.createTextNode(text);
|
||||
this.parsed.push(textNode);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Take all output and append it into a single DocumentFragment.
|
||||
*
|
||||
* @return {DocumentFragment}
|
||||
* Document Fragment
|
||||
*/
|
||||
_toDOM: function() {
|
||||
let win = Services.appShell.hiddenDOMWindow;
|
||||
let doc = win.document;
|
||||
let frag = doc.createDocumentFragment();
|
||||
|
||||
for (let item of this.parsed) {
|
||||
frag.appendChild(item);
|
||||
}
|
||||
|
||||
this.parsed.length = 0;
|
||||
return frag;
|
||||
},
|
||||
|
||||
/**
|
||||
* Check that a string represents a valid volor.
|
||||
*
|
||||
* @param {String} color
|
||||
* Color to check
|
||||
*/
|
||||
_isColorValid: function(color) {
|
||||
return new colorUtils.CssColor(color).valid;
|
||||
},
|
||||
|
||||
/**
|
||||
* Merges options objects. Default values are set here.
|
||||
*
|
||||
* @param {Object} overrides
|
||||
* The option values to override e.g. _mergeOptions({colors: false})
|
||||
*
|
||||
* Valid options are:
|
||||
* - colors: true // Allow processing of colors
|
||||
* - defaultColorType: true // Convert colors to the default type
|
||||
* // selected in the options panel.
|
||||
* - colorSwatchClass: "" // The class to use for color swatches.
|
||||
* - cssPropertyName: "" // Used by CSS tools. Passing in the
|
||||
* // property name allows appropriate
|
||||
* // processing of the property value.
|
||||
* @return {Object}
|
||||
* Overridden options object
|
||||
*/
|
||||
_mergeOptions: function(overrides) {
|
||||
let defaults = {
|
||||
colors: true,
|
||||
defaultColorType: true,
|
||||
colorSwatchClass: "",
|
||||
cssPropertyName: ""
|
||||
};
|
||||
|
||||
for (let item in overrides) {
|
||||
defaults[item] = overrides[item];
|
||||
}
|
||||
return defaults;
|
||||
},
|
||||
};
|
@ -232,6 +232,7 @@ var PageStyleActor = protocol.ActorClass({
|
||||
rule: rule,
|
||||
sourceText: this.getSelectorSource(selectorInfo, node.rawNode),
|
||||
selector: selectorInfo.selector.text,
|
||||
name: selectorInfo.property,
|
||||
value: selectorInfo.value,
|
||||
status: selectorInfo.status
|
||||
});
|
||||
@ -243,7 +244,7 @@ var PageStyleActor = protocol.ActorClass({
|
||||
matched: matched,
|
||||
rules: [...rules],
|
||||
sheets: [...sheets],
|
||||
}
|
||||
};
|
||||
}, {
|
||||
request: {
|
||||
node: Arg(0, "domnode"),
|
||||
|
@ -51,8 +51,6 @@ const RX_PSEUDO = /\s*:?:([\w-]+)(\(?\)?)\s*/g;
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
let {colorUtils} = require("devtools/css-color");
|
||||
|
||||
function CssLogic()
|
||||
{
|
||||
// The cache of examined CSS properties.
|
||||
@ -1614,7 +1612,7 @@ function CssSelectorInfo(aSelector, aProperty, aValue, aStatus)
|
||||
this.selector = aSelector;
|
||||
this.property = aProperty;
|
||||
this.status = aStatus;
|
||||
this.value = colorUtils.processCSSString(aValue);
|
||||
this.value = aValue;
|
||||
let priority = this.selector.cssRule.getPropertyPriority(this.property);
|
||||
this.important = (priority === "important");
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user