Bug 1398729 - Support devtools.panels.elements sidebar.setExpression API method.
MozReview-Commit-ID: 2tRLF59o8Bg
--- a/browser/components/extensions/ext-c-devtools-panels.js
+++ b/browser/components/extensions/ext-c-devtools-panels.js
@@ -217,16 +217,25 @@ class ChildDevToolsInspectorSidebar exte
setObject(jsonObject, rootTitle) {
return context.cloneScope.Promise.resolve().then(() => {
return context.childManager.callParentAsyncFunction(
"devtools.panels.elements.Sidebar.setObject",
[id, jsonObject, rootTitle]
);
});
},
+
+ setExpression(evalExpression, rootTitle) {
+ return context.cloneScope.Promise.resolve().then(() => {
+ return context.childManager.callParentAsyncFunction(
+ "devtools.panels.elements.Sidebar.setExpression",
+ [id, evalExpression, rootTitle]
+ );
+ });
+ },
};
}
}
this.devtools_panels = class extends ExtensionAPI {
getAPI(context) {
const themeChangeObserver = ExtensionChildDevToolsUtils.getThemeChangeObserver();
--- a/browser/components/extensions/ext-devtools-panels.js
+++ b/browser/components/extensions/ext-devtools-panels.js
@@ -439,16 +439,27 @@ class ParentDevToolsInspectorSidebar {
}
}
}
const sidebarsById = new Map();
this.devtools_panels = class extends ExtensionAPI {
getAPI(context) {
+ // Lazily retrieved inspectedWindow actor front per child context
+ // (used by Sidebar.setExpression).
+ let waitForInspectedWindowFront;
+
+ // TODO(rpl): retrive a more detailed callerInfo object, like the filename and
+ // lineNumber of the actual extension called, in the child process.
+ const callerInfo = {
+ addonId: context.extension.id,
+ url: context.extension.baseURI.spec,
+ };
+
// An incremental "per context" id used in the generated devtools panel id.
let nextPanelId = 0;
const toolboxSelectionObserver = new DevToolsSelectionObserver(context);
function newBasePanelId() {
return `${context.extension.id}-${context.contextId}-${nextPanelId++}`;
}
@@ -487,16 +498,37 @@ this.devtools_panels = class extends Ext
// The following methods are used internally to allow the sidebar API
// piece that is running in the child process to asks the parent process
// to execute the sidebar methods.
Sidebar: {
setObject(sidebarId, jsonObject, rootTitle) {
const sidebar = sidebarsById.get(sidebarId);
return sidebar.setObject(jsonObject, rootTitle);
},
+ async setExpression(sidebarId, evalExpression, rootTitle) {
+ const sidebar = sidebarsById.get(sidebarId);
+
+ if (!waitForInspectedWindowFront) {
+ waitForInspectedWindowFront = getInspectedWindowFront(context);
+ }
+
+ const front = await waitForInspectedWindowFront;
+ const evalOptions = Object.assign({}, getToolboxEvalOptions(context));
+ const evalResult = await front.eval(callerInfo, evalExpression, evalOptions);
+
+ let jsonObject;
+
+ if (evalResult.exceptionInfo) {
+ jsonObject = evalResult.exceptionInfo;
+ } else {
+ jsonObject = evalResult.value;
+ }
+
+ return sidebar.setObject(jsonObject, rootTitle);
+ },
},
},
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
@@ -175,17 +175,16 @@
"name": "height",
"type": "string",
"description": "A CSS-like size specification, such as <code>'100px'</code> or <code>'12ex'</code>."
}
]
},
{
"name": "setExpression",
- "unsupported": true,
"async": "callback",
"type": "function",
"description": "Sets an expression that is evaluated within the inspected page. The result is displayed in the sidebar pane.",
"parameters": [
{
"name": "expression",
"type": "string",
"description": "An expression to be evaluated in context of the inspected page. JavaScript objects and DOM nodes are displayed in an expandable tree similar to the console/watch."
--- a/browser/components/extensions/test/browser/browser_ext_devtools_panels_elements_sidebar.js
+++ b/browser/components/extensions/test/browser/browser_ext_devtools_panels_elements_sidebar.js
@@ -1,41 +1,52 @@
/* -*- 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");
+XPCOMUtils.defineLazyModuleGetter(this, "ContentTaskUtils",
+ "resource://testing-common/ContentTaskUtils.jsm");
function isActiveSidebarTabTitle(inspector, expectedTabTitle, message) {
const actualTabTitle = inspector.panelDoc.querySelector(".tabs-menu-item.is-active").innerText;
is(actualTabTitle, expectedTabTitle, message);
}
add_task(async function test_devtools_panels_elements_sidebar() {
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/");
async function devtools_page() {
const sidebar1 = await browser.devtools.panels.elements.createSidebarPane("Test Sidebar 1");
const sidebar2 = await browser.devtools.panels.elements.createSidebarPane("Test Sidebar 2");
+ const sidebar3 = await browser.devtools.panels.elements.createSidebarPane("Test Sidebar 3");
const onShownListener = (event, sidebarInstance) => {
browser.test.sendMessage(`devtools_sidebar_${event}`, sidebarInstance);
};
sidebar1.onShown.addListener(() => onShownListener("shown", "sidebar1"));
sidebar2.onShown.addListener(() => onShownListener("shown", "sidebar2"));
+ sidebar3.onShown.addListener(() => onShownListener("shown", "sidebar3"));
+
sidebar1.onHidden.addListener(() => onShownListener("hidden", "sidebar1"));
sidebar2.onHidden.addListener(() => onShownListener("hidden", "sidebar2"));
+ sidebar3.onHidden.addListener(() => onShownListener("hidden", "sidebar3"));
sidebar1.setObject({propertyName: "propertyValue"}, "Optional Root Object Title");
sidebar2.setObject({anotherPropertyName: 123});
+ // Refresh the sidebar content on every inspector selection.
+ browser.devtools.panels.elements.onSelectionChanged.addListener(() => {
+ sidebar3.setExpression("$0 && $0.tagName", "Selected Element tagName");
+ });
+
browser.test.sendMessage("devtools_page_loaded");
}
let extension = ExtensionTestUtils.loadExtension({
manifest: {
devtools_page: "devtools_page.html",
},
files: {
@@ -110,25 +121,59 @@ add_task(async function test_devtools_pa
ok(sidebarPanel2, "Got a rendered sidebar panel for the second registered extension sidebar");
is(sidebarPanel2.querySelectorAll("table.treeTable").length, 1,
"The second sidebar panel contains a rendered TreeView component");
is(sidebarPanel2.querySelectorAll("table.treeTable .numberCell").length, 1,
"The TreeView component contains the expected a cell of type number.");
+ inspector.sidebar.show(sidebarIds[2]);
+
+ const shownSidebarInstance3 = await extension.awaitMessage("devtools_sidebar_shown");
+ const hiddenSidebarInstance2 = await extension.awaitMessage("devtools_sidebar_hidden");
+
+ is(shownSidebarInstance3, "sidebar3", "Got the shown event on the third extension sidebar");
+ is(hiddenSidebarInstance2, "sidebar2", "Got the hidden event on the second extension sidebar");
+
+ isActiveSidebarTabTitle(inspector, "Test Sidebar 3",
+ "Got the expected title on the active sidebar tab");
+
+ const sidebarPanel3 = inspector.sidebar.getTabPanel(sidebarIds[2]);
+
+ ok(sidebarPanel3, "Got a rendered sidebar panel for the third registered extension sidebar");
+
+ info("Waiting for the third panel to be rendered");
+ await ContentTaskUtils.waitForCondition(() => {
+ return sidebarPanel3.querySelectorAll("table.treeTable").length > 0;
+ });
+
+ is(sidebarPanel3.querySelectorAll("table.treeTable").length, 1,
+ "The third sidebar panel contains a rendered TreeView component");
+
+ const treeViewStringValues = sidebarPanel3.querySelectorAll("table.treeTable .stringCell");
+
+ is(treeViewStringValues.length, 1,
+ "The TreeView component contains the expected content of type string.");
+
+ is(treeViewStringValues[0].innerText, "\"BODY\"",
+ "Got the expected content in the sidebar.setExpression rendered TreeView");
+
await extension.unload();
is(Array.from(toolbox._inspectorExtensionSidebars.keys()).length, 0,
"All the registered sidebars have been unregistered on extension unload");
is(inspector.sidebar.getTabPanel(sidebarIds[0]), undefined,
"The first registered sidebar has been removed");
is(inspector.sidebar.getTabPanel(sidebarIds[1]), undefined,
"The second registered sidebar has been removed");
+ is(inspector.sidebar.getTabPanel(sidebarIds[2]), undefined,
+ "The third registered sidebar has been removed");
+
await gDevTools.closeToolbox(target);
await target.destroy();
await BrowserTestUtils.removeTab(tab);
});