Bug 889638 - Color picker tooltip in the CSS rule view. r=harth

This commit is contained in:
Patrick Brosset
2013-11-15 21:46:59 -05:00
parent ddbf8b7295
commit a2c1894088
28 changed files with 1410 additions and 121 deletions

View File

@@ -15,6 +15,9 @@
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript;version=1.8"
src="chrome://browser/content/devtools/theme-switching.js"></script>
<script type="application/javascript"
src="chrome://global/content/viewSourceUtils.js"/>

View File

@@ -95,3 +95,5 @@ browser.jar:
content/browser/devtools/app-manager/index.js (app-manager/content/index.js)
content/browser/devtools/app-manager/help.xhtml (app-manager/content/help.xhtml)
content/browser/devtools/app-manager/manifest-editor.js (app-manager/content/manifest-editor.js)
content/browser/devtools/spectrum-frame.xhtml (shared/widgets/spectrum-frame.xhtml)
content/browser/devtools/spectrum.css (shared/widgets/spectrum.css)

View File

@@ -13,6 +13,9 @@
]>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript;version=1.8"
src="chrome://browser/content/devtools/theme-switching.js"></script>
<script type="application/javascript" src="shadereditor.js"/>
<vbox id="body" flex="1">

View File

@@ -30,3 +30,4 @@ support-files =
[browser_toolbar_tooltip.js]
[browser_toolbar_webconsole_errors_count.html]
[browser_toolbar_webconsole_errors_count.js]
[browser_spectrum.js]

View File

@@ -0,0 +1,122 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests that the spectrum color picker works correctly
const TEST_URI = "chrome://browser/content/devtools/spectrum-frame.xhtml";
const {Spectrum} = devtools.require("devtools/shared/widgets/Spectrum");
let doc;
function test() {
waitForExplicitFinish();
addTab(TEST_URI, () => {
doc = content.document;
startTests();
});
}
function endTests() {
doc = null;
gBrowser.removeCurrentTab();
finish();
}
function startTests() {
testCreateAndDestroyShouldAppendAndRemoveElements();
}
function testCreateAndDestroyShouldAppendAndRemoveElements() {
let containerElement = doc.querySelector("#spectrum");
ok(containerElement, "We have the root node to append spectrum to");
is(containerElement.childElementCount, 0, "Root node is empty");
let s = new Spectrum(containerElement, [255, 126, 255, 1]);
s.show();
ok(containerElement.childElementCount > 0, "Spectrum has appended elements");
s.destroy();
is(containerElement.childElementCount, 0, "Destroying spectrum removed all nodes");
testPassingAColorAtInitShouldSetThatColor();
}
function testPassingAColorAtInitShouldSetThatColor() {
let initRgba = [255, 126, 255, 1];
let s = new Spectrum(doc.querySelector("#spectrum"), initRgba);
s.show();
let setRgba = s.rgb;
is(initRgba[0], setRgba[0], "Spectrum initialized with the right color");
is(initRgba[1], setRgba[1], "Spectrum initialized with the right color");
is(initRgba[2], setRgba[2], "Spectrum initialized with the right color");
is(initRgba[3], setRgba[3], "Spectrum initialized with the right color");
s.destroy();
testSettingAndGettingANewColor();
}
function testSettingAndGettingANewColor() {
let s = new Spectrum(doc.querySelector("#spectrum"), [0, 0, 0, 1]);
s.show();
let colorToSet = [255, 255, 255, 1];
s.rgb = colorToSet;
let newColor = s.rgb;
is(colorToSet[0], newColor[0], "Spectrum set with the right color");
is(colorToSet[1], newColor[1], "Spectrum set with the right color");
is(colorToSet[2], newColor[2], "Spectrum set with the right color");
is(colorToSet[3], newColor[3], "Spectrum set with the right color");
s.destroy();
testChangingColorShouldEmitEvents();
}
function testChangingColorShouldEmitEvents() {
let s = new Spectrum(doc.querySelector("#spectrum"), [255, 255, 255, 1]);
s.show();
s.once("changed", (event, rgba, color) => {
EventUtils.sendMouseEvent({type: "mouseup"}, s.dragger, doc.defaultView);
ok(true, "Changed event was emitted on color change");
is(rgba[0], 128, "New color is correct");
is(rgba[1], 64, "New color is correct");
is(rgba[2], 64, "New color is correct");
is(rgba[3], 1, "New color is correct");
is("rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ", " + rgba[3] + ")", color, "RGBA and css color correspond");
s.destroy();
testSettingColorShoudUpdateTheUI();
});
executeSoon(() => {
EventUtils.synthesizeMouse(s.dragger, s.dragger.offsetWidth/2, s.dragger.offsetHeight/2, {}, content);
});
}
function testSettingColorShoudUpdateTheUI() {
let s = new Spectrum(doc.querySelector("#spectrum"), [255, 255, 255, 1]);
s.show();
let dragHelperOriginalPos = [s.dragHelper.style.top, s.dragHelper.style.left];
let alphaHelperOriginalPos = s.alphaSliderHelper.style.left;
s.rgb = [50, 240, 234, .2];
s.updateUI();
ok(s.alphaSliderHelper.style.left != alphaHelperOriginalPos, "Alpha helper has moved");
ok(s.dragHelper.style.top !== dragHelperOriginalPos[0], "Drag helper has moved");
ok(s.dragHelper.style.left !== dragHelperOriginalPos[1], "Drag helper has moved");
s.rgb = [240, 32, 124, 0];
s.updateUI();
is(s.alphaSliderHelper.style.left, - (s.alphaSliderHelper.offsetWidth/2) + "px",
"Alpha range UI has been updated again");
s.destroy();
executeSoon(endTests);
}

View File

@@ -0,0 +1,332 @@
/* 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 EventEmitter = require("devtools/shared/event-emitter");
/**
* Spectrum creates a color picker widget in any container you give it.
*
* Simple usage example:
*
* const {Spectrum} = require("devtools/shared/widgets/Spectrum");
* let s = new Spectrum(containerElement, [255, 126, 255, 1]);
* s.on("changed", (event, rgba, color) => {
* console.log("rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ", " + rgba[3] + ")");
* });
* s.show();
* s.destroy();
*
* Note that the color picker is hidden by default and you need to call show to
* make it appear. This 2 stages initialization helps in cases you are creating
* the color picker in a parent element that hasn't been appended anywhere yet
* or that is hidden. Calling show() when the parent element is appended and
* visible will allow spectrum to correctly initialize its various parts.
*
* Fires the following events:
* - changed : When the user changes the current color
*/
function Spectrum(parentEl, rgb) {
EventEmitter.decorate(this);
this.element = parentEl.ownerDocument.createElement('div');
this.parentEl = parentEl;
this.element.className = "spectrum-container";
this.element.innerHTML = [
"<div class='spectrum-top'>",
"<div class='spectrum-fill'></div>",
"<div class='spectrum-top-inner'>",
"<div class='spectrum-color spectrum-box'>",
"<div class='spectrum-sat'>",
"<div class='spectrum-val'>",
"<div class='spectrum-dragger'></div>",
"</div>",
"</div>",
"</div>",
"<div class='spectrum-hue spectrum-box'>",
"<div class='spectrum-slider spectrum-slider-control'></div>",
"</div>",
"</div>",
"</div>",
"<div class='spectrum-alpha spectrum-checker spectrum-box'>",
"<div class='spectrum-alpha-inner'>",
"<div class='spectrum-alpha-handle spectrum-slider-control'></div>",
"</div>",
"</div>",
].join("");
this.onElementClick = this.onElementClick.bind(this);
this.element.addEventListener("click", this.onElementClick, false);
this.parentEl.appendChild(this.element);
this.slider = this.element.querySelector(".spectrum-hue");
this.slideHelper = this.element.querySelector(".spectrum-slider");
Spectrum.draggable(this.slider, this.onSliderMove.bind(this));
this.dragger = this.element.querySelector(".spectrum-color");
this.dragHelper = this.element.querySelector(".spectrum-dragger");
Spectrum.draggable(this.dragger, this.onDraggerMove.bind(this));
this.alphaSlider = this.element.querySelector(".spectrum-alpha");
this.alphaSliderInner = this.element.querySelector(".spectrum-alpha-inner");
this.alphaSliderHelper = this.element.querySelector(".spectrum-alpha-handle");
Spectrum.draggable(this.alphaSliderInner, this.onAlphaSliderMove.bind(this));
if (rgb) {
this.rgb = rgb;
this.updateUI();
}
}
module.exports.Spectrum = Spectrum;
Spectrum.hsvToRgb = function(h, s, v, a) {
let r, g, b;
let i = Math.floor(h * 6);
let f = h * 6 - i;
let p = v * (1 - s);
let q = v * (1 - f * s);
let t = v * (1 - (1 - f) * s);
switch(i % 6) {
case 0: r = v, g = t, b = p; break;
case 1: r = q, g = v, b = p; break;
case 2: r = p, g = v, b = t; break;
case 3: r = p, g = q, b = v; break;
case 4: r = t, g = p, b = v; break;
case 5: r = v, g = p, b = q; break;
}
return [r * 255, g * 255, b * 255, a];
};
Spectrum.rgbToHsv = function(r, g, b, a) {
r = r / 255;
g = g / 255;
b = b / 255;
let max = Math.max(r, g, b), min = Math.min(r, g, b);
let h, s, v = max;
let d = max - min;
s = max == 0 ? 0 : d / max;
if(max == min) {
h = 0; // achromatic
}
else {
switch(max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return [h, s, v, a];
};
Spectrum.getOffset = function(el) {
let curleft = 0, curtop = 0;
if (el.offsetParent) {
do {
curleft += el.offsetLeft;
curtop += el.offsetTop;
} while (el = el.offsetParent);
}
return {
left: curleft,
top: curtop
};
};
Spectrum.draggable = function(element, onmove, onstart, onstop) {
onmove = onmove || function() {};
onstart = onstart || function() {};
onstop = onstop || function() {};
let doc = element.ownerDocument;
let dragging = false;
let offset = {};
let maxHeight = 0;
let maxWidth = 0;
function prevent(e) {
e.stopPropagation();
e.preventDefault();
}
function move(e) {
if (dragging) {
let pageX = e.pageX;
let pageY = e.pageY;
let dragX = Math.max(0, Math.min(pageX - offset.left, maxWidth));
let dragY = Math.max(0, Math.min(pageY - offset.top, maxHeight));
onmove.apply(element, [dragX, dragY]);
}
}
function start(e) {
let rightclick = e.which === 3;
if (!rightclick && !dragging) {
if (onstart.apply(element, arguments) !== false) {
dragging = true;
maxHeight = element.offsetHeight;
maxWidth = element.offsetWidth;
offset = Spectrum.getOffset(element);
move(e);
doc.addEventListener("selectstart", prevent, false);
doc.addEventListener("dragstart", prevent, false);
doc.addEventListener("mousemove", move, false);
doc.addEventListener("mouseup", stop, false);
prevent(e);
}
}
}
function stop() {
if (dragging) {
doc.removeEventListener("selectstart", prevent, false);
doc.removeEventListener("dragstart", prevent, false);
doc.removeEventListener("mousemove", move, false);
doc.removeEventListener("mouseup", stop, false);
onstop.apply(element, arguments);
}
dragging = false;
}
element.addEventListener("mousedown", start, false);
};
Spectrum.prototype = {
set rgb(color) {
this.hsv = Spectrum.rgbToHsv(color[0], color[1], color[2], color[3]);
},
get rgb() {
let rgb = Spectrum.hsvToRgb(this.hsv[0], this.hsv[1], this.hsv[2], this.hsv[3]);
return [Math.round(rgb[0]), Math.round(rgb[1]), Math.round(rgb[2]), Math.round(rgb[3]*100)/100];
},
get rgbNoSatVal() {
let rgb = Spectrum.hsvToRgb(this.hsv[0], 1, 1);
return [Math.round(rgb[0]), Math.round(rgb[1]), Math.round(rgb[2]), rgb[3]];
},
get rgbCssString() {
let rgb = this.rgb;
return "rgba(" + rgb[0] + ", " + rgb[1] + ", " + rgb[2] + ", " + rgb[3] + ")";
},
show: function() {
this.element.classList.add('spectrum-show');
this.slideHeight = this.slider.offsetHeight;
this.dragWidth = this.dragger.offsetWidth;
this.dragHeight = this.dragger.offsetHeight;
this.dragHelperHeight = this.dragHelper.offsetHeight;
this.slideHelperHeight = this.slideHelper.offsetHeight;
this.alphaSliderWidth = this.alphaSliderInner.offsetWidth;
this.alphaSliderHelperWidth = this.alphaSliderHelper.offsetWidth;
this.updateUI();
},
onElementClick: function(e) {
e.stopPropagation();
},
onSliderMove: function(dragX, dragY) {
this.hsv[0] = (dragY / this.slideHeight);
this.updateUI();
this.onChange();
},
onDraggerMove: function(dragX, dragY) {
this.hsv[1] = dragX / this.dragWidth;
this.hsv[2] = (this.dragHeight - dragY) / this.dragHeight;
this.updateUI();
this.onChange();
},
onAlphaSliderMove: function(dragX, dragY) {
this.hsv[3] = dragX / this.alphaSliderWidth;
this.updateUI();
this.onChange();
},
onChange: function() {
this.emit("changed", this.rgb, this.rgbCssString);
},
updateHelperLocations: function() {
let h = this.hsv[0];
let s = this.hsv[1];
let v = this.hsv[2];
// Placing the color dragger
let dragX = s * this.dragWidth;
let dragY = this.dragHeight - (v * this.dragHeight);
let helperDim = this.dragHelperHeight/2;
dragX = Math.max(
-helperDim,
Math.min(this.dragWidth - helperDim, dragX - helperDim)
);
dragY = Math.max(
-helperDim,
Math.min(this.dragHeight - helperDim, dragY - helperDim)
);
this.dragHelper.style.top = dragY + "px";
this.dragHelper.style.left = dragX + "px";
// Placing the hue slider
let slideY = (h * this.slideHeight) - this.slideHelperHeight/2;
this.slideHelper.style.top = slideY + "px";
// Placing the alpha slider
let alphaSliderX = (this.hsv[3] * this.alphaSliderWidth) - (this.alphaSliderHelperWidth / 2);
this.alphaSliderHelper.style.left = alphaSliderX + "px";
},
updateUI: function() {
this.updateHelperLocations();
let rgb = this.rgb;
let rgbNoSatVal = this.rgbNoSatVal;
let flatColor = "rgb(" + rgbNoSatVal[0] + ", " + rgbNoSatVal[1] + ", " + rgbNoSatVal[2] + ")";
let fullColor = "rgba(" + rgb[0] + ", " + rgb[1] + ", " + rgb[2] + ", " + rgb[3] + ")";
this.dragger.style.backgroundColor = flatColor;
var rgbNoAlpha = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")";
var rgbAlpha0 = "rgba(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ", 0)";
var alphaGradient = "linear-gradient(to right, " + rgbAlpha0 + ", " + rgbNoAlpha + ")";
this.alphaSliderInner.style.background = alphaGradient;
},
destroy: function() {
this.element.removeEventListener("click", this.onElementClick, false);
this.parentEl.removeChild(this.element);
this.slider = null;
this.dragger = null;
this.alphaSlider = this.alphaSliderInner = this.alphaSliderHelper = null;
this.parentEl = null;
this.element = null;
}
};

View File

@@ -8,6 +8,10 @@ const {Cc, Cu, Ci} = require("chrome");
const promise = require("sdk/core/promise");
const IOService = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService);
const {Spectrum} = require("devtools/shared/widgets/Spectrum");
const EventEmitter = require("devtools/shared/event-emitter");
const {colorUtils} = require("devtools/css-color");
const Heritage = require("sdk/core/heritage");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
@@ -16,6 +20,10 @@ const GRADIENT_RE = /\b(repeating-)?(linear|radial)-gradient\(((rgb|hsl)a?\(.+?\
const BORDERCOLOR_RE = /^border-[-a-z]*color$/ig;
const BORDER_RE = /^border(-(top|bottom|left|right))?$/ig;
const BACKGROUND_IMAGE_RE = /url\([\'\"]?(.*?)[\'\"]?\)/;
const XHTML_NS = "http://www.w3.org/1999/xhtml";
const SPECTRUM_FRAME = "chrome://browser/content/devtools/spectrum-frame.xhtml";
const ESCAPE_KEYCODE = Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE;
const ENTER_KEYCODE = Ci.nsIDOMKeyEvent.DOM_VK_RETURN;
/**
* Tooltip widget.
@@ -38,23 +46,58 @@ const BACKGROUND_IMAGE_RE = /url\([\'\"]?(.*?)[\'\"]?\)/;
*/
/**
* The low level structure of a tooltip is a XUL element (a <panel>, although
* <tooltip> is supported too, it won't have the nice arrow shape).
* Container used for dealing with optional parameters.
*
* @param {Object} defaults
* An object with all default options {p1: v1, p2: v2, ...}
* @param {Object} options
* The actual values.
*/
function OptionsStore(defaults, options) {
this.defaults = defaults || {};
this.options = options || {};
}
OptionsStore.prototype = {
/**
* Get the value for a given option name.
* @return {Object} Returns the value for that option, coming either for the
* actual values that have been set in the constructor, or from the
* defaults if that options was not specified.
*/
get: function(name) {
if (typeof this.options[name] !== "undefined") {
return this.options[name];
} else {
return this.defaults[name];
}
}
};
/**
* The low level structure of a tooltip is a XUL element (a <panel>).
*/
let PanelFactory = {
get: function(doc, xulTag="panel") {
/**
* Get a new XUL panel instance.
* @param {XULDocument} doc
* The XUL document to put that panel into
* @param {OptionsStore} options
* An options store to get some configuration from
*/
get: function(doc, options) {
// Create the tooltip
let panel = doc.createElement(xulTag);
let panel = doc.createElement("panel");
panel.setAttribute("hidden", true);
panel.setAttribute("ignorekeys", true);
if (xulTag === "panel") {
// Prevent the click used to close the panel from being consumed
panel.setAttribute("consumeoutsideclicks", false);
panel.setAttribute("type", "arrow");
panel.setAttribute("level", "top");
}
// Prevent the click used to close the panel from being consumed
panel.setAttribute("consumeoutsideclicks", options.get("consumeOutsideClick"));
panel.setAttribute("noautofocus", options.get("noAutoFocus"));
panel.setAttribute("type", "arrow");
panel.setAttribute("level", "top");
panel.setAttribute("class", "devtools-tooltip devtools-tooltip-" + xulTag);
panel.setAttribute("class", "devtools-tooltip theme-tooltip-panel");
doc.querySelector("window").appendChild(panel);
return panel;
@@ -81,15 +124,57 @@ let PanelFactory = {
* });
* t.destroy();
*
* @param XULDocument doc
* @param {XULDocument} doc
* The XUL document hosting this tooltip
* @param {Object} options
* Optional options that give options to consumers
* - consumeOutsideClick {Boolean} Wether the first click outside of the
* tooltip should close the tooltip and be consumed or not.
* Defaults to false
* - closeOnKeys {Array} An array of key codes that should close the
* tooltip. Defaults to [27] (escape key)
* - noAutoFocus {Boolean} Should the focus automatically go to the panel
* when it opens. Defaults to true
*
* Fires these events:
* - showing : just before the tooltip shows
* - shown : when the tooltip is shown
* - hiding : just before the tooltip closes
* - hidden : when the tooltip gets hidden
* - keypress : when any key gets pressed, with keyCode
*/
function Tooltip(doc) {
function Tooltip(doc, options) {
EventEmitter.decorate(this);
this.doc = doc;
this.panel = PanelFactory.get(doc);
this.options = new OptionsStore({
consumeOutsideClick: false,
closeOnKeys: [ESCAPE_KEYCODE],
noAutoFocus: true
}, options);
this.panel = PanelFactory.get(doc, this.options);
// Used for namedTimeouts in the mouseover handling
this.uid = "tooltip-" + Date.now();
// Emit show/hide events
for (let event of ["shown", "hidden", "showing", "hiding"]) {
this["_onPopup" + event] = ((e) => {
return () => this.emit(e);
})(event);
this.panel.addEventListener("popup" + event,
this["_onPopup" + event], false);
}
// Listen to keypress events to close the tooltip if configured to do so
let win = this.doc.querySelector("window");
this._onKeyPress = event => {
this.emit("keypress", event.keyCode);
if (this.options.get("closeOnKeys").indexOf(event.keyCode) !== -1) {
this.hide();
}
};
win.addEventListener("keypress", this._onKeyPress, false);
}
module.exports.Tooltip = Tooltip;
@@ -102,7 +187,7 @@ Tooltip.prototype = {
/**
* Show the tooltip. It might be wise to append some content first if you
* don't want the tooltip to be empty. You may access the content of the
* tooltip by setting a XUL node to t.tooltip.content.
* tooltip by setting a XUL node to t.content.
* @param {node} anchor
* Which node should the tooltip be shown on
* @param {string} position
@@ -125,6 +210,10 @@ Tooltip.prototype = {
this.panel.hidePopup();
},
isShown: function() {
return this.panel.state !== "closed" && this.panel.state !== "hiding";
},
/**
* Empty the tooltip's content
*/
@@ -139,6 +228,15 @@ Tooltip.prototype = {
*/
destroy: function () {
this.hide();
for (let event of ["shown", "hidden", "showing", "hiding"]) {
this.panel.removeEventListener("popup" + event,
this["_onPopup" + event], false);
}
let win = this.doc.querySelector("window");
win.removeEventListener("keypress", this._onKeyPress, false);
this.content = null;
this.doc = null;
@@ -307,15 +405,6 @@ Tooltip.prototype = {
let vbox = this.doc.createElement("vbox");
vbox.setAttribute("align", "center")
// Transparency tiles (image will go in there)
let tiles = createTransparencyTiles(this.doc, vbox);
// Temporary label during image load
let label = this.doc.createElement("label");
label.classList.add("devtools-tooltip-caption");
label.textContent = l10n.strings.GetStringFromName("previewTooltip.image.brokenImage");
vbox.appendChild(label);
// Display the image
let image = this.doc.createElement("image");
image.setAttribute("src", imageUrl);
@@ -323,7 +412,14 @@ Tooltip.prototype = {
image.style.maxWidth = options.maxDim + "px";
image.style.maxHeight = options.maxDim + "px";
}
tiles.appendChild(image);
vbox.appendChild(image);
// Temporary label during image load
let label = this.doc.createElement("label");
label.classList.add("devtools-tooltip-caption");
label.classList.add("theme-comment");
label.textContent = l10n.strings.GetStringFromName("previewTooltip.image.brokenImage");
vbox.appendChild(label);
this.content = vbox;
@@ -353,71 +449,249 @@ Tooltip.prototype = {
}
},
setCssGradientContent: function(cssGradient) {
let tiles = createTransparencyTiles(this.doc);
/**
* Fill the tooltip with a new instance of the spectrum color picker widget
* initialized with the given color, and return a promise that resolves to
* the instance of spectrum
*/
setColorPickerContent: function(color) {
let def = promise.defer();
let gradientBox = this.doc.createElement("box");
gradientBox.width = "100";
gradientBox.height = "100";
gradientBox.style.background = this.cssGradient;
gradientBox.style.borderRadius = "2px";
gradientBox.style.boxShadow = "inset 0 0 4px #333";
// Create an iframe to contain spectrum
let iframe = this.doc.createElementNS(XHTML_NS, "iframe");
iframe.setAttribute("transparent", true);
iframe.setAttribute("width", "210");
iframe.setAttribute("height", "195");
iframe.setAttribute("flex", "1");
iframe.setAttribute("class", "devtools-tooltip-iframe");
tiles.appendChild(gradientBox)
let panel = this.panel;
let xulWin = this.doc.ownerGlobal;
this.content = tiles;
},
// Wait for the load to initialize spectrum
function onLoad() {
iframe.removeEventListener("load", onLoad, true);
let win = iframe.contentWindow.wrappedJSObject;
_setSimpleCssPropertiesContent: function(properties, width, height) {
let tiles = createTransparencyTiles(this.doc);
let container = win.document.getElementById("spectrum");
let spectrum = new Spectrum(container, color);
let box = this.doc.createElement("box");
box.width = width + "";
box.height = height + "";
properties.forEach(({name, value}) => {
box.style[name] = value;
});
tiles.appendChild(box);
// Finalize spectrum's init when the tooltip becomes visible
panel.addEventListener("popupshown", function shown() {
panel.removeEventListener("popupshown", shown, true);
spectrum.show();
def.resolve(spectrum);
}, true);
}
iframe.addEventListener("load", onLoad, true);
iframe.setAttribute("src", SPECTRUM_FRAME);
this.content = tiles;
},
// Put the iframe in the tooltip
this.content = iframe;
setCssColorContent: function(cssColor) {
this._setSimpleCssPropertiesContent([
{name: "background", value: cssColor},
{name: "borderRadius", value: "2px"},
{name: "boxShadow", value: "inset 0 0 4px #333"},
], 50, 50);
},
setCssBoxShadowContent: function(cssBoxShadow) {
this._setSimpleCssPropertiesContent([
{name: "background", value: "white"},
{name: "boxShadow", value: cssBoxShadow}
], 80, 80);
},
setCssBorderContent: function(cssBorder) {
this._setSimpleCssPropertiesContent([
{name: "background", value: "white"},
{name: "border", value: cssBorder}
], 80, 80);
return def.promise;
}
};
/**
* Internal utility function that creates a tiled background useful for
* displaying semi-transparent images
* Base class for all (color, gradient, ...)-swatch based value editors inside
* tooltips
*
* @param {XULDocument} doc
*/
function createTransparencyTiles(doc, parentEl) {
let tiles = doc.createElement("box");
tiles.classList.add("devtools-tooltip-tiles");
if (parentEl) {
parentEl.appendChild(tiles);
}
return tiles;
function SwatchBasedEditorTooltip(doc) {
// Creating a tooltip instance
// This one will consume outside clicks as it makes more sense to let the user
// close the tooltip by clicking out
// It will also close on <escape> and <enter>
this.tooltip = new Tooltip(doc, {
consumeOutsideClick: true,
closeOnKeys: [ESCAPE_KEYCODE, ENTER_KEYCODE],
noAutoFocus: false
});
// By default, swatch-based editor tooltips revert value change on <esc> and
// commit value change on <enter>
this._onTooltipKeypress = (event, code) => {
if (code === ESCAPE_KEYCODE) {
this.revert();
} else if (code === ENTER_KEYCODE) {
this.commit();
}
};
this.tooltip.on("keypress", this._onTooltipKeypress);
// All target swatches are kept in a map, indexed by swatch DOM elements
this.swatches = new Map();
// When a swatch is clicked, and for as long as the tooltip is shown, the
// activeSwatch property will hold the reference to the swatch DOM element
// that was clicked
this.activeSwatch = null;
this._onSwatchClick = this._onSwatchClick.bind(this);
}
SwatchBasedEditorTooltip.prototype = {
show: function() {
if (this.activeSwatch) {
this.tooltip.show(this.activeSwatch, "topcenter bottomleft");
}
},
hide: function() {
this.tooltip.hide();
},
/**
* Add a new swatch DOM element to the list of swatch elements this editor
* tooltip knows about. That means from now on, clicking on that swatch will
* toggle the editor.
*
* @param {node} swatchEl
* The element to add
* @param {object} callbacks
* Callbacks that will be executed when the editor wants to preview a
* value change, or revert a change, or commit a change.
* @param {object} originalValue
* The original value before the editor in the tooltip makes changes
* This can be of any type, and will be passed, as is, in the revert
* callback
*/
addSwatch: function(swatchEl, callbacks={}, originalValue) {
if (!callbacks.onPreview) callbacks.onPreview = function() {};
if (!callbacks.onRevert) callbacks.onRevert = function() {};
if (!callbacks.onCommit) callbacks.onCommit = function() {};
this.swatches.set(swatchEl, {
callbacks: callbacks,
originalValue: originalValue
});
swatchEl.addEventListener("click", this._onSwatchClick, false);
},
removeSwatch: function(swatchEl) {
if (this.swatches.has(swatchEl)) {
if (this.activeSwatch === swatchEl) {
this.hide();
this.activeSwatch = null;
}
swatchEl.removeEventListener("click", this._onSwatchClick, false);
this.swatches.delete(swatchEl);
}
},
_onSwatchClick: function(event) {
let swatch = this.swatches.get(event.target);
if (swatch) {
this.activeSwatch = event.target;
this.show();
event.stopPropagation();
}
},
/**
* Not called by this parent class, needs to be taken care of by sub-classes
*/
preview: function(value) {
if (this.activeSwatch) {
let swatch = this.swatches.get(this.activeSwatch);
swatch.callbacks.onPreview(value);
}
},
/**
* This parent class only calls this on <esc> keypress
*/
revert: function() {
if (this.activeSwatch) {
let swatch = this.swatches.get(this.activeSwatch);
swatch.callbacks.onRevert(swatch.originalValue);
}
},
/**
* This parent class only calls this on <enter> keypress
*/
commit: function() {
if (this.activeSwatch) {
let swatch = this.swatches.get(this.activeSwatch);
swatch.callbacks.onCommit();
}
},
destroy: function() {
this.swatches.clear();
this.activeSwatch = null;
this.tooltip.off("keypress", this._onTooltipKeypress);
this.tooltip.destroy();
}
};
/**
* The swatch color picker tooltip class is a specific class meant to be used
* along with output-parser's generated color swatches.
* It extends the parent SwatchBasedEditorTooltip class.
* It just wraps a standard Tooltip and sets its content with an instance of a
* color picker.
*
* @param {XULDocument} doc
*/
function SwatchColorPickerTooltip(doc) {
SwatchBasedEditorTooltip.call(this, doc);
// Creating a spectrum instance. this.spectrum will always be a promise that
// resolves to the spectrum instance
this.spectrum = this.tooltip.setColorPickerContent([0, 0, 0, 1]);
this._onSpectrumColorChange = this._onSpectrumColorChange.bind(this);
}
module.exports.SwatchColorPickerTooltip = SwatchColorPickerTooltip;
SwatchColorPickerTooltip.prototype = Heritage.extend(SwatchBasedEditorTooltip.prototype, {
/**
* Overriding the SwatchBasedEditorTooltip.show function to set spectrum's
* color.
*/
show: function() {
// Call then parent class' show function
SwatchBasedEditorTooltip.prototype.show.call(this);
// Then set spectrum's color and listen to color changes to preview them
if (this.activeSwatch) {
let swatch = this.swatches.get(this.activeSwatch);
let color = this.activeSwatch.style.backgroundColor;
this.spectrum.then(spectrum => {
spectrum.off("changed", this._onSpectrumColorChange);
spectrum.rgb = this._colorToRgba(color);
spectrum.on("changed", this._onSpectrumColorChange);
spectrum.updateUI();
});
}
},
_onSpectrumColorChange: function(event, rgba, cssColor) {
if (this.activeSwatch) {
this.activeSwatch.style.backgroundColor = cssColor;
this.activeSwatch.nextSibling.textContent = cssColor;
this.preview(cssColor);
}
},
_colorToRgba: function(color) {
color = new colorUtils.CssColor(color);
let rgba = color._getRGBATuple();
return [rgba.r, rgba.g, rgba.b, rgba.a];
},
destroy: function() {
SwatchBasedEditorTooltip.prototype.destroy.call(this);
this.spectrum.then(spectrum => {
spectrum.off("changed", this._onSpectrumColorChange);
spectrum.destroy();
});
}
});
/**
* Internal util, checks whether a css declaration is a gradient
*/

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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/. -->
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link rel="stylesheet" href="chrome://browser/skin/devtools/common.css" type="text/css"/>
<link rel="stylesheet" href="chrome://browser/content/devtools/spectrum.css" ype="text/css"/>
<script type="application/javascript;version=1.8" src="theme-switching.js"/>
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body role="application">
<div id="spectrum"></div>
</body>
</html>

View File

@@ -0,0 +1,139 @@
/* 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/. */
/* Mix-in classes */
.spectrum-checker {
background-color: #eee;
background-image: linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc),
linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc);
background-size: 12px 12px;
background-position: 0 0, 6px 6px;
}
.spectrum-slider-control {
cursor: pointer;
border: 1px solid black;
background: white;
opacity: .7;
}
.spectrum-box {
border: solid 1px #333;
}
/* Elements */
.spectrum-container {
position: relative;
display: none;
top: 0;
left: 0;
border-radius: 0;
width: 200px;
padding: 5px;
}
.spectrum-show {
display: inline-block;
}
/* Keep aspect ratio:
http://www.briangrinstead.com/blog/keep-aspect-ratio-with-html-and-css */
.spectrum-top {
position: relative;
width: 100%;
display: inline-block;
}
.spectrum-top-inner {
position: absolute;
top:0;
left:0;
bottom:0;
right:0;
}
.spectrum-color {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 20%;
}
.spectrum-hue {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 83%;
}
.spectrum-fill {
/* Same as spectrum-color width */
margin-top: 85%;
}
.spectrum-sat, .spectrum-val {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.spectrum-dragger, .spectrum-slider {
user-select: none;
}
.spectrum-alpha {
position: relative;
height: 8px;
margin-top: 3px;
}
.spectrum-alpha-inner {
height: 100%;
}
.spectrum-alpha-handle {
position: absolute;
top: -3px;
bottom: -3px;
width: 5px;
left: 50%;
}
.spectrum-sat {
background-image: -moz-linear-gradient(left, #FFF, rgba(204, 154, 129, 0));
}
.spectrum-val {
background-image: -moz-linear-gradient(bottom, #000000, rgba(204, 154, 129, 0));
}
.spectrum-hue {
background: -moz-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
}
.spectrum-dragger {
position: absolute;
top: 0px;
left: 0px;
cursor: pointer;
border-radius: 50%;
height: 8px;
width: 8px;
border: 1px solid white;
background: black;
}
.spectrum-slider {
position: absolute;
top: 0;
height: 5px;
left: -3px;
right: -3px;
}

View File

@@ -9,12 +9,11 @@
const {Cc, Ci, Cu} = require("chrome");
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 {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
let {Tooltip} = require("devtools/shared/widgets/Tooltip");
const {CssLogic} = require("devtools/styleinspector/css-logic");
const {InplaceEditor, editableField, editableItem} = require("devtools/shared/inplace-editor");
const {ELEMENT_STYLE, PSEUDO_ELEMENTS} = require("devtools/server/actors/styles");
const {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
const {Tooltip, SwatchColorPickerTooltip} = require("devtools/shared/widgets/Tooltip");
const {OutputParser} = require("devtools/output-parser");
Cu.import("resource://gre/modules/Services.jsm");
@@ -1072,8 +1071,14 @@ function CssRuleView(aInspector, aDoc, aStore, aPageStyle)
};
this.popup = new AutocompletePopup(aDoc.defaultView.parent.document, options);
this.tooltip = new Tooltip(this.inspector.panelDoc);
this.tooltip.startTogglingOnHover(this.element, this._buildTooltipContent.bind(this));
// Create a tooltip for previewing things in the rule view (images for now)
this.previewTooltip = new Tooltip(this.inspector.panelDoc);
this.previewTooltip.startTogglingOnHover(this.element,
this._buildTooltipContent.bind(this));
// Also create a more complex tooltip for editing colors with the spectrum
// color picker
this.colorPicker = new SwatchColorPickerTooltip(this.inspector.panelDoc);
this._buildContextMenu();
this._showEmpty();
@@ -1129,11 +1134,9 @@ CssRuleView.prototype = {
target = target.parentNode;
}
let isEditing = this.isEditing;
// If the inplace-editor is visible or if this is not a background image
// don't show the tooltip
if (this.isEditing || (!isImageHref && !isValueWithImage)) {
if (!isImageHref && !isValueWithImage) {
return false;
}
@@ -1142,7 +1145,12 @@ CssRuleView.prototype = {
let href = property.rule.domRule.href;
// Fill some content
this.tooltip.setCssBackgroundImageContent(property.value, href);
this.previewTooltip.setCssBackgroundImageContent(property.value, href);
// Hide the color picker tooltip if shown and revert changes
this.colorPicker.revert();
this.colorPicker.hide();
return true;
},
@@ -1238,7 +1246,8 @@ CssRuleView.prototype = {
* Return {bool} true if the rule view currently has an input editor visible.
*/
get isEditing() {
return this.element.querySelectorAll(".styleinspector-propertyeditor").length > 0;
return this.element.querySelectorAll(".styleinspector-propertyeditor").length > 0
|| this.colorPicker.tooltip.isShown();
},
_handlePrefChange: function(event, data) {
@@ -1280,8 +1289,9 @@ CssRuleView.prototype = {
// We manage the popupNode ourselves so we also need to destroy it.
this.doc.popupNode = null;
this.tooltip.stopTogglingOnHover(this.element);
this.tooltip.destroy();
this.previewTooltip.stopTogglingOnHover(this.element);
this.previewTooltip.destroy();
this.colorPicker.destroy();
if (this.element.parentNode) {
this.element.parentNode.removeChild(this.element);
@@ -1819,7 +1829,8 @@ TextPropertyEditor.prototype = {
* Boolean indicating if the name or value is being currently edited.
*/
get editing() {
return !!(this.nameSpan.inplaceEditor || this.valueSpan.inplaceEditor);
return !!(this.nameSpan.inplaceEditor || this.valueSpan.inplaceEditor ||
this.ruleEditor.ruleView.colorPicker.tooltip.isShown());
},
/**
@@ -2014,9 +2025,10 @@ TextPropertyEditor.prototype = {
this.element.removeAttribute("dirty");
}
let swatchClass = "ruleview-colorswatch";
let outputParser = this.ruleEditor.ruleView._outputParser;
let frag = outputParser.parseCssProperty(name, val, {
colorSwatchClass: "ruleview-colorswatch",
colorSwatchClass: swatchClass,
defaultColorType: !propDirty,
urlClass: "theme-link",
baseURI: this.sheetURI
@@ -2024,6 +2036,22 @@ TextPropertyEditor.prototype = {
this.valueSpan.innerHTML = "";
this.valueSpan.appendChild(frag);
// Attach the color picker tooltip to the color swatches
this._swatchSpans = this.valueSpan.querySelectorAll("." + swatchClass);
if (this._swatchSpans.length) {
for (let span of this._swatchSpans) {
// Capture the original declaration value to be able to revert later
let originalValue = this.valueSpan.textContent;
// Adding this swatch to the list of swatches our colorpicker knows
// about.
this.ruleEditor.ruleView.colorPicker.addSwatch(span, {
onPreview: () => this._livePreview(this.valueSpan.textContent),
onCommit: () => this._applyNewValue(this.valueSpan.textContent),
onRevert: () => this._applyNewValue(originalValue)
});
}
}
// Populate the computed styles.
this._updateComputed();
},
@@ -2166,6 +2194,12 @@ TextPropertyEditor.prototype = {
*/
remove: function TextPropertyEditor_remove()
{
if (this._swatchSpans && this._swatchSpans.length) {
for (let span of this._swatchSpans) {
this.ruleEditor.ruleView.colorPicker.removeSwatch(span);
}
}
this.element.parentNode.removeChild(this.element);
this.ruleEditor.rule.editClosestTextProperty(this.prop);
this.valueSpan.textProperty = null;
@@ -2184,16 +2218,7 @@ TextPropertyEditor.prototype = {
_onValueDone: function PropertyEditor_onValueDone(aValue, aCommit)
{
if (aCommit) {
let val = this._parseValue(aValue);
// Any property should be removed if has an empty value.
if (val.value.trim() === "") {
this.remove();
} else {
this.prop.setValue(val.value, val.priority);
this.removeOnRevert = false;
this.committed.value = this.prop.value;
this.committed.priority = this.prop.priority;
}
this._applyNewValue(aValue);
} else {
// A new property should be removed when escape is pressed.
if (this.removeOnRevert) {
@@ -2207,6 +2232,20 @@ TextPropertyEditor.prototype = {
}
},
_applyNewValue: function PropetyEditor_applyNewValue(aValue)
{
let val = this._parseValue(aValue);
// Any property should be removed if has an empty value.
if (val.value.trim() === "") {
this.remove();
} else {
this.prop.setValue(val.value, val.priority);
this.removeOnRevert = false;
this.committed.value = this.prop.value;
this.committed.priority = this.prop.priority;
}
},
/**
* Live preview this property, without committing changes.
*

View File

@@ -61,4 +61,5 @@
.ruleview-colorswatch {
display: inline-block;
cursor: pointer;
}

View File

@@ -39,6 +39,7 @@ MOCHITEST_BROWSER_FILES = \
browser_computedview_bug835808_keyboard_nav.js \
browser_bug913014_matched_expand.js \
browser_bug765105_background_image_tooltip.js \
browser_bug889638_rule_view_color_picker.js \
head.js \
$(NULL)

View File

@@ -74,21 +74,21 @@ function assertTooltipShownOn(tooltip, element, cb) {
function testBodyRuleView() {
info("Testing tooltips in the rule view");
let panel = ruleView.tooltip.panel;
let panel = ruleView.previewTooltip.panel;
// Check that the rule view has a tooltip and that a XUL panel has been created
ok(ruleView.tooltip, "Tooltip instance exists");
ok(ruleView.previewTooltip, "Tooltip instance exists");
ok(panel, "XUL panel exists");
// Get the background-image property inside the rule view
let {nameSpan, valueSpan} = getRuleViewProperty("background-image");
// And verify that the tooltip gets shown on this property
assertTooltipShownOn(ruleView.tooltip, valueSpan, () => {
assertTooltipShownOn(ruleView.previewTooltip, valueSpan, () => {
let images = panel.getElementsByTagName("image");
is(images.length, 1, "Tooltip contains an image");
ok(images[0].src.indexOf("iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHe") !== -1, "The image URL seems fine");
ruleView.tooltip.hide();
ruleView.previewTooltip.hide();
inspector.selection.setNode(contentDoc.querySelector(".test-element"));
inspector.once("inspector-updated", testDivRuleView);
@@ -96,19 +96,19 @@ function testBodyRuleView() {
}
function testDivRuleView() {
let panel = ruleView.tooltip.panel;
let panel = ruleView.previewTooltip.panel;
// Get the background property inside the rule view
let {nameSpan, valueSpan} = getRuleViewProperty("background");
let uriSpan = valueSpan.querySelector(".theme-link");
// And verify that the tooltip gets shown on this property
assertTooltipShownOn(ruleView.tooltip, uriSpan, () => {
assertTooltipShownOn(ruleView.previewTooltip, uriSpan, () => {
let images = panel.getElementsByTagName("image");
is(images.length, 1, "Tooltip contains an image");
ok(images[0].src === "chrome://global/skin/icons/warning-64.png");
ruleView.tooltip.hide();
ruleView.previewTooltip.hide();
testComputedView();
});

View File

@@ -0,0 +1,225 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
let {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
let contentDoc;
let contentWin;
let inspector;
let ruleView;
let swatches = [];
const PAGE_CONTENT = [
'<style type="text/css">',
' body {',
' color: red;',
' background-color: #ededed;',
' background-image: url(chrome://global/skin/icons/warning-64.png);',
' border: 2em solid rgba(120, 120, 120, .5);',
' }',
'</style>',
'Testing the color picker tooltip!'
].join("\n");
function test() {
waitForExplicitFinish();
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function(evt) {
gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
contentDoc = content.document;
contentWin = contentDoc.defaultView;
waitForFocus(createDocument, content);
}, true);
content.location = "data:text/html,rule view color picker tooltip test";
}
function createDocument() {
contentDoc.body.innerHTML = PAGE_CONTENT;
openRuleView((aInspector, aRuleView) => {
inspector = aInspector;
ruleView = aRuleView;
startTests();
});
}
function startTests() {
inspector.selection.setNode(contentDoc.body);
inspector.once("inspector-updated", testColorSwatchesAreDisplayed);
}
function endTests() {
executeSoon(function() {
gDevTools.once("toolbox-destroyed", () => {
contentDoc = contentWin = inspector = ruleView = swatches = null;
gBrowser.removeCurrentTab();
finish();
});
inspector._toolbox.destroy();
});
}
function testColorSwatchesAreDisplayed() {
let cSwatch = getRuleViewProperty("color").valueSpan
.querySelector(".ruleview-colorswatch");
ok(cSwatch, "Color swatch is displayed for the color property");
let bgSwatch = getRuleViewProperty("background-color").valueSpan
.querySelector(".ruleview-colorswatch");
ok(bgSwatch, "Color swatch is displayed for the bg-color property");
let bSwatch = getRuleViewProperty("border").valueSpan
.querySelector(".ruleview-colorswatch");
ok(bSwatch, "Color swatch is displayed for the border property");
swatches = [cSwatch, bgSwatch, bSwatch];
testColorPickerAppearsOnColorSwatchClick();
}
function testColorPickerAppearsOnColorSwatchClick() {
let cPicker = ruleView.colorPicker;
let cPickerPanel = cPicker.tooltip.panel;
ok(cPickerPanel, "The XUL panel for the color picker exists");
function clickOnSwatch(index, cb) {
if (index === swatches.length) {
return cb();
}
let swatch = swatches[index];
cPicker.tooltip.once("shown", () => {
ok(true, "The color picker was shown on click of the color swatch");
ok(!inplaceEditor(swatch.parentNode),
"The inplace editor wasn't shown as a result of the color swatch click");
cPicker.hide();
clickOnSwatch(index + 1, cb);
});
swatch.click();
}
clickOnSwatch(0, testColorPickerHidesWhenImageTooltipAppears);
}
function testColorPickerHidesWhenImageTooltipAppears() {
let swatch = swatches[0];
let bgImageSpan = getRuleViewProperty("background-image").valueSpan;
let uriSpan = bgImageSpan.querySelector(".theme-link");
ruleView.colorPicker.tooltip.once("shown", () => {
info("The color picker is shown, now display an image tooltip to hide it");
ruleView.previewTooltip._showOnHover(uriSpan);
});
ruleView.colorPicker.tooltip.once("hidden", () => {
ok(true, "The color picker closed when the image preview tooltip appeared");
executeSoon(() => {
ruleView.previewTooltip.hide();
testPressingEscapeRevertsChanges();
});
});
swatch.click();
}
function testPressingEscapeRevertsChanges() {
let cPicker = ruleView.colorPicker;
let swatch = swatches[1];
cPicker.tooltip.once("shown", () => {
simulateColorChange(cPicker, [0, 0, 0, 1]);
is(swatch.style.backgroundColor, "rgb(0, 0, 0)",
"The color swatch's background was updated");
is(getRuleViewProperty("background-color").valueSpan.textContent, "rgba(0, 0, 0, 1)",
"The text of the background-color css property was updated");
// The change to the content element is done async, via the protocol,
// that's why we need to let it go first
executeSoon(() => {
is(contentWin.getComputedStyle(contentDoc.body).backgroundColor,
"rgb(0, 0, 0)", "The element's background-color was changed");
cPicker.spectrum.then(spectrum => {
// ESC out of the color picker
EventUtils.sendKey("ESCAPE", spectrum.element.ownerDocument.defaultView);
executeSoon(() => {
is(contentWin.getComputedStyle(contentDoc.body).backgroundColor,
"rgb(237, 237, 237)", "The element's background-color was reverted");
testPressingEnterCommitsChanges();
});
});
});
});
swatch.click();
}
function testPressingEnterCommitsChanges() {
let cPicker = ruleView.colorPicker;
let swatch = swatches[2]; // That's the border css declaration
cPicker.tooltip.once("shown", () => {
simulateColorChange(cPicker, [0, 255, 0, .5]);
is(swatch.style.backgroundColor, "rgba(0, 255, 0, 0.5)",
"The color swatch's background was updated");
is(getRuleViewProperty("border").valueSpan.textContent,
"2em solid rgba(0, 255, 0, 0.5)",
"The text of the border css property was updated");
// The change to the content element is done async, via the protocol,
// that's why we need to let it go first
executeSoon(() => {
is(contentWin.getComputedStyle(contentDoc.body).borderLeftColor,
"rgba(0, 255, 0, 0.5)", "The element's border was changed");
cPicker.tooltip.once("hidden", () => {
is(contentWin.getComputedStyle(contentDoc.body).borderLeftColor,
"rgba(0, 255, 0, 0.5)", "The element's border was kept after ENTER");
is(swatch.style.backgroundColor, "rgba(0, 255, 0, 0.5)",
"The color swatch's background was kept after ENTER");
is(getRuleViewProperty("border").valueSpan.textContent,
"2em solid rgba(0, 255, 0, 0.5)",
"The text of the border css property was kept after ENTER");
endTests();
});
cPicker.spectrum.then(spectrum => {
EventUtils.sendKey("RETURN", spectrum.element.ownerDocument.defaultView);
});
});
});
swatch.click();
}
function simulateColorChange(colorPicker, newRgba) {
// Note that this test isn't concerned with simulating events to test how the
// spectrum color picker reacts, see browser_spectrum.js for this.
// This test only cares about the color swatch <-> color picker <-> rule view
// interactions. That's why there's no event simulations here
colorPicker.spectrum.then(spectrum => {
spectrum.rgb = newRgba;
spectrum.updateUI();
spectrum.onChange();
});
}
function getRuleViewProperty(name) {
let prop = null;
[].forEach.call(ruleView.doc.querySelectorAll(".ruleview-property"), property => {
let nameSpan = property.querySelector(".ruleview-propertyname");
let valueSpan = property.querySelector(".ruleview-propertyvalue");
if (nameSpan.textContent === name) {
prop = {nameSpan: nameSpan, valueSpan: valueSpan};
}
});
return prop;
}

View File

@@ -259,3 +259,11 @@ browser.jar:
skin/classic/browser/notification-pluginNormal.png (../shared/plugins/notification-pluginNormal.png)
skin/classic/browser/notification-pluginAlert.png (../shared/plugins/notification-pluginAlert.png)
skin/classic/browser/notification-pluginBlocked.png (../shared/plugins/notification-pluginBlocked.png)
skin/classic/browser/devtools/tooltip/arrow-horizontal-dark.png (../shared/devtools/tooltip/arrow-horizontal-dark.png)
skin/classic/browser/devtools/tooltip/arrow-horizontal-dark@2x.png (../shared/devtools/tooltip/arrow-horizontal-dark@2x.png)
skin/classic/browser/devtools/tooltip/arrow-vertical-dark.png (../shared/devtools/tooltip/arrow-vertical-dark.png)
skin/classic/browser/devtools/tooltip/arrow-vertical-dark@2x.png (../shared/devtools/tooltip/arrow-vertical-dark@2x.png)
skin/classic/browser/devtools/tooltip/arrow-horizontal-light.png (../shared/devtools/tooltip/arrow-horizontal-light.png)
skin/classic/browser/devtools/tooltip/arrow-horizontal-light@2x.png (../shared/devtools/tooltip/arrow-horizontal-light@2x.png)
skin/classic/browser/devtools/tooltip/arrow-vertical-light.png (../shared/devtools/tooltip/arrow-vertical-light.png)
skin/classic/browser/devtools/tooltip/arrow-vertical-light@2x.png (../shared/devtools/tooltip/arrow-vertical-light@2x.png)

View File

@@ -363,6 +363,14 @@ browser.jar:
skin/classic/browser/notification-pluginNormal@2x.png (../shared/plugins/notification-pluginNormal@2x.png)
skin/classic/browser/notification-pluginAlert@2x.png (../shared/plugins/notification-pluginAlert@2x.png)
skin/classic/browser/notification-pluginBlocked@2x.png (../shared/plugins/notification-pluginBlocked@2x.png)
skin/classic/browser/devtools/tooltip/arrow-horizontal-dark.png (../shared/devtools/tooltip/arrow-horizontal-dark.png)
skin/classic/browser/devtools/tooltip/arrow-horizontal-dark@2x.png (../shared/devtools/tooltip/arrow-horizontal-dark@2x.png)
skin/classic/browser/devtools/tooltip/arrow-vertical-dark.png (../shared/devtools/tooltip/arrow-vertical-dark.png)
skin/classic/browser/devtools/tooltip/arrow-vertical-dark@2x.png (../shared/devtools/tooltip/arrow-vertical-dark@2x.png)
skin/classic/browser/devtools/tooltip/arrow-horizontal-light.png (../shared/devtools/tooltip/arrow-horizontal-light.png)
skin/classic/browser/devtools/tooltip/arrow-horizontal-light@2x.png (../shared/devtools/tooltip/arrow-horizontal-light@2x.png)
skin/classic/browser/devtools/tooltip/arrow-vertical-light.png (../shared/devtools/tooltip/arrow-vertical-light.png)
skin/classic/browser/devtools/tooltip/arrow-vertical-light@2x.png (../shared/devtools/tooltip/arrow-vertical-light@2x.png)
% override chrome://browser/skin/keyhole-circle.png chrome://browser/skin/lion/keyhole-circle.png os=Darwin osversion>=10.7
% override chrome://browser/skin/Toolbar.png chrome://browser/skin/lion/Toolbar.png os=Darwin osversion>=10.7

View File

@@ -115,25 +115,14 @@
/* Tooltip widget (see browser/devtools/shared/widgets/Tooltip.js) */
.devtools-tooltip.devtools-tooltip-tooltip {
/* If the tooltip uses a <tooltip> XUL element */
-moz-appearance: none;
padding: 4px;
background: #eee;
border-radius: 3px;
}
.devtools-tooltip.devtools-tooltip-panel .panel-arrowcontent {
/* If the tooltip uses a <panel> XUL element instead */
padding: 4px;
}
.devtools-tooltip-simple-text {
background: linear-gradient(1deg, transparent 0%, rgba(94,136,176,0.1) 100%);
max-width: 400px;
margin: 0 -4px; /* Compensate for the .panel-arrowcontent padding. */
padding: 8px 12px;
text-shadow: 0 1px 0 #fff;
white-space: pre-wrap;
}
@@ -152,3 +141,8 @@
background-size: 20px 20px;
background-position: 0 0, 10px 10px;
}
.devtools-tooltip-iframe {
border: none;
background: transparent;
}

View File

@@ -193,3 +193,56 @@ div.cm-s-mozilla span.CodeMirror-matchingbracket { /* highlight brackets */
line-height: 1.4em;
min-height: 1.4em;
}
/* XUL panel styling (see browser/devtools/shared/widgets/Tooltip.js) */
.theme-tooltip-panel .panel-arrowcontent {
padding: 5px;
background: rgba(19, 28, 38, .9);
border-radius: 5px;
box-shadow: none;
border: 3px solid #434850;
}
/* Overring panel arrow images to fit with our light and dark themes */
.theme-tooltip-panel .panel-arrow[side="top"] {
list-style-image: url("chrome://browser/skin/devtools/tooltip/arrow-vertical-dark.png");
margin-bottom: -4px;
}
.theme-tooltip-panel .panel-arrow[side="bottom"] {
list-style-image: url("chrome://browser/skin/devtools/tooltip/arrow-vertical-dark.png");
margin-top: -4px;
}
.theme-tooltip-panel .panel-arrow[side="left"] {
list-style-image: url("chrome://browser/skin/devtools/tooltip/arrow-horizontal-dark.png");
margin-right: -4px;
}
.theme-tooltip-panel .panel-arrow[side="right"] {
list-style-image: url("chrome://browser/skin/devtools/tooltip/arrow-horizontal-dark.png");
margin-left: -4px;
}
@media (min-resolution: 2dppx) {
.theme-tooltip-panel .panel-arrow[side="top"],
.theme-tooltip-panel .panel-arrow[side="bottom"] {
list-style-image: url("chrome://browser/skin/devtools/tooltip/arrow-vertical-dark@2x.png");
}
.theme-tooltip-panel .panel-arrow[side="left"],
.theme-tooltip-panel .panel-arrow[side="right"] {
list-style-image: url("chrome://browser/skin/devtools/tooltip/arrow-horizontal-dark@2x.png");
}
}
.theme-tooltip-panel .devtools-tooltip-simple-text {
color: white;
border-bottom: 1px solid #434850;
}
.theme-tooltip-panel .devtools-tooltip-simple-text:last-child {
border-bottom: 0;
}

View File

@@ -192,3 +192,56 @@ div.cm-s-mozilla span.CodeMirror-matchingbracket { /* highlight brackets */
line-height: 1.4em;
min-height: 1.4em;
}
/* XUL panel styling (see browser/devtools/shared/widgets/Tooltip.js) */
.theme-tooltip-panel .panel-arrowcontent {
padding: 4px;
background: rgba(255, 255, 255, .9);
border-radius: 5px;
box-shadow: none;
border: 3px solid #d9e1e8;
}
/* Overring panel arrow images to fit with our light and dark themes */
.theme-tooltip-panel .panel-arrow[side="top"] {
list-style-image: url("chrome://browser/skin/devtools/tooltip/arrow-vertical-light.png");
margin-bottom: -4px;
}
.theme-tooltip-panel .panel-arrow[side="bottom"] {
list-style-image: url("chrome://browser/skin/devtools/tooltip/arrow-vertical-light.png");
margin-top: -4px;
}
.theme-tooltip-panel .panel-arrow[side="left"] {
list-style-image: url("chrome://browser/skin/devtools/tooltip/arrow-horizontal-light.png");
margin-right: -4px;
}
.theme-tooltip-panel .panel-arrow[side="right"] {
list-style-image: url("chrome://browser/skin/devtools/tooltip/arrow-horizontal-light.png");
margin-left: -4px;
}
@media (min-resolution: 2dppx) {
.theme-tooltip-panel .panel-arrow[side="top"],
.theme-tooltip-panel .panel-arrow[side="bottom"] {
list-style-image: url("chrome://browser/skin/devtools/tooltip/arrow-vertical-light@2x.png");
}
.theme-tooltip-panel .panel-arrow[side="left"],
.theme-tooltip-panel .panel-arrow[side="right"] {
list-style-image: url("chrome://browser/skin/devtools/tooltip/arrow-horizontal-light@2x.png");
}
}
.theme-tooltip-panel .devtools-tooltip-simple-text {
color: black;
border-bottom: 1px solid #d9e1e8;
}
.theme-tooltip-panel .devtools-tooltip-simple-text:last-child {
border-bottom: 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -561,3 +561,11 @@ browser.jar:
skin/classic/aero/browser/syncProgress.css
#endif
#endif
skin/classic/browser/devtools/tooltip/arrow-horizontal-dark.png (../shared/devtools/tooltip/arrow-horizontal-dark.png)
skin/classic/browser/devtools/tooltip/arrow-horizontal-dark@2x.png (../shared/devtools/tooltip/arrow-horizontal-dark@2x.png)
skin/classic/browser/devtools/tooltip/arrow-vertical-dark.png (../shared/devtools/tooltip/arrow-vertical-dark.png)
skin/classic/browser/devtools/tooltip/arrow-vertical-dark@2x.png (../shared/devtools/tooltip/arrow-vertical-dark@2x.png)
skin/classic/browser/devtools/tooltip/arrow-horizontal-light.png (../shared/devtools/tooltip/arrow-horizontal-light.png)
skin/classic/browser/devtools/tooltip/arrow-horizontal-light@2x.png (../shared/devtools/tooltip/arrow-horizontal-light@2x.png)
skin/classic/browser/devtools/tooltip/arrow-vertical-light.png (../shared/devtools/tooltip/arrow-vertical-light.png)
skin/classic/browser/devtools/tooltip/arrow-vertical-light@2x.png (../shared/devtools/tooltip/arrow-vertical-light@2x.png)