Bug 1341304 - Implement devtools.panels.elements.onSelectionChanged. draft
authorLuca Greco <lgreco@mozilla.com>
Thu, 01 Jun 2017 20:06:53 +0200
changeset 609731 fbe427893523f6184de1c890a999bb55fe848a3a
parent 609730 bfa143d099fde404ba0f3c11d5c14bee6f294665
child 637653 11ba9c2497b122ddf8f3684bca49c5f1ae3315fc
push id68668
push userluca.greco@alcacoop.it
push dateMon, 17 Jul 2017 11:47:51 +0000
bugs1341304
milestone56.0a1
Bug 1341304 - Implement devtools.panels.elements.onSelectionChanged. 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
@@ -62,16 +62,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);
+});