tor-browser

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

browser_net_search-results.js (19688B)


      1 /* Any copyright is dedicated to the Public Domain.
      2  http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 /**
      7 * Test search match functionality.
      8 * Search panel is visible and clicking matches shows them in the request details.
      9 */
     10 
     11 const { PluralForm } = require("resource://devtools/shared/plural-form.js");
     12 
     13 add_task(async function () {
     14  const { tab, monitor } = await initNetMonitor(HTTPS_CUSTOM_GET_URL, {
     15    requestCount: 1,
     16  });
     17  info("Starting test... ");
     18 
     19  const { document, store, windowRequire } = monitor.panelWin;
     20 
     21  // Action should be processed synchronously in tests.
     22  const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
     23  store.dispatch(Actions.batchEnable(false));
     24 
     25  const SEARCH_STRING = "test";
     26  // Execute two XHRs and wait until they are finished.
     27  const URLS = [
     28    HTTPS_SEARCH_SJS + "?value=test1",
     29    HTTPS_SEARCH_SJS + "?value=test2",
     30  ];
     31 
     32  const wait = waitForNetworkEvents(monitor, 2);
     33  await SpecialPowers.spawn(tab.linkedBrowser, [URLS], makeRequests);
     34  await wait;
     35 
     36  // Open the Search panel
     37  await store.dispatch(Actions.openSearch());
     38 
     39  // Fill Filter input with text and check displayed messages.
     40  // The filter should be focused automatically.
     41  typeInNetmonitor(SEARCH_STRING, monitor);
     42  EventUtils.synthesizeKey("KEY_Enter");
     43 
     44  // Wait until there are two resources rendered in the results
     45  await waitForDOMIfNeeded(
     46    document,
     47    ".search-panel-content .treeRow.resourceRow",
     48    2
     49  );
     50 
     51  const searchMatchContents = document.querySelectorAll(
     52    ".search-panel-content .treeRow .treeIcon"
     53  );
     54 
     55  for (let i = searchMatchContents.length - 1; i >= 0; i--) {
     56    clickElement(searchMatchContents[i], monitor);
     57  }
     58 
     59  // Wait until there are two resources rendered in the results
     60  await waitForDOMIfNeeded(
     61    document,
     62    ".search-panel-content .treeRow.resultRow",
     63    12
     64  );
     65 
     66  // Check the matches
     67  const matches = document.querySelectorAll(
     68    ".search-panel-content .treeRow.resultRow"
     69  );
     70 
     71  await checkSearchResult(
     72    monitor,
     73    matches[0],
     74    "#headers-panel",
     75    ".url-preview .properties-view",
     76    ".treeRow",
     77    [SEARCH_STRING]
     78  );
     79  await checkSearchResult(
     80    monitor,
     81    matches[1],
     82    "#headers-panel",
     83    "#responseHeaders .properties-view",
     84    ".treeRow.selected",
     85    [SEARCH_STRING]
     86  );
     87  await checkSearchResult(
     88    monitor,
     89    matches[2],
     90    "#headers-panel",
     91    "#requestHeaders .properties-view",
     92    ".treeRow.selected",
     93    [SEARCH_STRING]
     94  );
     95  await checkSearchResult(
     96    monitor,
     97    matches[3],
     98    "#cookies-panel",
     99    "#responseCookies .properties-view",
    100    ".treeRow.selected",
    101    [SEARCH_STRING]
    102  );
    103  await checkSearchResult(
    104    monitor,
    105    matches[4],
    106    "#response-panel",
    107    ".cm-content",
    108    ".cm-line",
    109    [SEARCH_STRING]
    110  );
    111  await checkSearchResult(
    112    monitor,
    113    matches[5],
    114    "#headers-panel",
    115    ".url-preview .properties-view",
    116    ".treeRow",
    117    [SEARCH_STRING]
    118  );
    119  await checkSearchResult(
    120    monitor,
    121    matches[6],
    122    "#headers-panel",
    123    "#responseHeaders .properties-view",
    124    ".treeRow.selected",
    125    [SEARCH_STRING]
    126  );
    127  await checkSearchResult(
    128    monitor,
    129    matches[7],
    130    "#headers-panel",
    131    "#requestHeaders .properties-view",
    132    ".treeRow.selected",
    133    [SEARCH_STRING]
    134  );
    135  await checkSearchResult(
    136    monitor,
    137    matches[8],
    138    "#headers-panel",
    139    "#requestHeaders .properties-view",
    140    ".treeRow.selected",
    141    [SEARCH_STRING]
    142  );
    143  await checkSearchResult(
    144    monitor,
    145    matches[9],
    146    "#cookies-panel",
    147    "#responseCookies .properties-view",
    148    ".treeRow.selected",
    149    [SEARCH_STRING]
    150  );
    151  await checkSearchResult(
    152    monitor,
    153    matches[10],
    154    "#cookies-panel",
    155    "#requestCookies .properties-view",
    156    ".treeRow.selected",
    157    [SEARCH_STRING]
    158  );
    159  await checkSearchResult(
    160    monitor,
    161    matches[11],
    162    "#response-panel",
    163    ".cm-content",
    164    ".cm-line",
    165    [SEARCH_STRING]
    166  );
    167 
    168  await teardown(monitor);
    169 });
    170 
    171 /**
    172 * Test the context menu feature inside the search match functionality.
    173 * Search panel is visible and right-clicking matches shows the appropriate context-menu's.
    174 */
    175 
    176 add_task(async function () {
    177  const { tab, monitor } = await initNetMonitor(HTTPS_CUSTOM_GET_URL, {
    178    requestCount: 1,
    179  });
    180  info("Starting test... ");
    181 
    182  const { document, store, windowRequire } = monitor.panelWin;
    183 
    184  // Action should be processed synchronously in tests.
    185  const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
    186  store.dispatch(Actions.batchEnable(false));
    187 
    188  const SEARCH_STRING = "matchingResult";
    189  const matchingUrls = [
    190    HTTPS_SEARCH_SJS + "?value=matchingResult1",
    191    HTTPS_SEARCH_SJS + "?value=matchingResult2",
    192  ];
    193  const nonMatchingUrls = [HTTPS_SEARCH_SJS + "?value=somethingDifferent"];
    194 
    195  const wait = waitForNetworkEvents(
    196    monitor,
    197    matchingUrls.length + nonMatchingUrls.length
    198  );
    199  await SpecialPowers.spawn(tab.linkedBrowser, [matchingUrls], makeRequests);
    200  await SpecialPowers.spawn(tab.linkedBrowser, [nonMatchingUrls], makeRequests);
    201  await wait;
    202 
    203  // Open the Search panel
    204  await store.dispatch(Actions.openSearch());
    205 
    206  // Fill Filter input with text and check displayed messages.
    207  // The filter should be focused automatically.
    208  typeInNetmonitor(SEARCH_STRING, monitor);
    209  EventUtils.synthesizeKey("KEY_Enter");
    210 
    211  // Wait for all the updates to complete
    212  await waitForAllNetworkUpdateEvents();
    213 
    214  // Wait until there are two resources rendered in the results
    215  await waitForDOMIfNeeded(
    216    document,
    217    ".search-panel-content .treeRow.resourceRow",
    218    2
    219  );
    220 
    221  const resourceMatches = document.querySelectorAll(
    222    ".search-panel-content .treeRow .treeIcon"
    223  );
    224 
    225  // open content matches for first resource:
    226  const firstResourceMatch = resourceMatches[0];
    227  clickElement(firstResourceMatch, monitor);
    228 
    229  // Wait until the expanded resource is rendered in the results
    230  await waitForDOMIfNeeded(
    231    document,
    232    ".search-panel-content .treeRow.resultRow",
    233    1
    234  );
    235 
    236  // Check the content matches
    237  const contentMatches = document.querySelectorAll(
    238    ".search-panel-content .treeRow.resultRow"
    239  );
    240 
    241  // test context menu entries for contained content:
    242  const firstContentMatch = contentMatches[0];
    243  ok(
    244    document.querySelector(".treeRow.selected.opened"),
    245    "The previous line, which is the selected line, is expanded to show the result row"
    246  );
    247  await checkContentMenuCopy(firstContentMatch, matchingUrls[0], monitor);
    248 
    249  // test the context menu entries for resources
    250  const secondResourceMatch = resourceMatches[1];
    251  await checkResourceMenuCopyUrl(secondResourceMatch, matchingUrls[1], monitor);
    252 
    253  // checkResourceMenuResend will trigger a new request, which will be added to the search results
    254  await checkResourceMenuResend(secondResourceMatch, monitor);
    255 
    256  // Assert that the previously expanded search result is kept expanded when a new request is added
    257  ok(
    258    document.querySelector(".treeRow.selected.opened"),
    259    "The previous line is still expanded after having added a new request is the result list"
    260  );
    261 
    262  await checkResourceMenuBlockUnblock(
    263    secondResourceMatch,
    264    matchingUrls[1],
    265    monitor
    266  );
    267  await checkSaveAllAsHARWithContextMenu(
    268    secondResourceMatch,
    269    matchingUrls,
    270    monitor
    271  );
    272 
    273  // reload tab
    274  const waitForEvents = waitForNetworkEvents(monitor, 1);
    275  tab.linkedBrowser.reload();
    276  await waitForEvents;
    277 
    278  // test that the context menu entries are not available anymore:
    279  await checkResourceMenuNotAvailbale(secondResourceMatch, monitor);
    280 
    281  await teardown(monitor);
    282 });
    283 
    284 add_task(async function searchWithRequestOnUnload() {
    285  const { tab, monitor } = await initNetMonitor(HTTPS_CUSTOM_GET_URL, {
    286    requestCount: 1,
    287  });
    288  info("Starting test... ");
    289 
    290  const { document, store, windowRequire } = monitor.panelWin;
    291 
    292  // Action should be processed synchronously in tests.
    293  const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
    294  store.dispatch(Actions.batchEnable(false));
    295 
    296  await SpecialPowers.spawn(
    297    tab.linkedBrowser,
    298    [HTTPS_SEARCH_SJS + "?value=test1"],
    299    function (url) {
    300      content.addEventListener("unload", () => {
    301        content.wrappedJSObject.get(url);
    302      });
    303    }
    304  );
    305 
    306  const SEARCH_STRING = "html_custom-get-page.html";
    307 
    308  // reload tab, expect the html page and the unload request
    309  const waitForEvents = waitForNetworkEvents(monitor, 2);
    310  tab.linkedBrowser.reload();
    311  await waitForEvents;
    312 
    313  // Open the Search panel
    314  await store.dispatch(Actions.openSearch());
    315 
    316  // Fill Filter input with text and check displayed messages.
    317  // The filter should be focused automatically.
    318  typeInNetmonitor(SEARCH_STRING, monitor);
    319  EventUtils.synthesizeKey("KEY_Enter");
    320 
    321  // Wait until there are two resources rendered in the results
    322  await waitForDOMIfNeeded(
    323    document,
    324    ".search-panel-content .treeRow.resourceRow",
    325    1
    326  );
    327 
    328  // Wait until there are two resources rendered in the results
    329  await waitForDOMIfNeeded(document, ".search-panel .status-bar-label");
    330  const statusBar = document.querySelector(".search-panel .status-bar-label");
    331  const matchingLines = PluralForm.get(
    332    1,
    333    L10N.getStr("netmonitor.search.status.labels.matchingLines")
    334  ).replace("#1", 1);
    335  const matchingFiles = PluralForm.get(
    336    1,
    337    L10N.getStr("netmonitor.search.status.labels.fileCount")
    338  ).replace("#1", 1);
    339  is(
    340    statusBar.textContent,
    341    L10N.getFormatStr(
    342      "netmonitor.search.status.labels.done",
    343      matchingLines,
    344      matchingFiles
    345    ),
    346    "Search completed"
    347  );
    348 
    349  await teardown(monitor);
    350 });
    351 
    352 // Asserts that the content is scrolled to show the correct matched content
    353 // on the line when a match is selected from the network search list.
    354 add_task(async function testContentIsScrolledWhenMatchIsSelected() {
    355  const httpServer = createTestHTTPServer();
    356  httpServer.registerPathHandler(`/`, function (request, response) {
    357    response.setStatusLine(request.httpVersion, 200, "OK");
    358    response.write(`<html><meta charset=utf8>
    359      <script type="text/javascript" src="/script.js"></script>
    360      <h1>Test matches in scrolled content</h1>
    361      </html>`);
    362  });
    363 
    364  // The "data" path takes a size query parameter and will return a body of the
    365  // requested size.
    366  httpServer.registerPathHandler("/script.js", function (request, response) {
    367    response.setHeader("Content-Type", "text/javascript");
    368    response.setStatusLine(request.httpVersion, 200, "OK");
    369    response.write(
    370      `${Array.from({ length: 40 }, (_, i) => `// line ${++i}x`).join("\n")}`
    371    );
    372  });
    373 
    374  const TEST_URI = `http://localhost:${httpServer.identity.primaryPort}/`;
    375 
    376  const { tab, monitor } = await initNetMonitor(TEST_URI, {
    377    requestCount: 1,
    378  });
    379  info("Starting test... ");
    380  const { document, store, windowRequire } = monitor.panelWin;
    381 
    382  // Action should be processed synchronously in tests.
    383  const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
    384  store.dispatch(Actions.batchEnable(false));
    385 
    386  // reload tab, expect the html page and the script request
    387  const waitForEvents = waitForNetworkEvents(monitor, 2);
    388  tab.linkedBrowser.reload();
    389  await waitForEvents;
    390 
    391  // Open the Search panel
    392  await store.dispatch(Actions.openSearch());
    393 
    394  // Fill search input with text and check displayed messages.
    395  const waitForResult = waitFor(() =>
    396    document.querySelector(".search-panel-content .treeRow.resourceRow")
    397  );
    398  typeInNetmonitor("line 3x", monitor);
    399  EventUtils.synthesizeKey("KEY_Enter");
    400  await waitForResult;
    401 
    402  const result = document.querySelector(
    403    ".search-panel-content .treeRow.resourceRow"
    404  );
    405  // Click the matches to
    406  const waitForMatches = waitFor(
    407    () =>
    408      document.querySelectorAll(".search-panel-content .treeRow.resultRow")
    409        ?.length == 1
    410  );
    411  clickElement(result, monitor);
    412  await waitForMatches;
    413 
    414  const matches = document.querySelectorAll(
    415    ".search-panel-content .treeRow.resultRow"
    416  );
    417  // Click the first result match
    418  const waitForResponsePanelContent = waitFor(() =>
    419    document.querySelector(`#response-panel .cm-content`)
    420  );
    421  clickElement(matches[0], monitor);
    422  await waitForResponsePanelContent;
    423 
    424  const editor = getCMEditor(monitor);
    425  is(
    426    editor.getSelectionCursor().from.line,
    427    3,
    428    "The content on line 3 is highlighted"
    429  );
    430  is(
    431    editor.getText(editor.getSelectionCursor().from.line),
    432    "// line 3x",
    433    "The content on the line with cursor (line 3) is correct"
    434  );
    435 
    436  // Set the cursor to the bottom of the document
    437  let onScrolled = waitForEditorScrolling(monitor);
    438  await editor.setCursorAt(40, 0);
    439  await onScrolled;
    440 
    441  is(
    442    editor.getSelectionCursor().from.line,
    443    40,
    444    "The content on line 40 is highlighted"
    445  );
    446  is(
    447    editor.getText(editor.getSelectionCursor().from.line),
    448    "// line 40x",
    449    "The content on the line with cursor (line 40) is correct"
    450  );
    451 
    452  // Click the first result match again
    453  onScrolled = waitForEditorScrolling(monitor);
    454  clickElement(matches[0], monitor);
    455  await onScrolled;
    456 
    457  // The editor should scroll back up and the matched content on line 3 should be highlighted
    458 
    459  is(
    460    editor.getSelectionCursor().from.line,
    461    3,
    462    "The content on line 3 is highlighted"
    463  );
    464  is(
    465    editor.getText(editor.getSelectionCursor().from.line),
    466    "// line 3x",
    467    "The content on the line with cursor (line 3) is correct"
    468  );
    469 });
    470 
    471 async function makeRequests(urls) {
    472  await content.wrappedJSObject.get(urls[0]);
    473  await content.wrappedJSObject.get(urls[1]);
    474  info("XHR Requests executed");
    475 }
    476 
    477 async function checkContentMenuCopy(
    478  contentMatch,
    479  expectedClipboardValue,
    480  monitor
    481 ) {
    482  EventUtils.sendMouseEvent({ type: "contextmenu" }, contentMatch);
    483 
    484  // execute the copy command:
    485  await waitForClipboardPromise(async function setup() {
    486    await selectContextMenuItem(
    487      monitor,
    488      "properties-view-context-menu-copyvalue"
    489    );
    490  }, expectedClipboardValue);
    491 }
    492 
    493 async function checkResourceMenuCopyUrl(
    494  resourceMatch,
    495  expectedClipboardValue,
    496  monitor
    497 ) {
    498  // trigger context menu:
    499  EventUtils.sendMouseEvent({ type: "contextmenu" }, resourceMatch);
    500 
    501  // select the context menu entry 'copy-url':
    502  await waitForClipboardPromise(async function setup() {
    503    await selectContextMenuItem(monitor, "request-list-context-copy-url");
    504  }, expectedClipboardValue);
    505 }
    506 
    507 async function checkResourceMenuResend(resourceMatch, monitor) {
    508  const { store, windowRequire } = monitor.panelWin;
    509 
    510  const { getSelectedRequest, getDisplayedRequests } = windowRequire(
    511    "devtools/client/netmonitor/src/selectors/index"
    512  );
    513 
    514  // expect the appearing of a new request in the list:
    515  const displayedRequests = getDisplayedRequests(store.getState());
    516  const originalResourceIds = displayedRequests.map(r => r.id);
    517  const expectedNrOfRequestsAfterResend = displayedRequests.length + 1;
    518 
    519  // define wait functionality, that waits until a new entry appears with different ID:
    520  const waitForNewRequest = waitUntil(() => {
    521    const newResourceId = getSelectedRequest(store.getState())?.id;
    522    return (
    523      getDisplayedRequests(store.getState()).length ==
    524        expectedNrOfRequestsAfterResend &&
    525      !originalResourceIds.includes(newResourceId)
    526    );
    527  });
    528 
    529  // click the context menu 'resend'
    530  EventUtils.sendMouseEvent({ type: "contextmenu" }, resourceMatch);
    531  await selectContextMenuItem(monitor, "request-list-context-resend-only");
    532  await waitForNewRequest;
    533 }
    534 
    535 async function checkResourceMenuBlockUnblock(resourceMatch, blockUrl, monitor) {
    536  const { store, windowRequire } = monitor.panelWin;
    537 
    538  // block resource:
    539  await toggleBlockedUrl(resourceMatch, monitor, store);
    540 
    541  // assert that there is now 1 blocked URL:
    542  is(
    543    store.getState().requestBlocking.blockedUrls.length,
    544    1,
    545    "There should be 1 blocked URL"
    546  );
    547  is(
    548    store.getState().requestBlocking.blockedUrls[0].url,
    549    blockUrl,
    550    `The blocked URL should be '${blockUrl}'`
    551  );
    552 
    553  // Open the Search panel again
    554  const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
    555  await store.dispatch(Actions.openSearch());
    556 
    557  // block resource:
    558  await toggleBlockedUrl(resourceMatch, monitor, store, "unblock");
    559 
    560  // assert that there is no blocked URL anymore:
    561  is(
    562    store.getState().requestBlocking.blockedUrls.length,
    563    0,
    564    "There should be no blocked URL"
    565  );
    566 }
    567 
    568 async function checkSaveAllAsHARWithContextMenu(
    569  resourceMatch,
    570  expectedUrls,
    571  monitor
    572 ) {
    573  const { HarMenuUtils } = monitor.panelWin.windowRequire(
    574    "devtools/client/netmonitor/src/har/har-menu-utils"
    575  );
    576 
    577  EventUtils.sendMouseEvent({ type: "mousedown" }, resourceMatch);
    578  EventUtils.sendMouseEvent({ type: "contextmenu" }, resourceMatch);
    579 
    580  info("Trigger Copy All As HAR from the context menu");
    581  const onHarCopyDone = HarMenuUtils.once("copy-all-as-har-done");
    582  await selectContextMenuItem(monitor, "request-list-context-copy-all-as-har");
    583  const jsonString = await onHarCopyDone;
    584  info("exported JSON:\n" + jsonString);
    585  const parsedJson = JSON.parse(jsonString);
    586 
    587  is(
    588    parsedJson?.log?.entries?.length,
    589    expectedUrls.length,
    590    "Expected length of " + expectedUrls.length
    591  );
    592  for (let i = 0; i < expectedUrls.length; i++) {
    593    is(
    594      parsedJson.log.entries[i].request?.url,
    595      expectedUrls[i],
    596      "Expected url was '" + expectedUrls[i] + "'"
    597    );
    598  }
    599 }
    600 
    601 async function checkResourceMenuNotAvailbale(resourceMatch, monitor) {
    602  // trigger context menu:
    603  EventUtils.sendMouseEvent({ type: "contextmenu" }, resourceMatch);
    604 
    605  is(
    606    !!getContextMenuItem(
    607      monitor,
    608      "simple-view-context-menu-request-not-available-anymore"
    609    ),
    610    true,
    611    "context menu item 'not-available' should be present"
    612  );
    613  is(
    614    !!getContextMenuItem(monitor, "request-list-context-resend-only"),
    615    false,
    616    "context menu item 'resend' should not be present"
    617  );
    618  is(
    619    !!getContextMenuItem(monitor, "request-list-context-copy-all-as-har"),
    620    false,
    621    "context menu item 'copy all as HAR' should not be present"
    622  );
    623  is(
    624    !!getContextMenuItem(monitor, "netmonitor.context.blockURL"),
    625    false,
    626    "context menu item 'block URL' should not be present"
    627  );
    628 }
    629 
    630 /**
    631 * Check whether the search result is correctly linked with the related information
    632 */
    633 async function checkSearchResult(
    634  monitor,
    635  match,
    636  panelSelector,
    637  panelContentSelector,
    638  panelDetailSelector,
    639  expected
    640 ) {
    641  const { document } = monitor.panelWin;
    642 
    643  // Scroll the match into view so that it's clickable
    644  match.scrollIntoView();
    645 
    646  // Click on the match to show it
    647  clickElement(match, monitor);
    648 
    649  console.log(`${panelSelector} ${panelContentSelector}`);
    650  await waitFor(() =>
    651    document.querySelector(`${panelSelector} ${panelContentSelector}`)
    652  );
    653 
    654  const tabpanel = document.querySelector(panelSelector);
    655  const content = tabpanel.querySelectorAll(
    656    `${panelContentSelector} ${panelDetailSelector}`
    657  );
    658 
    659  is(
    660    content.length,
    661    expected.length,
    662    `There should be ${expected.length} item${
    663      expected.length === 1 ? "" : "s"
    664    } displayed in this tabpanel`
    665  );
    666 
    667  // Make sure only 1 item is selected
    668  if (panelDetailSelector === ".treeRow.selected") {
    669    const selectedElements = tabpanel.querySelectorAll(panelDetailSelector);
    670    is(
    671      selectedElements.length,
    672      1,
    673      `There should be only 1 item selected, found ${selectedElements.length} items selected`
    674    );
    675  }
    676 
    677  if (content.length === expected.length) {
    678    for (let i = 0; i < expected.length; i++) {
    679      is(
    680        content[i].textContent.includes(expected[i]),
    681        true,
    682        `Content must include ${expected[i]}`
    683      );
    684    }
    685  }
    686 }