зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1262439
- 1 - Introduce a new eye-dropper highlighter; r=zer0
This is mostly porting code from the XUL eye-dropper over to a new highlighter file. While I have tested this locally, this highlighter isn't yet used in devtools. This will come in later patches. MozReview-Commit-ID: IF0puLu5Yc7 --HG-- rename : devtools/client/shared/css-color-db.js => devtools/shared/css-color-db.js rename : devtools/client/shared/css-color.js => devtools/shared/css-color.js extra : rebase_source : 72431a9148d1c688987a5693a619769837c774c7
This commit is contained in:
Родитель
336105a0de
Коммит
d2cc90ec24
|
@ -3,10 +3,8 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {rgbToHsl, rgbToColorName} = require("devtools/shared/css-color").colorUtils;
|
||||
const {Cc, Ci} = require("chrome");
|
||||
const {rgbToHsl, rgbToColorName} =
|
||||
require("devtools/client/shared/css-color").colorUtils;
|
||||
const Telemetry = require("devtools/client/shared/telemetry");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const promise = require("promise");
|
||||
|
|
|
@ -63,6 +63,10 @@ skip-if = os == "mac" # Full keyboard navigation on OSX only works if Full Keybo
|
|||
[browser_inspector_highlighter-csstransform_01.js]
|
||||
[browser_inspector_highlighter-csstransform_02.js]
|
||||
[browser_inspector_highlighter-embed.js]
|
||||
[browser_inspector_highlighter-eyedropper-clipboard.js]
|
||||
subsuite = clipboard
|
||||
[browser_inspector_highlighter-eyedropper-events.js]
|
||||
[browser_inspector_highlighter-eyedropper-show-hide.js]
|
||||
[browser_inspector_highlighter-geometry_01.js]
|
||||
[browser_inspector_highlighter-geometry_02.js]
|
||||
[browser_inspector_highlighter-geometry_03.js]
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
/* 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";
|
||||
|
||||
// Test that the eyedropper can copy colors to the clipboard
|
||||
|
||||
const HIGHLIGHTER_TYPE = "EyeDropper";
|
||||
const ID = "eye-dropper-";
|
||||
const TEST_URI = "data:text/html;charset=utf-8,<style>html{background:red}</style>";
|
||||
|
||||
add_task(function* () {
|
||||
let helper = yield openInspectorForURL(TEST_URI)
|
||||
.then(getHighlighterHelperFor(HIGHLIGHTER_TYPE));
|
||||
helper.prefix = ID;
|
||||
|
||||
let {show, synthesizeKey, finalize} = helper;
|
||||
|
||||
info("Show the eyedropper with the copyOnSelect option");
|
||||
yield show("html", {copyOnSelect: true});
|
||||
|
||||
info("Make sure to wait until the eyedropper is done taking a screenshot of the page");
|
||||
yield waitForElementAttributeSet("root", "drawn", helper);
|
||||
|
||||
yield waitForClipboard(() => {
|
||||
info("Activate the eyedropper so the background color is copied");
|
||||
let generateKey = synthesizeKey({key: "VK_RETURN", options: {}});
|
||||
generateKey.next();
|
||||
}, "#FF0000");
|
||||
|
||||
ok(true, "The clipboard contains the right value");
|
||||
|
||||
yield waitForElementAttributeRemoved("root", "drawn", helper);
|
||||
yield waitForElementAttributeSet("root", "hidden", helper);
|
||||
ok(true, "The eyedropper is now hidden");
|
||||
|
||||
finalize();
|
||||
});
|
||||
|
||||
function* waitForElementAttributeSet(id, name, {getElementAttribute}) {
|
||||
yield poll(function* () {
|
||||
let value = yield getElementAttribute(id, name);
|
||||
return !!value;
|
||||
}, `Waiting for element ${id} to have attribute ${name} set`);
|
||||
}
|
||||
|
||||
function* waitForElementAttributeRemoved(id, name, {getElementAttribute}) {
|
||||
yield poll(function* () {
|
||||
let value = yield getElementAttribute(id, name);
|
||||
return !value;
|
||||
}, `Waiting for element ${id} to have attribute ${name} removed`);
|
||||
}
|
||||
|
||||
function* poll(check, desc) {
|
||||
info(desc);
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
if (yield check()) {
|
||||
return;
|
||||
}
|
||||
yield new Promise(resolve => setTimeout(resolve, 200));
|
||||
}
|
||||
|
||||
throw new Error(`Timeout while: ${desc}`);
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/* 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";
|
||||
|
||||
// Test the eyedropper mouse and keyboard handling.
|
||||
|
||||
const HIGHLIGHTER_TYPE = "EyeDropper";
|
||||
const ID = "eye-dropper-";
|
||||
|
||||
const MOVE_EVENTS_DATA = [
|
||||
{type: "mouse", x: 200, y: 100, expected: {x: 200, y: 100}},
|
||||
{type: "mouse", x: 100, y: 200, expected: {x: 100, y: 200}},
|
||||
{type: "keyboard", key: "VK_LEFT", expected: {x: 99, y: 200}},
|
||||
{type: "keyboard", key: "VK_LEFT", shift: true, expected: {x: 89, y: 200}},
|
||||
{type: "keyboard", key: "VK_RIGHT", expected: {x: 90, y: 200}},
|
||||
{type: "keyboard", key: "VK_RIGHT", shift: true, expected: {x: 100, y: 200}},
|
||||
{type: "keyboard", key: "VK_DOWN", expected: {x: 100, y: 201}},
|
||||
{type: "keyboard", key: "VK_DOWN", shift: true, expected: {x: 100, y: 211}},
|
||||
{type: "keyboard", key: "VK_UP", expected: {x: 100, y: 210}},
|
||||
{type: "keyboard", key: "VK_UP", shift: true, expected: {x: 100, y: 200}},
|
||||
];
|
||||
|
||||
add_task(function* () {
|
||||
let helper = yield openInspectorForURL("data:text/html;charset=utf-8,eye-dropper test")
|
||||
.then(getHighlighterHelperFor(HIGHLIGHTER_TYPE));
|
||||
helper.prefix = ID;
|
||||
|
||||
yield helper.show("html");
|
||||
yield respondsToMoveEvents(helper);
|
||||
yield respondsToReturnAndEscape(helper);
|
||||
|
||||
helper.finalize();
|
||||
});
|
||||
|
||||
function* respondsToMoveEvents(helper) {
|
||||
info("Checking that the eyedropper responds to events from the mouse and keyboard");
|
||||
let {mouse, synthesizeKey} = helper;
|
||||
|
||||
for (let {type, x, y, key, shift, expected} of MOVE_EVENTS_DATA) {
|
||||
info(`Simulating a ${type} event to move to ${expected.x} ${expected.y}`);
|
||||
if (type === "mouse") {
|
||||
yield mouse.move(x, y);
|
||||
} else if (type === "keyboard") {
|
||||
let options = shift ? {shiftKey: true} : {};
|
||||
yield synthesizeKey({key, options});
|
||||
}
|
||||
yield checkPosition(expected, helper);
|
||||
}
|
||||
}
|
||||
|
||||
function* checkPosition({x, y}, {getElementAttribute}) {
|
||||
let style = yield getElementAttribute("root", "style");
|
||||
is(style, `top:${y}px;left:${x}px;`,
|
||||
`The eyedropper is at the expected ${x} ${y} position`);
|
||||
}
|
||||
|
||||
function* respondsToReturnAndEscape({synthesizeKey, isElementHidden, show}) {
|
||||
info("Simulating return to select the color and hide the eyedropper");
|
||||
|
||||
yield synthesizeKey({key: "VK_RETURN", options: {}});
|
||||
let hidden = yield isElementHidden("root");
|
||||
ok(hidden, "The eyedropper has been hidden");
|
||||
|
||||
info("Showing the eyedropper again and simulating escape to hide it");
|
||||
|
||||
yield show("html");
|
||||
yield synthesizeKey({key: "VK_ESCAPE", options: {}});
|
||||
hidden = yield isElementHidden("root");
|
||||
ok(hidden, "The eyedropper has been hidden again");
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/* 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";
|
||||
|
||||
// Test the basic structure of the eye-dropper highlighter.
|
||||
|
||||
const HIGHLIGHTER_TYPE = "EyeDropper";
|
||||
const ID = "eye-dropper-";
|
||||
|
||||
add_task(function* () {
|
||||
let helper = yield openInspectorForURL("data:text/html;charset=utf-8,eye-dropper test")
|
||||
.then(getHighlighterHelperFor(HIGHLIGHTER_TYPE));
|
||||
helper.prefix = ID;
|
||||
|
||||
yield isInitiallyHidden(helper);
|
||||
yield canBeShownAndHidden(helper);
|
||||
|
||||
helper.finalize();
|
||||
});
|
||||
|
||||
function* isInitiallyHidden({isElementHidden}) {
|
||||
info("Checking that the eyedropper is hidden by default");
|
||||
|
||||
let hidden = yield isElementHidden("root");
|
||||
ok(hidden, "The eyedropper is hidden by default");
|
||||
}
|
||||
|
||||
function* canBeShownAndHidden({show, hide, isElementHidden, getElementAttribute}) {
|
||||
info("Asking to show and hide the highlighter actually works");
|
||||
|
||||
yield show("html");
|
||||
let hidden = yield isElementHidden("root");
|
||||
ok(!hidden, "The eyedropper is now shown");
|
||||
|
||||
let style = yield getElementAttribute("root", "style");
|
||||
is(style, "top:100px;left:100px;", "The eyedropper is correctly positioned");
|
||||
|
||||
yield hide();
|
||||
hidden = yield isElementHidden("root");
|
||||
ok(hidden, "The eyedropper is now hidden again");
|
||||
}
|
|
@ -422,6 +422,7 @@ const getHighlighterHelperFor = (type) => Task.async(
|
|||
set prefix(value) {
|
||||
prefix = value;
|
||||
},
|
||||
|
||||
get highlightedNode() {
|
||||
if (!highlightedNode) {
|
||||
return null;
|
||||
|
@ -435,9 +436,9 @@ const getHighlighterHelperFor = (type) => Task.async(
|
|||
};
|
||||
},
|
||||
|
||||
show: function* (selector = ":root") {
|
||||
show: function* (selector = ":root", options) {
|
||||
highlightedNode = yield getNodeFront(selector, inspector);
|
||||
return yield highlighter.show(highlightedNode);
|
||||
return yield highlighter.show(highlightedNode, options);
|
||||
},
|
||||
|
||||
hide: function* () {
|
||||
|
@ -464,6 +465,10 @@ const getHighlighterHelperFor = (type) => Task.async(
|
|||
yield testActor.synthesizeMouse(options);
|
||||
},
|
||||
|
||||
synthesizeKey: function* (options) {
|
||||
yield testActor.synthesizeKey(options);
|
||||
},
|
||||
|
||||
// This object will synthesize any "mouse" prefixed event to the
|
||||
// `testActor`, using the name of method called as suffix for the
|
||||
// event's name.
|
||||
|
|
|
@ -18,7 +18,7 @@ const { CanvasGraphUtils } = require("devtools/client/shared/widgets/Graphs");
|
|||
const promise = require("promise");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
|
||||
const { colorUtils } = require("devtools/client/shared/css-color");
|
||||
const { colorUtils } = require("devtools/shared/css-color");
|
||||
const { getColor } = require("devtools/client/shared/theme");
|
||||
const ProfilerGlobal = require("devtools/client/performance/modules/global");
|
||||
const { MarkersOverview } = require("devtools/client/performance/modules/widgets/markers-overview");
|
||||
|
|
|
@ -13,7 +13,7 @@ const { Cc, Ci, Cu, Cr } = require("chrome");
|
|||
const { Heritage } = require("devtools/client/shared/widgets/view-helpers");
|
||||
const { AbstractCanvasGraph } = require("devtools/client/shared/widgets/Graphs");
|
||||
|
||||
const { colorUtils } = require("devtools/client/shared/css-color");
|
||||
const { colorUtils } = require("devtools/shared/css-color");
|
||||
const { getColor } = require("devtools/client/shared/theme");
|
||||
const ProfilerGlobal = require("devtools/client/performance/modules/global");
|
||||
const { MarkerBlueprintUtils } = require("devtools/client/performance/modules/marker-blueprint-utils");
|
||||
|
|
|
@ -20,8 +20,6 @@ DevToolsModules(
|
|||
'autocomplete-popup.js',
|
||||
'browser-loader.js',
|
||||
'css-angle.js',
|
||||
'css-color-db.js',
|
||||
'css-color.js',
|
||||
'css-reload.js',
|
||||
'Curl.jsm',
|
||||
'demangle.js',
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
const {Cc, Ci} = require("chrome");
|
||||
const {angleUtils} = require("devtools/client/shared/css-angle");
|
||||
const {colorUtils} = require("devtools/client/shared/css-color");
|
||||
const {colorUtils} = require("devtools/shared/css-color");
|
||||
const {getCSSLexer} = require("devtools/shared/css-lexer");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const TEST_URI = "data:text/html;charset=utf-8,browser_css_color.js";
|
||||
var {colorUtils} = require("devtools/client/shared/css-color");
|
||||
var {colorUtils} = require("devtools/shared/css-color");
|
||||
var origColorUnit;
|
||||
|
||||
add_task(function* () {
|
||||
|
|
|
@ -10,7 +10,7 @@ var Ci = Components.interfaces;
|
|||
var Cc = Components.classes;
|
||||
|
||||
var {require, loader} = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||
const {colorUtils} = require("devtools/client/shared/css-color");
|
||||
const {colorUtils} = require("devtools/shared/css-color");
|
||||
|
||||
loader.lazyGetter(this, "DOMUtils", function () {
|
||||
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
|
||||
|
|
|
@ -13,8 +13,8 @@ var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
|||
|
||||
const DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
|
||||
|
||||
const {colorUtils} = require("devtools/client/shared/css-color");
|
||||
const {cssColors} = require("devtools/client/shared/css-color-db");
|
||||
const {colorUtils} = require("devtools/shared/css-color");
|
||||
const {cssColors} = require("devtools/shared/css-color-db");
|
||||
|
||||
function isValid(colorName) {
|
||||
ok(colorUtils.isValidCSSColor(colorName),
|
||||
|
|
|
@ -12,7 +12,7 @@ const {CubicBezierWidget} =
|
|||
const {CSSFilterEditorWidget} = require("devtools/client/shared/widgets/FilterWidget");
|
||||
const {TooltipToggle} = require("devtools/client/shared/widgets/tooltip/TooltipToggle");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const {colorUtils} = require("devtools/client/shared/css-color");
|
||||
const {colorUtils} = require("devtools/shared/css-color");
|
||||
const Heritage = require("sdk/core/heritage");
|
||||
const {Eyedropper} = require("devtools/client/eyedropper/eyedropper");
|
||||
const {gDevTools} = require("devtools/client/framework/devtools");
|
||||
|
|
|
@ -400,3 +400,67 @@
|
|||
stroke-dasharray: 5 3;
|
||||
shape-rendering: crispEdges;
|
||||
}
|
||||
|
||||
/* Eye dropper */
|
||||
|
||||
:-moz-native-anonymous .eye-dropper-root {
|
||||
--magnifier-width: 96px;
|
||||
--magnifier-height: 96px;
|
||||
/* Width accounts for all color formats (hsl being the longest) */
|
||||
--label-width: 160px;
|
||||
--color: #e0e0e0;
|
||||
|
||||
position: absolute;
|
||||
/* Tool start position. This should match the X/Y defines in JS */
|
||||
top: 100px;
|
||||
left: 100px;
|
||||
|
||||
/* Prevent interacting with the page when hovering and clicking */
|
||||
pointer-events: auto;
|
||||
|
||||
/* Offset the UI so it is centered around the pointer */
|
||||
transform: translate(
|
||||
calc(var(--magnifier-width) / -2), calc(var(--magnifier-height) / -2));
|
||||
|
||||
filter: drop-shadow(0 0 1px rgba(0,0,0,.4));
|
||||
|
||||
/* We don't need the UI to be reversed in RTL locales, otherwise the # would appear
|
||||
to the right of the hex code. Force LTR */
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
:-moz-native-anonymous .eye-dropper-canvas {
|
||||
image-rendering: -moz-crisp-edges;
|
||||
cursor: none;
|
||||
width: var(--magnifier-width);
|
||||
height: var(--magnifier-height);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 0 3px var(--color);
|
||||
display: block;
|
||||
}
|
||||
|
||||
:-moz-native-anonymous .eye-dropper-color-container {
|
||||
background-color: var(--color);
|
||||
border-radius: 2px;
|
||||
width: var(--label-width);
|
||||
transform: translateX(calc((var(--magnifier-width) - var(--label-width)) / 2));
|
||||
position: relative;
|
||||
}
|
||||
|
||||
:-moz-native-anonymous .eye-dropper-color-preview {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
position: absolute;
|
||||
offset-inline-start: 3px;
|
||||
offset-block-start: 3px;
|
||||
box-shadow: 0px 0px 0px black;
|
||||
border: solid 1px #fff;
|
||||
}
|
||||
|
||||
:-moz-native-anonymous .eye-dropper-color-value {
|
||||
text-shadow: 1px 1px 1px #fff;
|
||||
font: message-box;
|
||||
font-size: 11px;
|
||||
text-align: center;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
|
|
@ -679,3 +679,7 @@ exports.RulersHighlighter = RulersHighlighter;
|
|||
const { MeasuringToolHighlighter } = require("./highlighters/measuring-tool");
|
||||
register(MeasuringToolHighlighter);
|
||||
exports.MeasuringToolHighlighter = MeasuringToolHighlighter;
|
||||
|
||||
const { EyeDropper } = require("./highlighters/eye-dropper");
|
||||
register(EyeDropper);
|
||||
exports.EyeDropper = EyeDropper;
|
||||
|
|
|
@ -0,0 +1,501 @@
|
|||
/* 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";
|
||||
|
||||
// Eye-dropper tool. This is implemented as a highlighter so it can be displayed in the
|
||||
// content page.
|
||||
// It basically displays a magnifier that tracks mouse moves and shows a magnified version
|
||||
// of the page. On click, it samples the color at the pixel being hovered.
|
||||
|
||||
const {Ci, Cc} = require("chrome");
|
||||
const {CanvasFrameAnonymousContentHelper, createNode} = require("./utils/markup");
|
||||
const Services = require("Services");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const {rgbToHsl, rgbToColorName} = require("devtools/shared/css-color").colorUtils;
|
||||
const {getCurrentZoom, getFrameOffsets} = require("devtools/shared/layout/utils");
|
||||
|
||||
loader.lazyGetter(this, "clipboardHelper",
|
||||
() => Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper));
|
||||
loader.lazyGetter(this, "l10n",
|
||||
() => Services.strings.createBundle("chrome://devtools/locale/eyedropper.properties"));
|
||||
|
||||
const ZOOM_LEVEL_PREF = "devtools.eyedropper.zoom";
|
||||
const FORMAT_PREF = "devtools.defaultColorUnit";
|
||||
// Width of the canvas.
|
||||
const MAGNIFIER_WIDTH = 96;
|
||||
// Height of the canvas.
|
||||
const MAGNIFIER_HEIGHT = 96;
|
||||
// Start position, when the tool is first shown. This should match the top/left position
|
||||
// defined in CSS.
|
||||
const DEFAULT_START_POS_X = 100;
|
||||
const DEFAULT_START_POS_Y = 100;
|
||||
// How long to wait before closing after copy.
|
||||
const CLOSE_DELAY = 750;
|
||||
|
||||
/**
|
||||
* The EyeDropper is the class that draws the gradient line and
|
||||
* color stops as an overlay on top of a linear-gradient background-image.
|
||||
*/
|
||||
function EyeDropper(highlighterEnv) {
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
this.highlighterEnv = highlighterEnv;
|
||||
this.markup = new CanvasFrameAnonymousContentHelper(this.highlighterEnv,
|
||||
this._buildMarkup.bind(this));
|
||||
|
||||
// Get a couple of settings from prefs.
|
||||
this.format = Services.prefs.getCharPref(FORMAT_PREF);
|
||||
this.eyeDropperZoomLevel = Services.prefs.getIntPref(ZOOM_LEVEL_PREF);
|
||||
}
|
||||
|
||||
EyeDropper.prototype = {
|
||||
typeName: "EyeDropper",
|
||||
|
||||
ID_CLASS_PREFIX: "eye-dropper-",
|
||||
|
||||
get win() {
|
||||
return this.highlighterEnv.window;
|
||||
},
|
||||
|
||||
_buildMarkup() {
|
||||
// Highlighter main container.
|
||||
let container = createNode(this.win, {
|
||||
attributes: {"class": "highlighter-container"}
|
||||
});
|
||||
|
||||
// Wrapper element.
|
||||
let wrapper = createNode(this.win, {
|
||||
parent: container,
|
||||
attributes: {
|
||||
"id": "root",
|
||||
"class": "root",
|
||||
"hidden": "true"
|
||||
},
|
||||
prefix: this.ID_CLASS_PREFIX
|
||||
});
|
||||
|
||||
// The magnifier canvas element.
|
||||
createNode(this.win, {
|
||||
parent: wrapper,
|
||||
nodeType: "canvas",
|
||||
attributes: {
|
||||
"id": "canvas",
|
||||
"class": "canvas",
|
||||
"width": MAGNIFIER_WIDTH,
|
||||
"height": MAGNIFIER_HEIGHT
|
||||
},
|
||||
prefix: this.ID_CLASS_PREFIX
|
||||
});
|
||||
|
||||
// The color label element.
|
||||
let colorLabelContainer = createNode(this.win, {
|
||||
parent: wrapper,
|
||||
attributes: {"class": "color-container"},
|
||||
prefix: this.ID_CLASS_PREFIX
|
||||
});
|
||||
createNode(this.win, {
|
||||
nodeType: "div",
|
||||
parent: colorLabelContainer,
|
||||
attributes: {"id": "color-preview", "class": "color-preview"},
|
||||
prefix: this.ID_CLASS_PREFIX
|
||||
});
|
||||
createNode(this.win, {
|
||||
nodeType: "div",
|
||||
parent: colorLabelContainer,
|
||||
attributes: {"id": "color-value", "class": "color-value"},
|
||||
prefix: this.ID_CLASS_PREFIX
|
||||
});
|
||||
|
||||
return container;
|
||||
},
|
||||
|
||||
destroy() {
|
||||
this.hide();
|
||||
this.markup.destroy();
|
||||
},
|
||||
|
||||
getElement(id) {
|
||||
return this.markup.getElement(this.ID_CLASS_PREFIX + id);
|
||||
},
|
||||
|
||||
/**
|
||||
* Show the eye-dropper highlighter.
|
||||
* @param {DOMNode} node The node which document the highlighter should be inserted in.
|
||||
* @param {Object} options The options object may contain the following properties:
|
||||
* - {Boolean} copyOnSelect Whether selecting a color should copy it to the clipboard.
|
||||
*/
|
||||
show(node, options = {}) {
|
||||
this.options = options;
|
||||
|
||||
// Get the page's current zoom level.
|
||||
this.pageZoom = getCurrentZoom(this.win);
|
||||
|
||||
// Take a screenshot of the viewport. This needs to be done first otherwise the
|
||||
// eyedropper UI will appear in the screenshot itself (since the UI is injected as
|
||||
// native anonymous content in the page).
|
||||
// Once the screenshot is ready, the magnified area will be drawn.
|
||||
this.prepareImageCapture();
|
||||
|
||||
// Start listening for user events.
|
||||
let {pageListenerTarget} = this.highlighterEnv;
|
||||
pageListenerTarget.addEventListener("mousemove", this);
|
||||
pageListenerTarget.addEventListener("click", this);
|
||||
pageListenerTarget.addEventListener("keydown", this);
|
||||
pageListenerTarget.addEventListener("DOMMouseScroll", this);
|
||||
pageListenerTarget.addEventListener("FullZoomChange", this);
|
||||
|
||||
// Show the eye-dropper.
|
||||
this.getElement("root").removeAttribute("hidden");
|
||||
|
||||
// Prepare the canvas context on which we're drawing the magnified page portion.
|
||||
this.ctx = this.getElement("canvas").getCanvasContext();
|
||||
this.ctx.mozImageSmoothingEnabled = false;
|
||||
|
||||
this.magnifiedArea = {width: MAGNIFIER_WIDTH, height: MAGNIFIER_HEIGHT,
|
||||
x: DEFAULT_START_POS_X, y: DEFAULT_START_POS_Y};
|
||||
|
||||
this.moveTo(DEFAULT_START_POS_X, DEFAULT_START_POS_Y);
|
||||
|
||||
// Focus the content so the keyboard can be used.
|
||||
this.win.document.documentElement.focus();
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Hide the eye-dropper highlighter.
|
||||
*/
|
||||
hide() {
|
||||
this.pageImage = null;
|
||||
|
||||
let {pageListenerTarget} = this.highlighterEnv;
|
||||
pageListenerTarget.removeEventListener("mousemove", this);
|
||||
pageListenerTarget.removeEventListener("click", this);
|
||||
pageListenerTarget.removeEventListener("keydown", this);
|
||||
pageListenerTarget.removeEventListener("DOMMouseScroll", this);
|
||||
pageListenerTarget.removeEventListener("FullZoomChange", this);
|
||||
|
||||
this.getElement("root").setAttribute("hidden", "true");
|
||||
this.getElement("root").removeAttribute("drawn");
|
||||
|
||||
this.emit("hidden");
|
||||
},
|
||||
|
||||
prepareImageCapture() {
|
||||
// Get the page as an image.
|
||||
let imageData = getWindowAsImageData(this.win);
|
||||
let image = new this.win.Image();
|
||||
image.src = imageData;
|
||||
|
||||
// Wait for screenshot to load
|
||||
image.onload = () => {
|
||||
this.pageImage = image;
|
||||
// We likely haven't drawn anything yet (no mousemove events yet), so start now.
|
||||
this.draw();
|
||||
|
||||
// Set an attribute on the root element to be able to run tests after the first draw
|
||||
// was done.
|
||||
this.getElement("root").setAttribute("drawn", "true");
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the number of cells (blown-up pixels) per direction in the grid.
|
||||
*/
|
||||
get cellsWide() {
|
||||
// Canvas will render whole "pixels" (cells) only, and an even number at that. Round
|
||||
// up to the nearest even number of pixels.
|
||||
let cellsWide = Math.ceil(this.magnifiedArea.width / this.eyeDropperZoomLevel);
|
||||
cellsWide += cellsWide % 2;
|
||||
|
||||
return cellsWide;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the size of each cell (blown-up pixel) in the grid.
|
||||
*/
|
||||
get cellSize() {
|
||||
return this.magnifiedArea.width / this.cellsWide;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get index of cell in the center of the grid.
|
||||
*/
|
||||
get centerCell() {
|
||||
return Math.floor(this.cellsWide / 2);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get color of center cell in the grid.
|
||||
*/
|
||||
get centerColor() {
|
||||
let pos = (this.centerCell * this.cellSize) + (this.cellSize / 2);
|
||||
let rgb = this.ctx.getImageData(pos, pos, 1, 1).data;
|
||||
return rgb;
|
||||
},
|
||||
|
||||
draw() {
|
||||
// If the image of the page isn't ready yet, bail out, we'll draw later on mousemove.
|
||||
if (!this.pageImage) {
|
||||
return;
|
||||
}
|
||||
|
||||
let {width, height, x, y} = this.magnifiedArea;
|
||||
|
||||
let zoomedWidth = width / this.eyeDropperZoomLevel;
|
||||
let zoomedHeight = height / this.eyeDropperZoomLevel;
|
||||
|
||||
let sx = x - (zoomedWidth / 2);
|
||||
let sy = y - (zoomedHeight / 2);
|
||||
let sw = zoomedWidth;
|
||||
let sh = zoomedHeight;
|
||||
|
||||
this.ctx.drawImage(this.pageImage, sx, sy, sw, sh, 0, 0, width, height);
|
||||
|
||||
// Draw the grid on top, but only at 3x or more, otherwise it's too busy.
|
||||
if (this.eyeDropperZoomLevel > 2) {
|
||||
this.drawGrid();
|
||||
}
|
||||
|
||||
this.drawCrosshair();
|
||||
|
||||
// Update the color preview and value.
|
||||
let rgb = this.centerColor;
|
||||
this.getElement("color-preview").setAttribute("style",
|
||||
`background-color:${toColorString(rgb, "rgb")};`);
|
||||
this.getElement("color-value").setTextContent(toColorString(rgb, this.format));
|
||||
},
|
||||
|
||||
/**
|
||||
* Draw a grid on the canvas representing pixel boundaries.
|
||||
*/
|
||||
drawGrid() {
|
||||
let {width, height} = this.magnifiedArea;
|
||||
|
||||
this.ctx.lineWidth = 1;
|
||||
this.ctx.strokeStyle = "rgba(143, 143, 143, 0.2)";
|
||||
|
||||
for (let i = 0; i < width; i += this.cellSize) {
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(i - .5, 0);
|
||||
this.ctx.lineTo(i - .5, height);
|
||||
this.ctx.stroke();
|
||||
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(0, i - .5);
|
||||
this.ctx.lineTo(width, i - .5);
|
||||
this.ctx.stroke();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Draw a box on the canvas to highlight the center cell.
|
||||
*/
|
||||
drawCrosshair() {
|
||||
let pos = this.centerCell * this.cellSize;
|
||||
|
||||
this.ctx.lineWidth = 1;
|
||||
this.ctx.lineJoin = "miter";
|
||||
this.ctx.strokeStyle = "rgba(0, 0, 0, 1)";
|
||||
this.ctx.strokeRect(pos - 1.5, pos - 1.5, this.cellSize + 2, this.cellSize + 2);
|
||||
|
||||
this.ctx.strokeStyle = "rgba(255, 255, 255, 1)";
|
||||
this.ctx.strokeRect(pos - 0.5, pos - 0.5, this.cellSize, this.cellSize);
|
||||
},
|
||||
|
||||
handleEvent(e) {
|
||||
switch (e.type) {
|
||||
case "mousemove":
|
||||
// We might be getting an event from a child frame, so account for the offset.
|
||||
let [xOffset, yOffset] = getFrameOffsets(this.win, e.target);
|
||||
let x = xOffset + e.pageX - this.win.scrollX;
|
||||
let y = yOffset + e.pageY - this.win.scrollY;
|
||||
// Update the zoom area.
|
||||
this.magnifiedArea.x = x * this.pageZoom;
|
||||
this.magnifiedArea.y = y * this.pageZoom;
|
||||
// Redraw the portion of the screenshot that is now under the mouse.
|
||||
this.draw();
|
||||
// And move the eye-dropper's UI so it follows the mouse.
|
||||
this.moveTo(x, y);
|
||||
break;
|
||||
case "click":
|
||||
this.selectColor();
|
||||
break;
|
||||
case "keydown":
|
||||
this.handleKeyDown(e);
|
||||
break;
|
||||
case "DOMMouseScroll":
|
||||
// Prevent scrolling. That's because we only took a screenshot of the viewport, so
|
||||
// scrolling out of the viewport wouldn't draw the expected things. In the future
|
||||
// we can take the screenshot again on scroll, but for now it doesn't seem
|
||||
// important.
|
||||
e.preventDefault();
|
||||
break;
|
||||
case "FullZoomChange":
|
||||
this.hide();
|
||||
this.show();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
moveTo(x, y) {
|
||||
this.getElement("root").setAttribute("style", `top:${y}px;left:${x}px;`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Select the current color that's being previewed. Depending on the current options,
|
||||
* selecting might mean copying to the clipboard and closing the
|
||||
*/
|
||||
selectColor() {
|
||||
let onColorSelected = Promise.resolve();
|
||||
if (this.options.copyOnSelect) {
|
||||
onColorSelected = this.copyColor();
|
||||
}
|
||||
|
||||
this.emit("selected", toColorString(this.centerColor, this.format));
|
||||
onColorSelected.then(() => this.hide(), e => console.error(e));
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the keydown event. Either select the color or move the panel in a
|
||||
* direction depending on the key pressed.
|
||||
*/
|
||||
handleKeyDown(e) {
|
||||
if (e.keyCode === e.DOM_VK_RETURN) {
|
||||
this.selectColor();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.keyCode === e.DOM_VK_ESCAPE) {
|
||||
this.emit("canceled");
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
let offsetX = 0;
|
||||
let offsetY = 0;
|
||||
let modifier = 1;
|
||||
|
||||
if (e.keyCode === e.DOM_VK_LEFT) {
|
||||
offsetX = -1;
|
||||
}
|
||||
if (e.keyCode === e.DOM_VK_RIGHT) {
|
||||
offsetX = 1;
|
||||
}
|
||||
if (e.keyCode === e.DOM_VK_UP) {
|
||||
offsetY = -1;
|
||||
}
|
||||
if (e.keyCode === e.DOM_VK_DOWN) {
|
||||
offsetY = 1;
|
||||
}
|
||||
if (e.shiftKey) {
|
||||
modifier = 10;
|
||||
}
|
||||
|
||||
offsetY *= modifier;
|
||||
offsetX *= modifier;
|
||||
|
||||
if (offsetX !== 0 || offsetY !== 0) {
|
||||
this.magnifiedArea.x += offsetX;
|
||||
this.magnifiedArea.y += offsetY;
|
||||
|
||||
this.draw();
|
||||
|
||||
this.moveTo(this.magnifiedArea.x / this.pageZoom,
|
||||
this.magnifiedArea.y / this.pageZoom);
|
||||
}
|
||||
|
||||
// Prevent all keyboard interaction with the page, except if a modifier is used to let
|
||||
// keyboard shortcuts through.
|
||||
let hasModifier = e.metaKey || e.ctrlKey || e.altKey || e.shiftKey;
|
||||
if (!hasModifier) {
|
||||
e.preventDefault();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy the currently inspected color to the clipboard.
|
||||
* @return {Promise} Resolves when the copy has been done (after a delay that is used to
|
||||
* let users know that something was copied).
|
||||
*/
|
||||
copyColor() {
|
||||
// Copy to the clipboard.
|
||||
let color = toColorString(this.centerColor, this.format);
|
||||
clipboardHelper.copyString(color);
|
||||
|
||||
// Provide some feedback.
|
||||
this.getElement("color-value").setTextContent(
|
||||
"✓ " + l10n.GetStringFromName("colorValue.copied"));
|
||||
|
||||
// Hide the tool after a delay.
|
||||
clearTimeout(this._copyTimeout);
|
||||
return new Promise(resolve => {
|
||||
this._copyTimeout = setTimeout(resolve, CLOSE_DELAY);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.EyeDropper = EyeDropper;
|
||||
|
||||
/**
|
||||
* Get a content window as image data-url.
|
||||
* @param {Window} win
|
||||
* @return {String} The data-url
|
||||
*/
|
||||
function getWindowAsImageData(win) {
|
||||
let canvas = win.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
|
||||
let scale = getCurrentZoom(win);
|
||||
let width = win.innerWidth;
|
||||
let height = win.innerHeight;
|
||||
canvas.width = width * scale;
|
||||
canvas.height = height * scale;
|
||||
canvas.mozOpaque = true;
|
||||
|
||||
let ctx = canvas.getContext("2d");
|
||||
|
||||
ctx.scale(scale, scale);
|
||||
ctx.drawWindow(win, win.scrollX, win.scrollY, width, height, "#fff");
|
||||
|
||||
return canvas.toDataURL();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a formatted CSS color string from a color value.
|
||||
* @param {array} rgb Rgb values of a color to format.
|
||||
* @param {string} format Format of string. One of "hex", "rgb", "hsl", "name".
|
||||
* @return {string} Formatted color value, e.g. "#FFF" or "hsl(20, 10%, 10%)".
|
||||
*/
|
||||
function toColorString(rgb, format) {
|
||||
let [r, g, b] = rgb;
|
||||
|
||||
switch (format) {
|
||||
case "hex":
|
||||
return hexString(rgb);
|
||||
case "rgb":
|
||||
return "rgb(" + r + ", " + g + ", " + b + ")";
|
||||
case "hsl":
|
||||
let [h, s, l] = rgbToHsl(rgb);
|
||||
return "hsl(" + h + ", " + s + "%, " + l + "%)";
|
||||
case "name":
|
||||
let str;
|
||||
try {
|
||||
str = rgbToColorName(r, g, b);
|
||||
} catch (e) {
|
||||
str = hexString(rgb);
|
||||
}
|
||||
return str;
|
||||
default:
|
||||
return hexString(rgb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a hex-formatted color string from rgb values.
|
||||
* @param {array} rgb Rgb values of color to stringify.
|
||||
* @return {string} Hex formatted string for color, e.g. "#FFEE00".
|
||||
*/
|
||||
function hexString([r, g, b]) {
|
||||
let val = (1 << 24) + (r << 16) + (g << 8) + (b << 0);
|
||||
return "#" + val.toString(16).substr(-6).toUpperCase();
|
||||
}
|
|
@ -12,6 +12,7 @@ DevToolsModules(
|
|||
'auto-refresh.js',
|
||||
'box-model.js',
|
||||
'css-transform.js',
|
||||
'eye-dropper.js',
|
||||
'geometry-editor.js',
|
||||
'measuring-tool.js',
|
||||
'rect.js',
|
||||
|
|
|
@ -325,6 +325,10 @@ CanvasFrameAnonymousContentHelper.prototype = {
|
|||
return typeof this.getAttributeForElement(id, name) === "string";
|
||||
},
|
||||
|
||||
getCanvasContext: function (id, type = "2d") {
|
||||
return this.content ? this.content.getCanvasContext(id, type) : null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Add an event listener to one of the elements inserted in the canvasFrame
|
||||
* native anonymous container.
|
||||
|
@ -460,6 +464,7 @@ CanvasFrameAnonymousContentHelper.prototype = {
|
|||
getAttribute: name => this.getAttributeForElement(id, name),
|
||||
removeAttribute: name => this.removeAttributeForElement(id, name),
|
||||
hasAttribute: name => this.hasAttributeForElement(id, name),
|
||||
getCanvasContext: type => this.getCanvasContext(id, type),
|
||||
addEventListener: (type, handler) => {
|
||||
return this.addEventListenerForElement(id, type, handler);
|
||||
},
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
const Services = require("Services");
|
||||
|
||||
const {getCSSLexer} = require("devtools/shared/css-lexer");
|
||||
const {cssColors} = require("devtools/client/shared/css-color-db");
|
||||
const {cssColors} = require("devtools/shared/css-color-db");
|
||||
|
||||
const COLOR_UNIT_PREF = "devtools.defaultColorUnit";
|
||||
|
|
@ -166,6 +166,7 @@ function getFrameOffsets(boundaryWindow, node) {
|
|||
|
||||
return [xOffset * scale, yOffset * scale];
|
||||
}
|
||||
exports.getFrameOffsets = getFrameOffsets;
|
||||
|
||||
/**
|
||||
* Get box quads adjusted for iframes and zoom level.
|
||||
|
|
|
@ -42,6 +42,8 @@ DevToolsModules(
|
|||
'async-utils.js',
|
||||
'builtin-modules.js',
|
||||
'content-observer.js',
|
||||
'css-color-db.js',
|
||||
'css-color.js',
|
||||
'css-lexer.js',
|
||||
'css-parsing-utils.js',
|
||||
'css-properties-db.js',
|
||||
|
|
Загрузка…
Ссылка в новой задаче