Bug 1403130 - Allow DOMNodes and cyclic objects to be rendered with the sidebar.setExpression API method. draft
authorLuca Greco <lgreco@mozilla.com>
Thu, 12 Oct 2017 15:55:47 +0200
changeset 748481 15427462ce2cf1ffcaa038da6e06ebd773917766
parent 748480 21c769b58b0ca6bbae9bfc894dbe520e6ac66453
push id97184
push userluca.greco@alcacoop.it
push dateMon, 29 Jan 2018 22:01:37 +0000
bugs1403130
milestone59.0a1
Bug 1403130 - Allow DOMNodes and cyclic objects to be rendered with the sidebar.setExpression API method. MozReview-Commit-ID: AjHn7KfVhas
browser/components/extensions/ext-devtools-panels.js
browser/components/extensions/test/browser/browser-common.ini
browser/components/extensions/test/browser/browser_ext_devtools_panels_elements_sidebar.js
--- a/browser/components/extensions/ext-devtools-panels.js
+++ b/browser/components/extensions/ext-devtools-panels.js
@@ -435,16 +435,27 @@ class ParentDevToolsInspectorSidebar {
 
     if (this.extensionSidebar) {
       this.extensionSidebar.setObject(object);
     } else {
       // Defer the sidebar.setObject call.
       this._initializeSidebar = () => this.extensionSidebar.setObject(object);
     }
   }
+
+  setObjectValueGrip(objectValueGrip, rootTitle) {
+    if (this.extensionSidebar) {
+      this.extensionSidebar.setObjectValueGrip(objectValueGrip, rootTitle);
+    } else {
+      // Defer the sidebar.setObject call.
+      this._initializeSidebar = () => {
+        this.extensionSidebar.setObjectValueGrip(objectValueGrip, rootTitle);
+      };
+    }
+  }
 }
 
 const sidebarsById = new Map();
 
 this.devtools_panels = class extends ExtensionAPI {
   getAPI(context) {
     // Lazily retrieved inspectedWindow actor front per child context
     // (used by Sidebar.setExpression).
@@ -508,28 +519,30 @@ this.devtools_panels = class extends Ext
               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 evalOptions = Object.assign({
+                  evalResultAsGrip: true,
+                }, 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);
                 }
 
-                return sidebar.setObject(jsonObject, rootTitle);
+                return sidebar.setObjectValueGrip(evalResult.valueGrip, 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);
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -79,16 +79,18 @@ skip-if = (os == 'win' && !debug) # bug 
 [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_devtools_panels_elements_sidebar.js]
+support-files =
+  ../../../../../devtools/client/inspector/extensions/test/head_devtools_inspector_sidebar.js
 [browser_ext_find.js]
 skip-if = (os == 'win' && ccov) # Bug 1423667
 [browser_ext_geckoProfiler_symbolicate.js]
 [browser_ext_getViews.js]
 [browser_ext_history_redirect.js]
 [browser_ext_identity_indication.js]
 [browser_ext_incognito_views.js]
 [browser_ext_incognito_popup.js]
--- 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
@@ -4,21 +4,61 @@
 
 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");
 
+Services.scriptloader.loadSubScript(new URL("head_devtools_inspector_sidebar.js", gTestPath).href,
+                                    this);
+
 function isActiveSidebarTabTitle(inspector, expectedTabTitle, message) {
   const actualTabTitle = inspector.panelDoc.querySelector(".tabs-menu-item.is-active").innerText;
   is(actualTabTitle, expectedTabTitle, message);
 }
 
+function testSetObjectSidebarPanel(panel, expectedCellType, expectedTitle) {
+  is(panel.querySelectorAll("table.treeTable").length, 1,
+     "The second sidebar panel contains a rendered TreeView component");
+
+  is(panel.querySelectorAll(`table.treeTable .${expectedCellType}Cell`).length, 1,
+     "The TreeView component contains the expected a cell of type number.");
+
+  if (expectedTitle) {
+    const panelTree = panel.querySelector("table.treeTable");
+    ok(
+      panelTree.innerText.includes(expectedTitle),
+      "The optional root object title has been included in the object tree"
+    );
+  }
+}
+
+async function testSidebarPanelSelect(extension, inspector, tabId, expected) {
+  const {
+    sidebarShown,
+    sidebarHidden,
+    activeSidebarTabTitle,
+  } = expected;
+
+  inspector.sidebar.show(tabId);
+
+  const shown = await extension.awaitMessage("devtools_sidebar_shown");
+  is(shown, sidebarShown, "Got the shown event on the second extension sidebar");
+
+  if (sidebarHidden) {
+    const hidden = await extension.awaitMessage("devtools_sidebar_hidden");
+    is(hidden, sidebarHidden, "Got the hidden event on the first extension sidebar");
+  }
+
+  isActiveSidebarTabTitle(inspector, activeSidebarTabTitle,
+                          "Got the expected title on the active sidebar tab");
+}
+
 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");
 
@@ -29,24 +69,31 @@ add_task(async function test_devtools_pa
     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");
+      const expression = `
+        var obj = Object.create(null);
+        obj.prop1 = 123;
+        obj[Symbol('sym1')] = 456;
+        obj.cyclic = obj;
+        obj;
+      `;
+      sidebar1.setExpression(expression, "sidebar.setExpression rootTitle");
     });
 
+    sidebar2.setObject({anotherPropertyName: 123});
+    sidebar3.setObject({propertyName: "propertyValue"}, "Optional Root Object Title");
+
     browser.test.sendMessage("devtools_page_loaded");
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       devtools_page: "devtools_page.html",
     },
     files: {
@@ -75,92 +122,70 @@ add_task(async function test_devtools_pa
   const waitInspector = toolbox.once("inspector-selected");
   toolbox.selectTool("inspector");
   await waitInspector;
 
   const sidebarIds = Array.from(toolbox._inspectorExtensionSidebars.keys());
 
   const inspector = await toolbox.getPanel("inspector");
 
+  info("Test extension inspector sidebar 1 (sidebar.setExpression)");
+
   inspector.sidebar.show(sidebarIds[0]);
 
   const shownSidebarInstance = await extension.awaitMessage("devtools_sidebar_shown");
 
   is(shownSidebarInstance, "sidebar1", "Got the shown event on the first extension sidebar");
 
   isActiveSidebarTabTitle(inspector, "Test Sidebar 1",
                           "Got the expected title on the active sidebar tab");
 
   const sidebarPanel1 = inspector.sidebar.getTabPanel(sidebarIds[0]);
 
   ok(sidebarPanel1, "Got a rendered sidebar panel for the first registered extension sidebar");
 
-  is(sidebarPanel1.querySelectorAll("table.treeTable").length, 1,
-     "The first sidebar panel contains a rendered TreeView component");
-
-  is(sidebarPanel1.querySelectorAll("table.treeTable .stringCell").length, 1,
-     "The TreeView component contains the expected number of string cells.");
+  info("Waiting for the first panel to be rendered");
 
-  const sidebarPanel1Tree = sidebarPanel1.querySelector("table.treeTable");
-  ok(
-    sidebarPanel1Tree.innerText.includes("Optional Root Object Title"),
-    "The optional root object title has been included in the object tree"
-  );
-
-  inspector.sidebar.show(sidebarIds[1]);
+  // Verify that the panel contains an ObjectInspector, with the expected number of nodes
+  // and with the expected property names.
+  await testSetExpressionSidebarPanel(sidebarPanel1, {
+    nodesLength: 4,
+    propertiesNames: ["cyclic", "prop1", "Symbol(sym1)"],
+    rootTitle: "sidebar.setExpression rootTitle",
+  });
 
-  const shownSidebarInstance2 = await extension.awaitMessage("devtools_sidebar_shown");
-  const hiddenSidebarInstance1 = await extension.awaitMessage("devtools_sidebar_hidden");
+  info("Test extension inspector sidebar 2 (sidebar.setObject without a root title)");
 
-  is(shownSidebarInstance2, "sidebar2", "Got the shown event on the second extension sidebar");
-  is(hiddenSidebarInstance1, "sidebar1", "Got the hidden event on the first extension sidebar");
-
-  isActiveSidebarTabTitle(inspector, "Test Sidebar 2",
-                          "Got the expected title on the active sidebar tab");
+  await testSidebarPanelSelect(extension, inspector, sidebarIds[1], {
+    sidebarShown: "sidebar2",
+    sidebarHidden: "sidebar1",
+    activeSidebarTabTitle: "Test Sidebar 2",
+  });
 
   const sidebarPanel2 = inspector.sidebar.getTabPanel(sidebarIds[1]);
 
   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");
+  testSetObjectSidebarPanel(sidebarPanel2, "number");
 
-  is(sidebarPanel2.querySelectorAll("table.treeTable .numberCell").length, 1,
-     "The TreeView component contains the expected a cell of type number.");
-
-  inspector.sidebar.show(sidebarIds[2]);
+  info("Test extension inspector sidebar 3 (sidebar.setObject with a root title)");
 
-  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");
+  await testSidebarPanelSelect(extension, inspector, sidebarIds[2], {
+    sidebarShown: "sidebar3",
+    sidebarHidden: "sidebar2",
+    activeSidebarTabTitle: "Test Sidebar 3",
+  });
 
   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");
+  testSetObjectSidebarPanel(sidebarPanel3, "string", "Optional Root Object Title");
 
-  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");
+  info("Unloading the extension and check that all the sidebar have been removed");
 
   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");