Bug 1136257 - Shift-click to switch between color unit format in place. r=bgrins

This commit is contained in:
Michael Ratcliffe 2015-04-17 11:53:06 +01:00
parent 73419b4b95
commit 71f47313f9
9 changed files with 264 additions and 51 deletions

View File

@ -1,7 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const COLOR_UNIT_PREF = "devtools.defaultColorUnit";
const TEST_URI = "data:text/html;charset=utf-8,browser_css_color.js";
let {colorUtils} = devtools.require("devtools/css-color");
let origColorUnit;
@ -9,7 +8,6 @@ let origColorUnit;
add_task(function*() {
yield promiseTab("about:blank");
let [host, win, doc] = yield createHost("bottom", TEST_URI);
origColorUnit = Services.prefs.getCharPref(COLOR_UNIT_PREF);
info("Creating a test canvas element to test colors");
let canvas = createTestCanvas(doc);
@ -49,23 +47,19 @@ function testColorUtils(canvas) {
}
function testToString(color, name, hex, hsl, rgb) {
switchColorUnit(colorUtils.CssColor.COLORUNIT.name);
color.colorUnit = colorUtils.CssColor.COLORUNIT.name;
is(color.toString(), name, "toString() with authored type");
switchColorUnit(colorUtils.CssColor.COLORUNIT.hex);
color.colorUnit = colorUtils.CssColor.COLORUNIT.hex;
is(color.toString(), hex, "toString() with hex type");
switchColorUnit(colorUtils.CssColor.COLORUNIT.hsl);
color.colorUnit = colorUtils.CssColor.COLORUNIT.hsl;
is(color.toString(), hsl, "toString() with hsl type");
switchColorUnit(colorUtils.CssColor.COLORUNIT.rgb);
color.colorUnit = colorUtils.CssColor.COLORUNIT.rgb;
is(color.toString(), rgb, "toString() with rgb type");
}
function switchColorUnit(unit) {
Services.prefs.setCharPref(COLOR_UNIT_PREF, unit);
}
function testColorMatch(name, hex, hsl, rgb, rgba, canvas) {
let target;
let ctx = canvas.getContext("2d");
@ -110,7 +104,6 @@ function testColorMatch(name, hex, hsl, rgb, rgba, canvas) {
test(hex, "hex");
test(hsl, "hsl");
test(rgb, "rgb");
switchColorUnit(origColorUnit);
}
function testProcessCSSString() {

View File

@ -1020,6 +1020,11 @@ SwatchBasedEditorTooltip.prototype = {
_onSwatchClick: function(event) {
let swatch = this.swatches.get(event.target);
if (event.shiftKey) {
event.stopPropagation();
return;
}
if (swatch) {
this.activeSwatch = event.target;
this.show();

View File

@ -980,7 +980,6 @@ function PropertyView(aTree, aName)
this.link = "https://developer.mozilla.org/CSS/" + aName;
this.templateMatchedSelectors = aTree.styleDocument.getElementById("templateMatchedSelectors");
this._propertyInfo = new PropertyInfo(aTree, aName);
}
@ -1192,6 +1191,7 @@ PropertyView.prototype = {
this.propertyInfo.value,
{
colorSwatchClass: "computedview-colorswatch",
colorClass: "computedview-color",
urlClass: "theme-link"
// No need to use baseURI here as computed URIs are never relative.
});
@ -1222,9 +1222,10 @@ PropertyView.prototype = {
}
this._matchedSelectorResponse = matched;
CssHtmlTree.processTemplate(this.templateMatchedSelectors,
this.matchedSelectorsContainer, this);
this._buildMatchedSelectors();
this.matchedExpander.setAttribute("open", "");
this.tree.inspector.emit("computed-view-property-expanded");
}).then(null, console.error);
} else {
@ -1240,6 +1241,40 @@ PropertyView.prototype = {
return this._matchedSelectorResponse;
},
_buildMatchedSelectors: function() {
let frag = this.element.ownerDocument.createDocumentFragment();
for (let selector of this.matchedSelectorViews) {
let p = createChild(frag, "p");
let span = createChild(p, "span", {
class: "rule-link"
});
let link = createChild(span, "a", {
target: "_blank",
class: "link theme-link",
title: selector.href,
sourcelocation: selector.source,
tabindex: "0",
textContent: selector.source
});
link.addEventListener("click", selector.openStyleEditor, false);
link.addEventListener("keydown", selector.maybeOpenStyleEditor, false);
let status = createChild(p, "span", {
dir: "ltr",
class: "rule-text theme-fg-color3 " + selector.statusClass,
title: selector.statusText,
textContent: selector.sourceText
});
let valueSpan = createChild(status, "span", {
class: "other-property-value theme-fg-color1"
});
valueSpan.appendChild(selector.outputFragment);
}
this.matchedSelectorsContainer.appendChild(frag);
},
/**
* Provide access to the matched SelectorViews that we are currently
* displaying.
@ -1279,6 +1314,9 @@ PropertyView.prototype = {
*/
onMatchedToggle: function PropertyView_onMatchedToggle(aEvent)
{
if (aEvent.shiftKey) {
return;
}
this.matchedExpanded = !this.matchedExpanded;
this.refreshMatchedSelectors();
aEvent.preventDefault();
@ -1328,6 +1366,9 @@ function SelectorView(aTree, aSelectorInfo)
this.selectorInfo = aSelectorInfo;
this._cacheStatusNames();
this.openStyleEditor = this.openStyleEditor.bind(this);
this.maybeOpenStyleEditor = this.maybeOpenStyleEditor.bind(this);
this.updateSourceLink();
}
@ -1418,6 +1459,7 @@ SelectorView.prototype = {
this.selectorInfo.name,
this.selectorInfo.value, {
colorSwatchClass: "computedview-colorswatch",
colorClass: "computedview-color",
urlClass: "theme-link",
baseURI: this.selectorInfo.rule.href
});
@ -1540,5 +1582,32 @@ SelectorView.prototype = {
}
};
/**
* Create a child element with a set of attributes.
*
* @param {Element} aParent
* The parent node.
* @param {string} aTag
* The tag name.
* @param {object} aAttributes
* A set of attributes to set on the node.
*/
function createChild(aParent, aTag, aAttributes={}) {
let elt = aParent.ownerDocument.createElementNS(HTML_NS, aTag);
for (let attr in 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]);
}
}
}
aParent.appendChild(elt);
return elt;
}
exports.CssHtmlTree = CssHtmlTree;
exports.PropertyView = PropertyView;

View File

@ -72,38 +72,5 @@
&noPropertiesFound;
</div>
<!--
To visually debug the templates without running firefox, alter the display:none
-->
<div style="display:none;">
<!--
A templateMatchedSelectors sits inside each templateProperties showing the
list of selectors that affect that property. Each needs data like this:
{
matchedSelectorViews: ..., // from cssHtmlTree.propertyViews[name].matchedSelectorViews
}
This is a template so the parent does not need to be a table, except that
using a div as the parent causes the DOM to muck with the tr elements
-->
<div id="templateMatchedSelectors">
<loop foreach="selector in ${matchedSelectorViews}">
<p>
<span class="rule-link">
<a target="_blank" class="link theme-link"
onclick="${selector.openStyleEditor}"
onkeydown="${selector.maybeOpenStyleEditor}"
title="${selector.href}"
sourcelocation="${selector.source}"
tabindex="0">${selector.source}</a>
</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.outputFragment}</span>
</span>
</p>
</loop>
</div>
</div>
</body>
</html>

View File

@ -27,6 +27,7 @@ support-files =
head.js
[browser_computedview_browser-styles.js]
[browser_computedview_cycle_color.js]
[browser_computedview_getNodeInfo.js]
[browser_computedview_keybindings_01.js]
[browser_computedview_keybindings_02.js]
@ -99,6 +100,7 @@ skip-if = (os == "win" && debug) || e10s # bug 963492: win. bug 1040653: e10s.
[browser_ruleview_multiple_properties_01.js]
[browser_ruleview_multiple_properties_02.js]
[browser_ruleview_original-source-link.js]
[browser_ruleview_cycle-color.js]
[browser_ruleview_override.js]
[browser_ruleview_pseudo-element_01.js]
[browser_ruleview_pseudo-element_02.js]

View File

@ -0,0 +1,68 @@
/* 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";
// Computed view color cycling test.
const PAGE_CONTENT = [
"<style type=\"text/css\">",
".matches {color: #F00;}</style>",
"<span id=\"matches\" class=\"matches\">Some styled text</span>",
"</div>"
].join("\n");
add_task(function*() {
yield addTab("data:text/html;charset=utf-8," +
"Computed view color cycling test.");
content.document.body.innerHTML = PAGE_CONTENT;
info("Opening the computed view");
let {toolbox, inspector, view} = yield openComputedView();
info("Selecting the test node");
yield selectNode("#matches", inspector);
info("Checking the property itself");
let container = getComputedViewPropertyView(view, "color").valueNode;
checkColorCycling(container, inspector);
info("Checking matched selectors");
container = yield getComputedViewMatchedRules(view, "color");
checkColorCycling(container, inspector);
});
function checkColorCycling(container, inspector) {
let swatch = container.querySelector(".computedview-colorswatch");
let valueNode = container.querySelector(".computedview-color");
let win = inspector.sidebar.getWindowForTab("computedview");
// Hex (default)
is(valueNode.textContent, "#F00", "Color displayed as a hex value.");
// HSL
EventUtils.synthesizeMouseAtCenter(swatch, {type: "mousedown", shiftKey: true}, win);
is(valueNode.textContent, "hsl(0, 100%, 50%)",
"Color displayed as an HSL value.");
// RGB
EventUtils.synthesizeMouseAtCenter(swatch, {type: "mousedown", shiftKey: true}, win);
is(valueNode.textContent, "rgb(255, 0, 0)",
"Color displayed as an RGB value.");
// Color name
EventUtils.synthesizeMouseAtCenter(swatch, {type: "mousedown", shiftKey: true}, win);
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
EventUtils.synthesizeMouseAtCenter(swatch, {type: "mousedown", shiftKey: true}, win);
is(valueNode.textContent, "#F00",
"Color displayed as hex again.");
}

View File

@ -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";
// Test cycling color types in the rule view.
const PAGE_CONTENT = [
"<style type=\"text/css\">",
" body {",
" color: #F00;",
" }",
"</style>",
"Test cycling color types in the rule view!"
].join("\n");
add_task(function*() {
yield addTab("data:text/html;charset=utf-8,Test cycling color types in the " +
"rule view.");
content.document.body.innerHTML = PAGE_CONTENT;
let {toolbox, inspector, view} = yield openRuleView();
let container = getRuleViewProperty(view, "body", "color").valueSpan;
checkColorCycling(container, inspector);
});
function checkColorCycling(container, inspector) {
let swatch = container.querySelector(".ruleview-colorswatch");
let valueNode = container.querySelector(".ruleview-color");
let win = inspector.sidebar.getWindowForTab("ruleview");
// Hex (default)
is(valueNode.textContent, "#F00", "Color displayed as a hex value.");
// HSL
EventUtils.synthesizeMouseAtCenter(swatch, {type: "mousedown", shiftKey: true}, win);
is(valueNode.textContent, "hsl(0, 100%, 50%)",
"Color displayed as an HSL value.");
// RGB
EventUtils.synthesizeMouseAtCenter(swatch, {type: "mousedown", shiftKey: true}, win);
is(valueNode.textContent, "rgb(255, 0, 0)",
"Color displayed as an RGB value.");
// Color name
EventUtils.synthesizeMouseAtCenter(swatch, {type: "mousedown", shiftKey: true}, win);
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
EventUtils.synthesizeMouseAtCenter(swatch, {type: "mousedown", shiftKey: true}, win);
is(valueNode.textContent, "#F00",
"Color displayed as hex again.");
}

View File

@ -95,8 +95,22 @@ CssColor.COLORUNIT = {
};
CssColor.prototype = {
_colorUnit: null,
authored: null,
get colorUnit() {
if (this._colorUnit === null) {
let defaultUnit = Services.prefs.getCharPref(COLOR_UNIT_PREF);
this._colorUnit = CssColor.COLORUNIT[defaultUnit];
}
return this._colorUnit;
},
set colorUnit(unit) {
this._colorUnit = unit;
},
get hasAlpha() {
if (!this.valid) {
return false;
@ -269,15 +283,31 @@ CssColor.prototype = {
return this;
},
nextColorUnit: function() {
// Reorder the formats array to have the current format at the
// front so we can cycle through.
let formats = ["authored", "hex", "hsl", "rgb", "name"];
let putOnEnd = formats.splice(0, formats.indexOf(this.colorUnit));
formats = formats.concat(putOnEnd);
let currentDisplayedColor = this[formats[0]];
for (let format of formats) {
if (this[format].toLowerCase() !== currentDisplayedColor.toLowerCase()) {
this.colorUnit = CssColor.COLORUNIT[format];
break;
}
}
return this.toString();
},
/**
* Return a string representing a color of type defined in COLOR_UNIT_PREF.
*/
toString: function() {
let color;
let defaultUnit = Services.prefs.getCharPref(COLOR_UNIT_PREF);
let unit = CssColor.COLORUNIT[defaultUnit];
switch(unit) {
switch(this.colorUnit) {
case CssColor.COLORUNIT.authored:
color = this.authored;
break;

View File

@ -77,6 +77,8 @@ loader.lazyGetter(this, "REGEX_ALL_CSS_PROPERTIES", function () {
*/
function OutputParser() {
this.parsed = [];
this.colorSwatches = new WeakMap();
this._onSwatchMouseDown = this._onSwatchMouseDown.bind(this);
}
exports.OutputParser = OutputParser;
@ -396,12 +398,14 @@ OutputParser.prototype = {
class: options.colorSwatchClass,
style: "background-color:" + color
});
this.colorSwatches.set(swatch, colorObj);
swatch.addEventListener("mousedown", this._onSwatchMouseDown, false);
container.appendChild(swatch);
}
if (options.defaultColorType) {
color = colorObj.toString();
container.dataset["color"] = color;
container.dataset.color = color;
}
let value = this._createNode("span", {
@ -435,6 +439,21 @@ OutputParser.prototype = {
this.parsed.push(container);
},
_onSwatchMouseDown: function(event) {
// Prevent text selection in the case of shift-click or double-click.
event.preventDefault();
if (!event.shiftKey) {
return;
}
let swatch = event.target;
let color = this.colorSwatches.get(swatch);
let val = color.nextColorUnit();
swatch.nextElementSibling.textContent = val;
},
/**
* Append a URL to the output.
*