Bug 1341304 - Implement devtools.panels.elements.onSelectionChanged. r=aswan
authorLuca Greco <lgreco@mozilla.com>
Thu, 01 Jun 2017 20:06:53 +0200
changeset 369366 2fb0f93b7b5bc9880f57534673f0e27c6b5a789e
parent 369365 bf290e0676b5a16a6851369327fbc4cb1ad686d3
child 369367 32af7843b1cba64ef99fecd7e7c6ac9e9177955b
push id46674
push userluca.greco@alcacoop.it
push dateTue, 18 Jul 2017 13:33:45 +0000
treeherderautoland@2fb0f93b7b5b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan
bugs1341304
milestone56.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1341304 - Implement devtools.panels.elements.onSelectionChanged. r=aswan MozReview-Commit-ID: HMKvnk3wDyX
browser/components/extensions/ext-devtools-panels.js
browser/components/extensions/schemas/devtools_panels.json
browser/components/extensions/test/browser/browser-common.ini
browser/components/extensions/test/browser/browser_ext_devtools_panels_elements.js
--- a/browser/components/extensions/ext-devtools-panels.js
+++ b/browser/components/extensions/ext-devtools-panels.js
@@ -211,24 +211,89 @@ class ParentDevToolsPanel {
 
     this.context = null;
     this.toolbox = null;
     this.waitTopLevelContext = null;
     this._resolveTopLevelContext = null;
   }
 }
 
+class DevToolsSelectionObserver extends EventEmitter {
+  constructor(context) {
+    if (!context.devToolsToolbox) {
+      // This should never happen when this constructor is called with a valid
+      // devtools extension context.
+      throw Error("Missing mandatory toolbox");
+    }
+
+    super();
+    context.callOnClose(this);
+
+    this.toolbox = context.devToolsToolbox;
+    this.onSelected = this.onSelected.bind(this);
+    this.initialized = false;
+  }
+
+  on(...args) {
+    this.lazyInit();
+    super.on.apply(this, args);
+  }
+
+  once(...args) {
+    this.lazyInit();
+    super.once.apply(this, args);
+  }
+
+  async lazyInit() {
+    if (!this.initialized) {
+      this.initialized = true;
+      this.toolbox.on("selection-changed", this.onSelected);
+    }
+  }
+
+  close() {
+    if (this.destroyed) {
+      throw new Error("Unable to close a destroyed DevToolsSelectionObserver");
+    }
+
+    if (this.initialized) {
+      this.toolbox.off("selection-changed", this.onSelected);
+    }
+
+    this.toolbox = null;
+    this.destroyed = true;
+  }
+
+  onSelected(event) {
+    this.emit("selectionChanged");
+  }
+}
+
 this.devtools_panels = class extends ExtensionAPI {
   getAPI(context) {
     // An incremental "per context" id used in the generated devtools panel id.
     let nextPanelId = 0;
 
+    const toolboxSelectionObserver = new DevToolsSelectionObserver(context);
+
     return {
       devtools: {
         panels: {
+          elements: {
+            onSelectionChanged: new EventManager(
+              context, "devtools.panels.elements.onSelectionChanged", fire => {
+                const listener = (eventName) => {
+                  fire.async();
+                };
+                toolboxSelectionObserver.on("selectionChanged", listener);
+                return () => {
+                  toolboxSelectionObserver.off("selectionChanged", listener);
+                };
+              }).api(),
+          },
           create(title, icon, url) {
             // Get a fallback icon from the manifest data.
             if (icon === "" && context.extension.manifest.icons) {
               const iconInfo = IconDetails.getPreferredIcon(context.extension.manifest.icons,
                                                             context.extension, 128);
               icon = iconInfo ? iconInfo.icon : "";
             }
 
--- a/browser/components/extensions/schemas/devtools_panels.json
+++ b/browser/components/extensions/schemas/devtools_panels.json
@@ -12,17 +12,17 @@
     "types": [
       {
         "id": "ElementsPanel",
         "type": "object",
         "description": "Represents the Elements panel.",
         "events": [
           {
             "name": "onSelectionChanged",
-            "unsupported": true,
+            "type": "function",
             "description": "Fired when an object is selected in the panel."
           }
         ],
         "functions": [
           {
             "name": "createSidebarPane",
             "unsupported": true,
             "type": "function",
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -63,16 +63,17 @@ skip-if = (os == 'win' && !debug) # bug 
 [browser_ext_contextMenus_urlPatterns.js]
 [browser_ext_currentWindow.js]
 [browser_ext_devtools_inspectedWindow.js]
 [browser_ext_devtools_inspectedWindow_eval_bindings.js]
 [browser_ext_devtools_inspectedWindow_reload.js]
 [browser_ext_devtools_network.js]
 [browser_ext_devtools_page.js]
 [browser_ext_devtools_panel.js]
+[browser_ext_devtools_panels_elements.js]
 [browser_ext_geckoProfiler_symbolicate.js]
 [browser_ext_getViews.js]
 [browser_ext_identity_indication.js]
 [browser_ext_incognito_views.js]
 [browser_ext_incognito_popup.js]
 [browser_ext_lastError.js]
 [browser_ext_menus.js]
 [browser_ext_omnibox.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_devtools_panels_elements.js
@@ -0,0 +1,92 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
+                                  "resource://devtools/client/framework/gDevTools.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "devtools",
+                                  "resource://devtools/shared/Loader.jsm");
+
+add_task(async function test_devtools_panels_elements_onSelectionChanged() {
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/");
+
+  function devtools_page() {
+    let doTabReload = true;
+
+    browser.devtools.panels.elements.onSelectionChanged.addListener(async () => {
+      const [
+        evalResult, exceptionInfo,
+      ] = await browser.devtools.inspectedWindow.eval("$0 && $0.tagName");
+
+      if (exceptionInfo) {
+        browser.test.fail("Unexpected exceptionInfo on inspectedWindow.eval: " +
+                          JSON.stringify(exceptionInfo));
+      }
+
+      browser.test.sendMessage("devtools_eval_result", evalResult);
+
+      if (doTabReload) {
+        // Force a reload to test that the expected onSelectionChanged events are sent
+        // while the page is navigating and once it has been fully reloaded.
+        doTabReload = false;
+        await browser.devtools.inspectedWindow.eval("window.location.reload();");
+      }
+    });
+
+    browser.test.sendMessage("devtools_page_loaded");
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      devtools_page: "devtools_page.html",
+    },
+    files: {
+      "devtools_page.html": `<!DOCTYPE html>
+      <html>
+       <head>
+         <meta charset="utf-8">
+       </head>
+       <body>
+         <script src="devtools_page.js"></script>
+       </body>
+      </html>`,
+      "devtools_page.js": devtools_page,
+    },
+  });
+
+  await extension.startup();
+
+  let target = devtools.TargetFactory.forTab(tab);
+
+  const toolbox = await gDevTools.showToolbox(target, "webconsole");
+  info("developer toolbox opened");
+
+  await extension.awaitMessage("devtools_page_loaded");
+
+  toolbox.selectTool("inspector");
+
+  const evalResult = await extension.awaitMessage("devtools_eval_result");
+
+  is(evalResult, "BODY", "Got the expected onSelectionChanged once the inspector is selected");
+
+  const evalResultNavigating = await extension.awaitMessage("devtools_eval_result");
+
+  is(evalResultNavigating, undefined, "Got the expected onSelectionChanged once the tab is navigating");
+
+  const evalResultNavigated = await extension.awaitMessage("devtools_eval_result");
+
+  is(evalResultNavigated, undefined, "Got the expected onSelectionChanged once the tab navigated");
+
+  const evalResultReloaded = await extension.awaitMessage("devtools_eval_result");
+
+  is(evalResultReloaded, "BODY",
+     "Got the expected onSelectionChanged once the tab has been completely reloaded");
+
+  await gDevTools.closeToolbox(target);
+
+  await target.destroy();
+
+  await extension.unload();
+
+  await BrowserTestUtils.removeTab(tab);
+});