diff options
Diffstat (limited to '')
-rw-r--r-- | devtools/client/inspector/extensions/test/browser_inspector_extension_sidebar.js | 451 |
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(); +}); |