Bug 1262439 - 2 - Add a new pickColor inspector method to use the new eye-dropper highlighter; r=bgrins
The inspector actor now has a new method that can be used to pick a
color from the page. This method just instantiates the eye-dropper
highlighter and shows it on the page.
The method doesn't return the color because this requires user interaction.
Instead, it returns immediately and an event is sent later, when the user
has selected a color or escaped.
MozReview-Commit-ID: cjadLyNXQd
--- a/devtools/server/actors/inspector.js
+++ b/devtools/server/actors/inspector.js
@@ -61,17 +61,19 @@ const object = require("sdk/util/object"
const events = require("sdk/event/core");
const {Class} = require("sdk/core/heritage");
const {WalkerSearch} = require("devtools/server/actors/utils/walker-search");
const {PageStyleActor, getFontPreviewData} = require("devtools/server/actors/styles");
const {
HighlighterActor,
CustomHighlighterActor,
isTypeRegistered,
+ HighlighterEnvironment
} = require("devtools/server/actors/highlighters");
+const {EyeDropper} = require("devtools/server/actors/highlighters/eye-dropper");
const {
isAnonymous,
isNativeAnonymous,
isXBLAnonymous,
isShadowAnonymous,
getFrameElement
} = require("devtools/shared/layout/utils");
const {getLayoutChangesObserver, releaseLayoutChangesObserver} =
@@ -2585,21 +2587,27 @@ var WalkerActor = protocol.ActorClassWit
/**
* Server side of the inspector actor, which is used to create
* inspector-related actors, including the walker.
*/
var InspectorActor = exports.InspectorActor = protocol.ActorClassWithSpec(inspectorSpec, {
initialize: function (conn, tabActor) {
protocol.Actor.prototype.initialize.call(this, conn);
this.tabActor = tabActor;
+
+ this._onColorPicked = this._onColorPicked.bind(this);
+ this._onColorPickCanceled = this._onColorPickCanceled.bind(this);
+ this.destroyEyeDropper = this.destroyEyeDropper.bind(this);
},
destroy: function () {
protocol.Actor.prototype.destroy.call(this);
+ this.destroyEyeDropper();
+
this._highlighterPromise = null;
this._pageStylePromise = null;
this._walkerPromise = null;
this.walker = null;
this.tabActor = null;
},
// Forces destruction of the actor and all its children
@@ -2737,16 +2745,76 @@ var InspectorActor = exports.InspectorAc
: nodeDocument(node.rawNode);
if (!document) {
return url;
}
let baseURI = Services.io.newURI(document.location.href, null, null);
return Services.io.newURI(url, null, baseURI).spec;
+ },
+
+ /**
+ * Create an instance of the eye-dropper highlighter and store it on this._eyeDropper.
+ * Note that for now, a new instance is created every time to deal with page navigation.
+ */
+ createEyeDropper: function () {
+ this.destroyEyeDropper();
+ this._highlighterEnv = new HighlighterEnvironment();
+ this._highlighterEnv.initFromTabActor(this.tabActor);
+ this._eyeDropper = new EyeDropper(this._highlighterEnv);
+ },
+
+ /**
+ * Destroy the current eye-dropper highlighter instance.
+ */
+ destroyEyeDropper: function () {
+ if (this._eyeDropper) {
+ this.cancelPickColorFromPage();
+ this._eyeDropper.destroy();
+ this._eyeDropper = null;
+ this._highlighterEnv.destroy();
+ this._highlighterEnv = null;
+ }
+ },
+
+ /**
+ * Pick a color from the page using the eye-dropper. This method doesn't return anything
+ * but will cause events to be sent to the front when a color is picked or when the user
+ * cancels the picker.
+ * @param {Object} options
+ */
+ pickColorFromPage: function (options) {
+ this.createEyeDropper();
+ this._eyeDropper.show(this.window.document.documentElement, options);
+ this._eyeDropper.once("selected", this._onColorPicked);
+ this._eyeDropper.once("canceled", this._onColorPickCanceled);
+ events.once(this.tabActor, "will-navigate", this.destroyEyeDropper);
+ },
+
+ /**
+ * After the pickColorFromPage method is called, the only way to dismiss the eye-dropper
+ * highlighter is for the user to click in the page and select a color. If you need to
+ * dismiss the eye-dropper programatically instead, use this method.
+ */
+ cancelPickColorFromPage: function () {
+ if (this._eyeDropper) {
+ this._eyeDropper.hide();
+ this._eyeDropper.off("selected", this._onColorPicked);
+ this._eyeDropper.off("canceled", this._onColorPickCanceled);
+ events.off(this.tabActor, "will-navigate", this.destroyEyeDropper);
+ }
+ },
+
+ _onColorPicked: function (e, color) {
+ events.emit(this, "color-picked", color);
+ },
+
+ _onColorPickCanceled: function () {
+ events.emit(this, "color-pick-canceled");
}
});
// Exported for test purposes.
exports._documentWalker = DocumentWalker;
function nodeDocument(node) {
if (Cu.isDeadWrapper(node)) {
--- a/devtools/server/tests/mochitest/chrome.ini
+++ b/devtools/server/tests/mochitest/chrome.ini
@@ -6,16 +6,17 @@ support-files =
Debugger.Source.prototype.element.js
Debugger.Source.prototype.element-2.js
Debugger.Source.prototype.element.html
director-helpers.js
hello-actor.js
inspector_css-properties.html
inspector_getImageData.html
inspector-delay-image-response.sjs
+ inspector-eyedropper.html
inspector-helpers.js
inspector-search-data.html
inspector-styles-data.css
inspector-styles-data.html
inspector-traversal-data.html
large-image.jpg
memory-helpers.js
nonchrome_unsafeDereference.html
@@ -70,16 +71,17 @@ skip-if = buildapp == 'mulet'
[test_inspector_getNodeFromActor.html]
[test_inspector-hide.html]
[test_inspector-insert.html]
[test_inspector-mutations-attr.html]
[test_inspector-mutations-events.html]
[test_inspector-mutations-childlist.html]
[test_inspector-mutations-frameload.html]
[test_inspector-mutations-value.html]
+[test_inspector-pick-color.html]
[test_inspector-pseudoclass-lock.html]
[test_inspector-release.html]
[test_inspector-reload.html]
[test_inspector-remove.html]
[test_inspector-resize.html]
[test_inspector-resolve-url.html]
[test_inspector-retain.html]
[test_inspector-search.html]
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/mochitest/inspector-eyedropper.html
@@ -0,0 +1,18 @@
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Inspector Eyedropper tests</title>
+ <style>
+ html {
+ background: black;
+ }
+ </style>
+ <script type="text/javascript">
+ window.onload = function() {
+ window.opener.postMessage('ready', '*');
+ };
+ </script>
+</head>
+</body>
+</body>
+</html>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-pick-color.html
@@ -0,0 +1,101 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test that the inspector actor has the pickColorFromPage and cancelPickColorFromPage
+methods and that when a color is picked the color-picked event is emitted and that when
+the eyedropper is dimissed, the color-pick-canceled event is emitted.
+https://bugzilla.mozilla.org/show_bug.cgi?id=1262439
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1262439</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+window.onload = function() {
+ const Cu = Components.utils;
+ Cu.import("resource://devtools/shared/Loader.jsm");
+ const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
+ const {InspectorFront} = devtools.require("devtools/shared/fronts/inspector");
+ const {console} = Cu.import("resource://gre/modules/Console.jsm", {});
+
+ SimpleTest.waitForExplicitFinish();
+
+ let win = null;
+ let inspector = null;
+
+ addAsyncTest(function*() {
+ info("Setting up inspector actor");
+
+ let url = document.getElementById("inspectorContent").href;
+
+ yield new Promise(resolve => {
+ attachURL(url, function(err, client, tab, doc) {
+ win = doc.defaultView;
+ inspector = InspectorFront(client, tab);
+ resolve();
+ });
+ });
+
+ runNextTest();
+ });
+
+ addAsyncTest(function*() {
+ info("Start picking a color from the page");
+ yield inspector.pickColorFromPage();
+
+ info("Click in the page and make sure a color-picked event is received");
+ let onColorPicked = waitForEvent("color-picked");
+ win.document.body.click();
+ let color = yield onColorPicked;
+
+ is(color, "#000000", "The color-picked event was received with the right color");
+
+ runNextTest();
+ });
+
+ addAsyncTest(function*() {
+ info("Start picking a color from the page");
+ yield inspector.pickColorFromPage();
+
+ info("Use the escape key to dismiss the eyedropper");
+ let onPickCanceled = waitForEvent("color-pick-canceled");
+
+ let keyboardEvent = win.document.createEvent("KeyboardEvent");
+ keyboardEvent.initKeyEvent("keydown", true, true, win, false, false,
+ false, false, 27, 0);
+ win.document.dispatchEvent(keyboardEvent);
+
+ yield onPickCanceled;
+ ok(true, "The color-pick-canceled event was received");
+
+ runNextTest();
+ });
+
+ addAsyncTest(function*() {
+ info("Start picking a color from the page");
+ yield inspector.pickColorFromPage();
+
+ info("And cancel the color picking");
+ yield inspector.cancelPickColorFromPage();
+
+ runNextTest();
+ });
+
+ function waitForEvent(name) {
+ return new Promise(resolve => inspector.once(name, resolve));
+ }
+
+ runNextTest();
+};
+ </script>
+</head>
+<body>
+<a id="inspectorContent" target="_blank" href="inspector-eyedropper.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+</pre>
+</body>
+</html>
--- a/devtools/shared/specs/inspector.js
+++ b/devtools/shared/specs/inspector.js
@@ -366,16 +366,26 @@ const walkerSpec = generateActorSpec({
}
});
exports.walkerSpec = walkerSpec;
const inspectorSpec = generateActorSpec({
typeName: "inspector",
+ events: {
+ "color-picked": {
+ type: "colorPicked",
+ color: Arg(0, "string")
+ },
+ "color-pick-canceled": {
+ type: "colorPickCanceled"
+ }
+ },
+
methods: {
getWalker: {
request: {
options: Arg(0, "nullable:json")
},
response: {
walker: RetVal("domwalker")
}
@@ -404,13 +414,21 @@ const inspectorSpec = generateActorSpec(
},
getImageDataFromURL: {
request: {url: Arg(0), maxDim: Arg(1, "nullable:number")},
response: RetVal("imageData")
},
resolveRelativeURL: {
request: {url: Arg(0, "string"), node: Arg(1, "nullable:domnode")},
response: {value: RetVal("string")}
+ },
+ pickColorFromPage: {
+ request: {options: Arg(1, "nullable:json")},
+ response: {}
+ },
+ cancelPickColorFromPage: {
+ request: {},
+ response: {}
}
}
});
exports.inspectorSpec = inspectorSpec;