Bug 1341304 - Implement devtools.panels.elements.onSelectionChanged.
MozReview-Commit-ID: HMKvnk3wDyX
--- 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);
+});