summaryrefslogtreecommitdiffstats
path: root/devtools/client/inspector/extensions/test/browser_inspector_extension_sidebar.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--devtools/client/inspector/extensions/test/browser_inspector_extension_sidebar.js451
1 files changed, 451 insertions, 0 deletions
diff --git a/devtools/client/inspector/extensions/test/browser_inspector_extension_sidebar.js b/devtools/client/inspector/extensions/test/browser_inspector_extension_sidebar.js
new file mode 100644
index 0000000000..740b1fda13
--- /dev/null
+++ b/devtools/client/inspector/extensions/test/browser_inspector_extension_sidebar.js
@@ -0,0 +1,451 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const SIDEBAR_ID = "an-extension-sidebar";
+const SIDEBAR_TITLE = "Sidebar Title";
+
+let extension;
+let fakeExtCallerInfo;
+
+let toolbox;
+let inspector;
+
+add_task(async function setupExtensionSidebar() {
+ extension = ExtensionTestUtils.loadExtension({
+ background() {
+ // This is just an empty extension used to ensure that the caller extension uuid
+ // actually exists.
+ },
+ });
+
+ await extension.startup();
+
+ fakeExtCallerInfo = {
+ url: WebExtensionPolicy.getByID(extension.id).getURL(
+ "fake-caller-script.js"
+ ),
+ lineNumber: 1,
+ addonId: extension.id,
+ };
+
+ const res = await openInspectorForURL("about:blank");
+ inspector = res.inspector;
+ toolbox = res.toolbox;
+
+ const onceSidebarCreated = toolbox.once(
+ `extension-sidebar-created-${SIDEBAR_ID}`
+ );
+ toolbox.registerInspectorExtensionSidebar(SIDEBAR_ID, {
+ title: SIDEBAR_TITLE,
+ });
+
+ const sidebar = await onceSidebarCreated;
+
+ // Test sidebar properties.
+ is(
+ sidebar,
+ inspector.getPanel(SIDEBAR_ID),
+ "Got an extension sidebar instance equal to the one saved in the inspector"
+ );
+ is(
+ sidebar.title,
+ SIDEBAR_TITLE,
+ "Got the expected title in the extension sidebar instance"
+ );
+ is(
+ sidebar.provider.props.title,
+ SIDEBAR_TITLE,
+ "Got the expeted title in the provider props"
+ );
+
+ // Test sidebar Redux state.
+ const inspectorStoreState = inspector.store.getState();
+ ok(
+ "extensionsSidebar" in inspectorStoreState,
+ "Got the extensionsSidebar sub-state in the inspector Redux store"
+ );
+ Assert.deepEqual(
+ inspectorStoreState.extensionsSidebar,
+ {},
+ "The extensionsSidebar should be initially empty"
+ );
+});
+
+add_task(async function testSidebarSetObject() {
+ const object = {
+ propertyName: {
+ nestedProperty: "propertyValue",
+ anotherProperty: "anotherValue",
+ },
+ };
+
+ const sidebar = inspector.getPanel(SIDEBAR_ID);
+ sidebar.setObject(object);
+
+ // Test updated sidebar Redux state.
+ const inspectorStoreState = inspector.store.getState();
+ is(
+ Object.keys(inspectorStoreState.extensionsSidebar).length,
+ 1,
+ "The extensionsSidebar state contains the newly registered extension sidebar state"
+ );
+ Assert.deepEqual(
+ inspectorStoreState.extensionsSidebar,
+ {
+ [SIDEBAR_ID]: {
+ viewMode: "object-treeview",
+ object,
+ },
+ },
+ "Got the expected state for the registered extension sidebar"
+ );
+
+ // Select the extension sidebar.
+ const waitSidebarSelected = toolbox.once(`inspector-sidebar-select`);
+ inspector.sidebar.show(SIDEBAR_ID);
+ await waitSidebarSelected;
+
+ const sidebarPanelContent = inspector.sidebar.getTabPanel(SIDEBAR_ID);
+
+ // Test extension sidebar content.
+ ok(
+ sidebarPanelContent,
+ "Got a sidebar panel for the registered extension sidebar"
+ );
+
+ assertTreeView(sidebarPanelContent, {
+ expectedTreeTables: 1,
+ expectedStringCells: 2,
+ expectedNumberCells: 0,
+ });
+
+ // Test sidebar refreshed on further sidebar.setObject calls.
+ info("Change the inspected object in the extension sidebar object treeview");
+ sidebar.setObject({ aNewProperty: 123 });
+
+ assertTreeView(sidebarPanelContent, {
+ expectedTreeTables: 1,
+ expectedStringCells: 0,
+ expectedNumberCells: 1,
+ });
+});
+
+add_task(async function testSidebarSetExpressionResult() {
+ const { commands } = toolbox;
+ const sidebar = inspector.getPanel(SIDEBAR_ID);
+ const sidebarPanelContent = inspector.sidebar.getTabPanel(SIDEBAR_ID);
+
+ info("Testing sidebar.setExpressionResult with rootTitle");
+
+ const expression = `
+ var obj = Object.create(null);
+ obj.prop1 = 123;
+ obj[Symbol('sym1')] = 456;
+ obj.cyclic = obj;
+ obj;
+ `;
+
+ const consoleFront = await toolbox.target.getFront("console");
+ let evalResult = await commands.inspectedWindowCommand.eval(
+ fakeExtCallerInfo,
+ expression,
+ {
+ consoleFront,
+ }
+ );
+
+ sidebar.setExpressionResult(evalResult, "Expected Root Title");
+
+ // Wait the ObjectInspector component to be rendered and test its content.
+ await testSetExpressionSidebarPanel(sidebarPanelContent, {
+ nodesLength: 4,
+ propertiesNames: ["cyclic", "prop1", "Symbol(sym1)"],
+ rootTitle: "Expected Root Title",
+ });
+
+ info("Testing sidebar.setExpressionResult without rootTitle");
+
+ sidebar.setExpressionResult(evalResult);
+
+ // Wait the ObjectInspector component to be rendered and test its content.
+ await testSetExpressionSidebarPanel(sidebarPanelContent, {
+ nodesLength: 4,
+ propertiesNames: ["cyclic", "prop1", "Symbol(sym1)"],
+ });
+
+ info("Test expanding the object");
+ const oi = sidebarPanelContent.querySelector(".tree");
+ const cyclicNode = oi.querySelectorAll(".node")[1];
+ ok(cyclicNode.innerText.includes("cyclic"), "Found the expected node");
+ cyclicNode.click();
+
+ await TestUtils.waitForCondition(
+ () => oi.querySelectorAll(".node").length === 7,
+ "Wait for the 'cyclic' node to be expanded"
+ );
+
+ await TestUtils.waitForCondition(
+ () => oi.querySelector(".tree-node.focused"),
+ "Wait for the 'cyclic' node to be focused"
+ );
+ ok(
+ oi.querySelector(".tree-node.focused").innerText.includes("cyclic"),
+ "'cyclic' node is focused"
+ );
+
+ info("Test keyboard navigation");
+ EventUtils.synthesizeKey("KEY_ArrowLeft", {}, oi.ownerDocument.defaultView);
+ await TestUtils.waitForCondition(
+ () => oi.querySelectorAll(".node").length === 4,
+ "Wait for the 'cyclic' node to be collapsed"
+ );
+ ok(
+ oi.querySelector(".tree-node.focused").innerText.includes("cyclic"),
+ "'cyclic' node is still focused"
+ );
+
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, oi.ownerDocument.defaultView);
+ await TestUtils.waitForCondition(
+ () => oi.querySelectorAll(".tree-node")[2].classList.contains("focused"),
+ "Wait for the 'prop1' node to be focused"
+ );
+
+ ok(
+ oi.querySelector(".tree-node.focused").innerText.includes("prop1"),
+ "'prop1' node is focused"
+ );
+
+ info(
+ "Testing sidebar.setExpressionResult for an expression returning a longstring"
+ );
+ evalResult = await commands.inspectedWindowCommand.eval(
+ fakeExtCallerInfo,
+ `"ab ".repeat(10000)`,
+ {
+ consoleFront,
+ }
+ );
+ sidebar.setExpressionResult(evalResult);
+
+ await TestUtils.waitForCondition(() => {
+ const longStringEl = sidebarPanelContent.querySelector(
+ ".tree .objectBox-string"
+ );
+ return (
+ longStringEl && longStringEl.textContent.includes("ab ".repeat(10000))
+ );
+ }, "Wait for the longString to be render with its full text");
+ ok(true, "The longString is expanded and its full text is displayed");
+
+ info(
+ "Testing sidebar.setExpressionResult for an expression returning a primitive"
+ );
+ evalResult = await commands.inspectedWindowCommand.eval(
+ fakeExtCallerInfo,
+ `1 + 2`,
+ {
+ consoleFront,
+ }
+ );
+ sidebar.setExpressionResult(evalResult);
+ const numberEl = await TestUtils.waitForCondition(
+ () => sidebarPanelContent.querySelector(".objectBox-number"),
+ "Wait for the result number element to be rendered"
+ );
+ is(numberEl.textContent, "3", `The "1 + 2" expression was evaluated as "3"`);
+});
+
+add_task(async function testSidebarDOMNodeHighlighting() {
+ const { commands } = toolbox;
+ const sidebar = inspector.getPanel(SIDEBAR_ID);
+ const sidebarPanelContent = inspector.sidebar.getTabPanel(SIDEBAR_ID);
+
+ const { waitForHighlighterTypeShown, waitForHighlighterTypeHidden } =
+ getHighlighterTestHelpers(inspector);
+
+ const expression = "({ body: document.body })";
+
+ const consoleFront = await toolbox.target.getFront("console");
+ const evalResult = await commands.inspectedWindowCommand.eval(
+ fakeExtCallerInfo,
+ expression,
+ {
+ consoleFront,
+ }
+ );
+
+ sidebar.setExpressionResult(evalResult);
+
+ // Wait the DOM node to be rendered inside the component.
+ await waitForObjectInspector(sidebarPanelContent, "node");
+
+ // Wait for the object to be expanded so we only target the "body" property node, and
+ // not the root object element.
+ await TestUtils.waitForCondition(
+ () =>
+ sidebarPanelContent.querySelectorAll(".object-inspector .tree-node")
+ .length > 1
+ );
+
+ // Get and verify the DOMNode and the "open inspector"" icon
+ // rendered inside the ObjectInspector.
+
+ assertObjectInspector(sidebarPanelContent, {
+ expectedDOMNodes: 2,
+ expectedOpenInspectors: 2,
+ });
+
+ // Test highlight DOMNode on mouseover.
+ info("Highlight the node by moving the cursor on it");
+
+ const onNodeHighlight = waitForHighlighterTypeShown(
+ inspector.highlighters.TYPES.BOXMODEL
+ );
+
+ moveMouseOnObjectInspectorDOMNode(sidebarPanelContent);
+
+ const { nodeFront } = await onNodeHighlight;
+ is(nodeFront.displayName, "body", "The correct node was highlighted");
+
+ // Test unhighlight DOMNode on mousemove.
+ info("Unhighlight the node by moving away from the node");
+ const onNodeUnhighlight = waitForHighlighterTypeHidden(
+ inspector.highlighters.TYPES.BOXMODEL
+ );
+
+ moveMouseOnPanelCenter(sidebarPanelContent);
+
+ await onNodeUnhighlight;
+ info("The node is no longer highlighted");
+});
+
+add_task(async function testSidebarDOMNodeOpenInspector() {
+ const sidebarPanelContent = inspector.sidebar.getTabPanel(SIDEBAR_ID);
+
+ // Test DOMNode selected in the inspector when "open inspector"" icon clicked.
+ info("Unselect node in the inspector");
+ let onceNewNodeFront = inspector.selection.once("new-node-front");
+ inspector.selection.setNodeFront(null);
+ let nodeFront = await onceNewNodeFront;
+ is(nodeFront, null, "The inspector selection should have been unselected");
+
+ info(
+ "Select the ObjectInspector DOMNode in the inspector panel by clicking on it"
+ );
+
+ // In test mode, shown highlighters are not automatically hidden after a delay to
+ // prevent intermittent test failures from race conditions.
+ // Restore this behavior just for this test because it is explicitly checked.
+ const HIGHLIGHTER_AUTOHIDE_TIMER = inspector.HIGHLIGHTER_AUTOHIDE_TIMER;
+ inspector.HIGHLIGHTER_AUTOHIDE_TIMER = 1000;
+ registerCleanupFunction(() => {
+ // Restore the value to disable autohiding to not impact other tests.
+ inspector.HIGHLIGHTER_AUTOHIDE_TIMER = HIGHLIGHTER_AUTOHIDE_TIMER;
+ });
+
+ const { waitForHighlighterTypeShown, waitForHighlighterTypeHidden } =
+ getHighlighterTestHelpers(inspector);
+
+ // Once we click the open-inspector icon we expect a new node front to be selected
+ // and the node to have been highlighted and unhighlighted.
+ const onNodeHighlight = waitForHighlighterTypeShown(
+ inspector.highlighters.TYPES.BOXMODEL
+ );
+ const onNodeUnhighlight = waitForHighlighterTypeHidden(
+ inspector.highlighters.TYPES.BOXMODEL
+ );
+ onceNewNodeFront = inspector.selection.once("new-node-front");
+
+ clickOpenInspectorIcon(sidebarPanelContent);
+
+ nodeFront = await onceNewNodeFront;
+ is(nodeFront.displayName, "body", "The correct node has been selected");
+ const { nodeFront: highlightedNodeFront } = await onNodeHighlight;
+ is(
+ highlightedNodeFront.displayName,
+ "body",
+ "The correct node was highlighted"
+ );
+
+ await onNodeUnhighlight;
+});
+
+add_task(async function testSidebarSetExtensionPage() {
+ const sidebar = inspector.getPanel(SIDEBAR_ID);
+ const sidebarPanelContent = inspector.sidebar.getTabPanel(SIDEBAR_ID);
+
+ info("Testing sidebar.setExtensionPage");
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["security.allow_unsafe_parent_loads", true]],
+ });
+
+ const expectedURL =
+ "data:text/html,<!DOCTYPE html><html><body><h1>Extension Page";
+
+ sidebar.setExtensionPage(expectedURL);
+
+ await testSetExtensionPageSidebarPanel(sidebarPanelContent, expectedURL);
+
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function teardownExtensionSidebar() {
+ info("Remove the sidebar instance");
+
+ toolbox.unregisterInspectorExtensionSidebar(SIDEBAR_ID);
+
+ ok(
+ !inspector.sidebar.getTabPanel(SIDEBAR_ID),
+ "The rendered extension sidebar has been removed"
+ );
+
+ const inspectorStoreState = inspector.store.getState();
+
+ Assert.deepEqual(
+ inspectorStoreState.extensionsSidebar,
+ {},
+ "The extensions sidebar Redux store data has been cleared"
+ );
+
+ await extension.unload();
+
+ toolbox = null;
+ inspector = null;
+ extension = null;
+});
+
+add_task(async function testActiveTabOnNonExistingSidebar() {
+ // Set a fake non existing sidebar id in the activeSidebar pref,
+ // to simulate the scenario where an extension has installed a sidebar
+ // which has been saved in the preference but it doesn't exist anymore.
+ await SpecialPowers.pushPrefEnv({
+ set: [["devtools.inspector.activeSidebar", "unexisting-sidebar-id"]],
+ });
+
+ const res = await openInspectorForURL("about:blank");
+ inspector = res.inspector;
+ toolbox = res.toolbox;
+
+ const onceSidebarCreated = toolbox.once(
+ `extension-sidebar-created-${SIDEBAR_ID}`
+ );
+ toolbox.registerInspectorExtensionSidebar(SIDEBAR_ID, {
+ title: SIDEBAR_TITLE,
+ });
+
+ // Wait the extension sidebar to be created and then unregister it to force the tabbar
+ // to select a new one.
+ await onceSidebarCreated;
+ toolbox.unregisterInspectorExtensionSidebar(SIDEBAR_ID);
+
+ is(
+ inspector.sidebar.getCurrentTabID(),
+ "layoutview",
+ "Got the expected inspector sidebar tab selected"
+ );
+
+ await SpecialPowers.popPrefEnv();
+});