Bug 889638 - Color picker tooltip in the CSS rule view. r=harth
@@ -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"/>
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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]
|
||||
|
||||
122
browser/devtools/shared/test/browser_spectrum.js
Normal 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);
|
||||
}
|
||||
332
browser/devtools/shared/widgets/Spectrum.js
Normal 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;
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
23
browser/devtools/shared/widgets/spectrum-frame.xhtml
Normal 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>
|
||||
139
browser/devtools/shared/widgets/spectrum.css
Normal 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;
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -61,4 +61,5 @@
|
||||
|
||||
.ruleview-colorswatch {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
BIN
browser/themes/shared/devtools/tooltip/arrow-horizontal-dark.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
BIN
browser/themes/shared/devtools/tooltip/arrow-vertical-dark.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
BIN
browser/themes/shared/devtools/tooltip/arrow-vertical-light.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
@@ -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)
|
||||
|
||||