Bug 1262439 - 2 - Add a new pickColor inspector method to use the new eye-dropper highlighter; r=bgrins draft
authorPatrick Brosset <pbrosset@mozilla.com>
Thu, 09 Jun 2016 13:00:28 +0200
changeset 385008 b44f042366f442406da6230f1b936a4aabcc1510
parent 385007 db2cfd6ef42a3d9c961dee4c994d7b4a6d93b3cb
child 385009 4763dcb72f8472d40ebc7d7d711f6cce68d496e2
push id22388
push userpbrosset@mozilla.com
push dateThu, 07 Jul 2016 13:03:20 +0000
reviewersbgrins
bugs1262439
milestone50.0a1
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
devtools/server/actors/inspector.js
devtools/server/tests/mochitest/chrome.ini
devtools/server/tests/mochitest/inspector-eyedropper.html
devtools/server/tests/mochitest/test_inspector-pick-color.html
devtools/shared/specs/inspector.js
--- 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;