tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

browser_inspector_extension_sidebar.js (13292B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 const SIDEBAR_ID = "an-extension-sidebar";
      7 const SIDEBAR_TITLE = "Sidebar Title";
      8 
      9 let extension;
     10 let fakeExtCallerInfo;
     11 
     12 let toolbox;
     13 let inspector;
     14 
     15 add_task(async function setupExtensionSidebar() {
     16  extension = ExtensionTestUtils.loadExtension({
     17    background() {
     18      // This is just an empty extension used to ensure that the caller extension uuid
     19      // actually exists.
     20    },
     21  });
     22 
     23  await extension.startup();
     24 
     25  fakeExtCallerInfo = {
     26    url: WebExtensionPolicy.getByID(extension.id).getURL(
     27      "fake-caller-script.js"
     28    ),
     29    lineNumber: 1,
     30    addonId: extension.id,
     31  };
     32 
     33  const res = await openInspectorForURL("about:blank");
     34  inspector = res.inspector;
     35  toolbox = res.toolbox;
     36 
     37  const onceSidebarCreated = toolbox.once(
     38    `extension-sidebar-created-${SIDEBAR_ID}`
     39  );
     40  toolbox.registerInspectorExtensionSidebar(SIDEBAR_ID, {
     41    title: SIDEBAR_TITLE,
     42  });
     43 
     44  const sidebar = await onceSidebarCreated;
     45 
     46  // Test sidebar properties.
     47  is(
     48    sidebar,
     49    inspector.getPanel(SIDEBAR_ID),
     50    "Got an extension sidebar instance equal to the one saved in the inspector"
     51  );
     52  is(
     53    sidebar.title,
     54    SIDEBAR_TITLE,
     55    "Got the expected title in the extension sidebar instance"
     56  );
     57  is(
     58    sidebar.provider.props.title,
     59    SIDEBAR_TITLE,
     60    "Got the expeted title in the provider props"
     61  );
     62 
     63  // Test sidebar Redux state.
     64  const inspectorStoreState = inspector.store.getState();
     65  ok(
     66    "extensionsSidebar" in inspectorStoreState,
     67    "Got the extensionsSidebar sub-state in the inspector Redux store"
     68  );
     69  Assert.deepEqual(
     70    inspectorStoreState.extensionsSidebar,
     71    {},
     72    "The extensionsSidebar should be initially empty"
     73  );
     74 });
     75 
     76 add_task(async function testSidebarSetObject() {
     77  const object = {
     78    propertyName: {
     79      nestedProperty: "propertyValue",
     80      anotherProperty: "anotherValue",
     81    },
     82  };
     83 
     84  const sidebar = inspector.getPanel(SIDEBAR_ID);
     85  sidebar.setObject(object);
     86 
     87  // Test updated sidebar Redux state.
     88  const inspectorStoreState = inspector.store.getState();
     89  is(
     90    Object.keys(inspectorStoreState.extensionsSidebar).length,
     91    1,
     92    "The extensionsSidebar state contains the newly registered extension sidebar state"
     93  );
     94  Assert.deepEqual(
     95    inspectorStoreState.extensionsSidebar,
     96    {
     97      [SIDEBAR_ID]: {
     98        viewMode: "object-treeview",
     99        object,
    100      },
    101    },
    102    "Got the expected state for the registered extension sidebar"
    103  );
    104 
    105  // Select the extension sidebar.
    106  const waitSidebarSelected = toolbox.once(`inspector-sidebar-select`);
    107  inspector.sidebar.show(SIDEBAR_ID);
    108  await waitSidebarSelected;
    109 
    110  const sidebarPanelContent = inspector.sidebar.getTabPanel(SIDEBAR_ID);
    111 
    112  // Test extension sidebar content.
    113  ok(
    114    sidebarPanelContent,
    115    "Got a sidebar panel for the registered extension sidebar"
    116  );
    117 
    118  assertTreeView(sidebarPanelContent, {
    119    expectedTreeTables: 1,
    120    expectedStringCells: 2,
    121    expectedNumberCells: 0,
    122  });
    123 
    124  // Test sidebar refreshed on further sidebar.setObject calls.
    125  info("Change the inspected object in the extension sidebar object treeview");
    126  sidebar.setObject({ aNewProperty: 123 });
    127 
    128  assertTreeView(sidebarPanelContent, {
    129    expectedTreeTables: 1,
    130    expectedStringCells: 0,
    131    expectedNumberCells: 1,
    132  });
    133 });
    134 
    135 add_task(async function testSidebarSetExpressionResult() {
    136  const { commands } = toolbox;
    137  const sidebar = inspector.getPanel(SIDEBAR_ID);
    138  const sidebarPanelContent = inspector.sidebar.getTabPanel(SIDEBAR_ID);
    139 
    140  info("Testing sidebar.setExpressionResult with rootTitle");
    141 
    142  const expression = `
    143    var obj = Object.create(null);
    144    obj.prop1 = 123;
    145    obj[Symbol('sym1')] = 456;
    146    obj.cyclic = obj;
    147    obj;
    148  `;
    149 
    150  const consoleFront = await toolbox.target.getFront("console");
    151  let evalResult = await commands.inspectedWindowCommand.eval(
    152    fakeExtCallerInfo,
    153    expression,
    154    {
    155      consoleFront,
    156    }
    157  );
    158 
    159  sidebar.setExpressionResult(evalResult, "Expected Root Title");
    160 
    161  // Wait the ObjectInspector component to be rendered and test its content.
    162  await testSetExpressionSidebarPanel(sidebarPanelContent, {
    163    nodesLength: 4,
    164    propertiesNames: ["cyclic", "prop1", "Symbol(sym1)"],
    165    rootTitle: "Expected Root Title",
    166  });
    167 
    168  info("Testing sidebar.setExpressionResult without rootTitle");
    169 
    170  sidebar.setExpressionResult(evalResult);
    171 
    172  // Wait the ObjectInspector component to be rendered and test its content.
    173  await testSetExpressionSidebarPanel(sidebarPanelContent, {
    174    nodesLength: 4,
    175    propertiesNames: ["cyclic", "prop1", "Symbol(sym1)"],
    176  });
    177 
    178  info("Test expanding the object");
    179  const oi = sidebarPanelContent.querySelector(".tree");
    180  const cyclicNode = oi.querySelectorAll(".node")[1];
    181  ok(cyclicNode.innerText.includes("cyclic"), "Found the expected node");
    182  cyclicNode.click();
    183 
    184  await TestUtils.waitForCondition(
    185    () => oi.querySelectorAll(".node").length === 7,
    186    "Wait for the 'cyclic' node to be expanded"
    187  );
    188 
    189  await TestUtils.waitForCondition(
    190    () => oi.querySelector(".tree-node.focused"),
    191    "Wait for the 'cyclic' node to be focused"
    192  );
    193  ok(
    194    oi.querySelector(".tree-node.focused").innerText.includes("cyclic"),
    195    "'cyclic' node is focused"
    196  );
    197 
    198  info("Test keyboard navigation");
    199  EventUtils.synthesizeKey("KEY_ArrowLeft", {}, oi.ownerDocument.defaultView);
    200  await TestUtils.waitForCondition(
    201    () => oi.querySelectorAll(".node").length === 4,
    202    "Wait for the 'cyclic' node to be collapsed"
    203  );
    204  ok(
    205    oi.querySelector(".tree-node.focused").innerText.includes("cyclic"),
    206    "'cyclic' node is still focused"
    207  );
    208 
    209  EventUtils.synthesizeKey("KEY_ArrowDown", {}, oi.ownerDocument.defaultView);
    210  await TestUtils.waitForCondition(
    211    () => oi.querySelectorAll(".tree-node")[2].classList.contains("focused"),
    212    "Wait for the 'prop1' node to be focused"
    213  );
    214 
    215  ok(
    216    oi.querySelector(".tree-node.focused").innerText.includes("prop1"),
    217    "'prop1' node is focused"
    218  );
    219 
    220  info(
    221    "Testing sidebar.setExpressionResult for an expression returning a longstring"
    222  );
    223  evalResult = await commands.inspectedWindowCommand.eval(
    224    fakeExtCallerInfo,
    225    `"ab ".repeat(10000)`,
    226    {
    227      consoleFront,
    228    }
    229  );
    230  sidebar.setExpressionResult(evalResult);
    231 
    232  await TestUtils.waitForCondition(() => {
    233    const longStringEl = sidebarPanelContent.querySelector(
    234      ".tree .objectBox-string"
    235    );
    236    return (
    237      longStringEl && longStringEl.textContent.includes("ab ".repeat(10000))
    238    );
    239  }, "Wait for the longString to be render with its full text");
    240  ok(true, "The longString is expanded and its full text is displayed");
    241 
    242  info(
    243    "Testing sidebar.setExpressionResult for an expression returning a primitive"
    244  );
    245  evalResult = await commands.inspectedWindowCommand.eval(
    246    fakeExtCallerInfo,
    247    `1 + 2`,
    248    {
    249      consoleFront,
    250    }
    251  );
    252  sidebar.setExpressionResult(evalResult);
    253  const numberEl = await TestUtils.waitForCondition(
    254    () => sidebarPanelContent.querySelector(".objectBox-number"),
    255    "Wait for the result number element to be rendered"
    256  );
    257  is(numberEl.textContent, "3", `The "1 + 2" expression was evaluated as "3"`);
    258 });
    259 
    260 add_task(async function testSidebarDOMNodeHighlighting() {
    261  const { commands } = toolbox;
    262  const sidebar = inspector.getPanel(SIDEBAR_ID);
    263  const sidebarPanelContent = inspector.sidebar.getTabPanel(SIDEBAR_ID);
    264 
    265  const { waitForHighlighterTypeShown, waitForHighlighterTypeHidden } =
    266    getHighlighterTestHelpers(inspector);
    267 
    268  const expression = "({ body: document.body })";
    269 
    270  const consoleFront = await toolbox.target.getFront("console");
    271  const evalResult = await commands.inspectedWindowCommand.eval(
    272    fakeExtCallerInfo,
    273    expression,
    274    {
    275      consoleFront,
    276    }
    277  );
    278 
    279  sidebar.setExpressionResult(evalResult);
    280 
    281  // Wait the DOM node to be rendered inside the component.
    282  await waitForObjectInspector(sidebarPanelContent, "node");
    283 
    284  // Wait for the object to be expanded so we only target the "body" property node, and
    285  // not the root object element.
    286  await TestUtils.waitForCondition(
    287    () =>
    288      sidebarPanelContent.querySelectorAll(".object-inspector .tree-node")
    289        .length > 1
    290  );
    291 
    292  // Get and verify the DOMNode and the "open inspector"" icon
    293  // rendered inside the ObjectInspector.
    294 
    295  assertObjectInspector(sidebarPanelContent, {
    296    expectedDOMNodes: 2,
    297    expectedOpenInspectors: 2,
    298  });
    299 
    300  // Test highlight DOMNode on mouseover.
    301  info("Highlight the node by moving the cursor on it");
    302 
    303  const onNodeHighlight = waitForHighlighterTypeShown(
    304    inspector.highlighters.TYPES.BOXMODEL
    305  );
    306 
    307  moveMouseOnObjectInspectorDOMNode(sidebarPanelContent);
    308 
    309  const { nodeFront } = await onNodeHighlight;
    310  is(nodeFront.displayName, "body", "The correct node was highlighted");
    311 
    312  // Test unhighlight DOMNode on mousemove.
    313  info("Unhighlight the node by moving away from the node");
    314  const onNodeUnhighlight = waitForHighlighterTypeHidden(
    315    inspector.highlighters.TYPES.BOXMODEL
    316  );
    317 
    318  moveMouseOnPanelCenter(sidebarPanelContent);
    319 
    320  await onNodeUnhighlight;
    321  info("The node is no longer highlighted");
    322 });
    323 
    324 add_task(async function testSidebarDOMNodeOpenInspector() {
    325  const sidebarPanelContent = inspector.sidebar.getTabPanel(SIDEBAR_ID);
    326 
    327  // Test DOMNode selected in the inspector when "open inspector"" icon clicked.
    328  info("Unselect node in the inspector");
    329  let onceNewNodeFront = inspector.selection.once("new-node-front");
    330  inspector.selection.setNodeFront(null);
    331  let nodeFront = await onceNewNodeFront;
    332  is(nodeFront, null, "The inspector selection should have been unselected");
    333 
    334  info(
    335    "Select the ObjectInspector DOMNode in the inspector panel by clicking on it"
    336  );
    337 
    338  // In test mode, shown highlighters are not automatically hidden after a delay to
    339  // prevent intermittent test failures from race conditions.
    340  // Restore this behavior just for this test because it is explicitly checked.
    341  const HIGHLIGHTER_AUTOHIDE_TIMER = inspector.HIGHLIGHTER_AUTOHIDE_TIMER;
    342  inspector.HIGHLIGHTER_AUTOHIDE_TIMER = 1000;
    343  registerCleanupFunction(() => {
    344    // Restore the value to disable autohiding to not impact other tests.
    345    inspector.HIGHLIGHTER_AUTOHIDE_TIMER = HIGHLIGHTER_AUTOHIDE_TIMER;
    346  });
    347 
    348  const { waitForHighlighterTypeShown, waitForHighlighterTypeHidden } =
    349    getHighlighterTestHelpers(inspector);
    350 
    351  // Once we click the open-inspector icon we expect a new node front to be selected
    352  // and the node to have been highlighted and unhighlighted.
    353  const onNodeHighlight = waitForHighlighterTypeShown(
    354    inspector.highlighters.TYPES.BOXMODEL
    355  );
    356  const onNodeUnhighlight = waitForHighlighterTypeHidden(
    357    inspector.highlighters.TYPES.BOXMODEL
    358  );
    359  onceNewNodeFront = inspector.selection.once("new-node-front");
    360 
    361  clickOpenInspectorIcon(sidebarPanelContent);
    362 
    363  nodeFront = await onceNewNodeFront;
    364  is(nodeFront.displayName, "body", "The correct node has been selected");
    365  const { nodeFront: highlightedNodeFront } = await onNodeHighlight;
    366  is(
    367    highlightedNodeFront.displayName,
    368    "body",
    369    "The correct node was highlighted"
    370  );
    371 
    372  await onNodeUnhighlight;
    373 });
    374 
    375 add_task(async function testSidebarSetExtensionPage() {
    376  const sidebar = inspector.getPanel(SIDEBAR_ID);
    377  const sidebarPanelContent = inspector.sidebar.getTabPanel(SIDEBAR_ID);
    378 
    379  info("Testing sidebar.setExtensionPage");
    380 
    381  const expectedURL = getRootDirectory(gTestPath) + "extension_page.html";
    382 
    383  sidebar.setExtensionPage(expectedURL);
    384 
    385  await testSetExtensionPageSidebarPanel(sidebarPanelContent, expectedURL);
    386 });
    387 
    388 add_task(async function teardownExtensionSidebar() {
    389  info("Remove the sidebar instance");
    390 
    391  toolbox.unregisterInspectorExtensionSidebar(SIDEBAR_ID);
    392 
    393  ok(
    394    !inspector.sidebar.getTabPanel(SIDEBAR_ID),
    395    "The rendered extension sidebar has been removed"
    396  );
    397 
    398  const inspectorStoreState = inspector.store.getState();
    399 
    400  Assert.deepEqual(
    401    inspectorStoreState.extensionsSidebar,
    402    {},
    403    "The extensions sidebar Redux store data has been cleared"
    404  );
    405 
    406  await extension.unload();
    407 
    408  toolbox = null;
    409  inspector = null;
    410  extension = null;
    411 });
    412 
    413 add_task(async function testActiveTabOnNonExistingSidebar() {
    414  // Set a fake non existing sidebar id in the activeSidebar pref,
    415  // to simulate the scenario where an extension has installed a sidebar
    416  // which has been saved in the preference but it doesn't exist anymore.
    417  await SpecialPowers.pushPrefEnv({
    418    set: [["devtools.inspector.activeSidebar", "unexisting-sidebar-id"]],
    419  });
    420 
    421  const res = await openInspectorForURL("about:blank");
    422  inspector = res.inspector;
    423  toolbox = res.toolbox;
    424 
    425  const onceSidebarCreated = toolbox.once(
    426    `extension-sidebar-created-${SIDEBAR_ID}`
    427  );
    428  toolbox.registerInspectorExtensionSidebar(SIDEBAR_ID, {
    429    title: SIDEBAR_TITLE,
    430  });
    431 
    432  // Wait the extension sidebar to be created and then unregister it to force the tabbar
    433  // to select a new one.
    434  await onceSidebarCreated;
    435  toolbox.unregisterInspectorExtensionSidebar(SIDEBAR_ID);
    436 
    437  is(
    438    inspector.sidebar.getCurrentTabID(),
    439    "layoutview",
    440    "Got the expected inspector sidebar tab selected"
    441  );
    442 
    443  await SpecialPowers.popPrefEnv();
    444 });