tor-browser

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

browser_link_hover_speculative_connection.js (12023B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 const { HttpServer } = ChromeUtils.importESModule(
      8  "resource://testing-common/httpd.sys.mjs"
      9 );
     10 
     11 const TEST_PATH = "/browser/netwerk/test/browser/";
     12 
     13 let gServer;
     14 let gServerURL;
     15 let gConnectionNumberWhenRequested = 0;
     16 
     17 add_setup(async function () {
     18  await SpecialPowers.pushPrefEnv({
     19    set: [
     20      // Enable speculative connections for hover on HTTPS
     21      ["network.predictor.enable-hover-on-ssl", true],
     22      // Enable network debugging observations
     23      ["network.http.debug-observations", true],
     24    ],
     25  });
     26 
     27  // Set up local HTTP server for the target page
     28  gServer = new HttpServer();
     29  gServer.start(-1);
     30  gServerURL = `http://localhost:${gServer.identity.primaryPort}`;
     31 
     32  // Register handler for the target page
     33  gServer.registerPathHandler("/target.html", handleTarget);
     34 
     35  registerCleanupFunction(async () => {
     36    await gServer.stop();
     37  });
     38 });
     39 
     40 function handleTarget(request, response) {
     41  response.setHeader("Content-Type", "text/html", false);
     42  response.setHeader("Cache-Control", "no-cache", false);
     43 
     44  // Record which connection number handled this request
     45  gConnectionNumberWhenRequested = response._connection.number;
     46 
     47  let body = `
     48    <!DOCTYPE html>
     49    <html>
     50    <head>
     51      <meta charset="UTF-8">
     52      <title>Target Page</title>
     53    </head>
     54    <body>
     55      <h1>Target Page</h1>
     56      <p>Connection: ${gConnectionNumberWhenRequested}</p>
     57    </body>
     58    </html>
     59  `;
     60 
     61  response.write(body);
     62 }
     63 
     64 /**
     65 * Helper function to simulate hovering over a link element
     66 */
     67 function hoverOverLink(browser, linkId) {
     68  return SpecialPowers.spawn(browser, [linkId], async id => {
     69    let link = content.document.getElementById(id);
     70    // Dispatch mouseover event which should trigger the speculative connection
     71    let event = new content.MouseEvent("mouseover", {
     72      bubbles: true,
     73      cancelable: true,
     74      view: content,
     75    });
     76    link.dispatchEvent(event);
     77  });
     78 }
     79 
     80 /**
     81 * Helper function to click a link and wait for navigation
     82 */
     83 async function clickLink(browser, linkId) {
     84  let loadedPromise = BrowserTestUtils.browserLoaded(browser);
     85 
     86  await SpecialPowers.spawn(browser, [linkId], async id => {
     87    let link = content.document.getElementById(id);
     88    link.click();
     89  });
     90 
     91  await loadedPromise;
     92 }
     93 
     94 add_task(async function test_link_hover_https_page() {
     95  let speculativeConnectObserved = false;
     96  let observer = {
     97    QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
     98    observe(aSubject, aTopic, aData) {
     99      if (
    100        aTopic == "speculative-connect-request" &&
    101        aData.includes("localhost")
    102      ) {
    103        info("Observed speculative connection request for: " + aData);
    104        speculativeConnectObserved = true;
    105      }
    106    },
    107  };
    108  Services.obs.addObserver(observer, "speculative-connect-request");
    109 
    110  // Load the test page from HTTPS example.com with the target URL pointing to our local server
    111  const targetURL = encodeURIComponent(gServerURL + "/target.html");
    112  const pageURL =
    113    "https://example.com" +
    114    TEST_PATH +
    115    "file_link_hover.sjs?target=" +
    116    targetURL;
    117 
    118  await BrowserTestUtils.withNewTab(
    119    {
    120      gBrowser,
    121      url: pageURL,
    122      waitForLoad: true,
    123    },
    124    async function (browser) {
    125      // Record the current connection count before hovering
    126      let connectionCountBeforeHover = gServer.connectionNumber;
    127      info("Connection count before hover: " + connectionCountBeforeHover);
    128 
    129      // Wait for the link element to be available in the page
    130      await SpecialPowers.spawn(browser, [], async () => {
    131        await ContentTaskUtils.waitForCondition(
    132          () => content.document.getElementById("testLink"),
    133          "Waiting for link element to be available"
    134        );
    135      });
    136 
    137      // Hover over the link to trigger speculative connection
    138      await hoverOverLink(browser, "testLink");
    139 
    140      // Wait for the speculative connection to be fully established
    141      // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    142      await new Promise(resolve => setTimeout(resolve, 1000));
    143 
    144      // Check connection count after hover
    145      let connectionCountAfterHover = gServer.connectionNumber;
    146      info("Connection count after hover: " + connectionCountAfterHover);
    147 
    148      // Verify that a speculative connection request was observed
    149      Assert.ok(
    150        speculativeConnectObserved,
    151        "Speculative connection should be triggered on link hover"
    152      );
    153 
    154      // Now click the link - it should use the speculative connection
    155      await clickLink(browser, "testLink");
    156 
    157      // Check connection count after click
    158      let connectionCountAfterClick = gServer.connectionNumber;
    159      info("Connection count after click: " + connectionCountAfterClick);
    160      info(
    161        "Connection number that handled the request: " +
    162          gConnectionNumberWhenRequested
    163      );
    164 
    165      // Verify that exactly one NEW connection was established
    166      Assert.equal(
    167        connectionCountAfterClick,
    168        connectionCountBeforeHover + 1,
    169        "Exactly one connection should be established for the HTTP request"
    170      );
    171 
    172      // Verify that the request was handled by the new connection
    173      Assert.equal(
    174        gConnectionNumberWhenRequested,
    175        connectionCountBeforeHover + 1,
    176        "The HTTP request should be handled by the new connection"
    177      );
    178    }
    179  );
    180 
    181  Services.obs.removeObserver(observer, "speculative-connect-request");
    182 });
    183 
    184 add_task(async function test_link_hover_http_page() {
    185  let speculativeConnectObserved = false;
    186  let observer = {
    187    QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
    188    observe(aSubject, aTopic, aData) {
    189      if (
    190        aTopic == "speculative-connect-request" &&
    191        aData.includes("localhost")
    192      ) {
    193        info("Observed speculative connection request for: " + aData);
    194        speculativeConnectObserved = true;
    195      }
    196    },
    197  };
    198  Services.obs.addObserver(observer, "speculative-connect-request");
    199 
    200  // Load the test page from HTTP example.com with the target URL pointing to our local server
    201  // eslint-disable-next-line @microsoft/sdl/no-insecure-url
    202  const targetURL = encodeURIComponent(gServerURL + "/target.html");
    203  const pageURL =
    204    // eslint-disable-next-line @microsoft/sdl/no-insecure-url
    205    "http://example.com" +
    206    TEST_PATH +
    207    "file_link_hover.sjs?target=" +
    208    targetURL;
    209 
    210  await BrowserTestUtils.withNewTab(
    211    {
    212      gBrowser,
    213      url: pageURL,
    214      waitForLoad: true,
    215    },
    216    async function (browser) {
    217      // Record the current connection count before hovering
    218      // This should be 0 since we haven't connected to our server yet
    219      let connectionCountBeforeHover = gServer.connectionNumber;
    220      info("Connection count before hover: " + connectionCountBeforeHover);
    221 
    222      // Wait for the link element to be available in the page
    223      await SpecialPowers.spawn(browser, [], async () => {
    224        await ContentTaskUtils.waitForCondition(
    225          () => content.document.getElementById("testLink"),
    226          "Waiting for link element to be available"
    227        );
    228      });
    229 
    230      // Hover over the link to trigger speculative connection
    231      await hoverOverLink(browser, "testLink");
    232 
    233      // Wait for the speculative connection to be fully established
    234      // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    235      await new Promise(resolve => setTimeout(resolve, 1000));
    236 
    237      // Check connection count after hover
    238      let connectionCountAfterHover = gServer.connectionNumber;
    239      info("Connection count after hover: " + connectionCountAfterHover);
    240 
    241      // Verify that a speculative connection request was observed
    242      Assert.ok(
    243        speculativeConnectObserved,
    244        "Speculative connection should be triggered on link hover from HTTP page"
    245      );
    246 
    247      // Now click the link - it should use the speculative connection
    248      await clickLink(browser, "testLink");
    249 
    250      // Check connection count after click
    251      let connectionCountAfterClick = gServer.connectionNumber;
    252      info("Connection count after click: " + connectionCountAfterClick);
    253      info(
    254        "Connection number that handled the request: " +
    255          gConnectionNumberWhenRequested
    256      );
    257 
    258      // Verify that exactly one NEW connection was established
    259      Assert.equal(
    260        connectionCountAfterClick,
    261        connectionCountBeforeHover + 1,
    262        "Exactly one connection should be established for the HTTP request"
    263      );
    264 
    265      // Verify that the request was handled by the new connection
    266      Assert.equal(
    267        gConnectionNumberWhenRequested,
    268        connectionCountBeforeHover + 1,
    269        "The HTTP request should be handled by the new connection"
    270      );
    271    }
    272  );
    273 
    274  Services.obs.removeObserver(observer, "speculative-connect-request");
    275 });
    276 
    277 add_task(async function test_link_hover_https_page_pref_disabled() {
    278  // Disable the pref for speculative connections on HTTPS
    279  await SpecialPowers.pushPrefEnv({
    280    set: [["network.predictor.enable-hover-on-ssl", false]],
    281  });
    282 
    283  let speculativeConnectObserved = false;
    284  let observer = {
    285    QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
    286    observe(aSubject, aTopic, aData) {
    287      if (
    288        aTopic == "speculative-connect-request" &&
    289        aData.includes("localhost")
    290      ) {
    291        info("Observed speculative connection request for: " + aData);
    292        speculativeConnectObserved = true;
    293      }
    294    },
    295  };
    296  Services.obs.addObserver(observer, "speculative-connect-request");
    297 
    298  // Load the test page from HTTPS example.com with the target URL pointing to our local server
    299  const targetURL = encodeURIComponent(gServerURL + "/target.html");
    300  const pageURL =
    301    "https://example.com" +
    302    TEST_PATH +
    303    "file_link_hover.sjs?target=" +
    304    targetURL;
    305 
    306  await BrowserTestUtils.withNewTab(
    307    {
    308      gBrowser,
    309      url: pageURL,
    310      waitForLoad: true,
    311    },
    312    async function (browser) {
    313      // Record the current connection count before hovering
    314      let connectionCountBeforeHover = gServer.connectionNumber;
    315      info("Connection count before hover: " + connectionCountBeforeHover);
    316 
    317      // Wait for the link element to be available in the page
    318      await SpecialPowers.spawn(browser, [], async () => {
    319        await ContentTaskUtils.waitForCondition(
    320          () => content.document.getElementById("testLink"),
    321          "Waiting for link element to be available"
    322        );
    323      });
    324 
    325      // Hover over the link
    326      await hoverOverLink(browser, "testLink");
    327 
    328      // Wait a bit to see if any speculative connection would be triggered
    329      // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    330      await new Promise(resolve => setTimeout(resolve, 1000));
    331 
    332      // Check connection count after hover
    333      let connectionCountAfterHover = gServer.connectionNumber;
    334      info("Connection count after hover: " + connectionCountAfterHover);
    335 
    336      // Verify that NO speculative connection request was observed
    337      Assert.ok(
    338        !speculativeConnectObserved,
    339        "Speculative connection should NOT be triggered on link hover from HTTPS page when pref is disabled"
    340      );
    341 
    342      // Now click the link - it will create a new connection
    343      await clickLink(browser, "testLink");
    344 
    345      // Check connection count after click
    346      let connectionCountAfterClick = gServer.connectionNumber;
    347      info("Connection count after click: " + connectionCountAfterClick);
    348 
    349      // Verify that exactly one NEW connection was created (by the click, not hover)
    350      Assert.equal(
    351        connectionCountAfterClick,
    352        connectionCountBeforeHover + 1,
    353        "Exactly one connection should be established (from the click, not hover)"
    354      );
    355    }
    356  );
    357 
    358  Services.obs.removeObserver(observer, "speculative-connect-request");
    359 
    360  // Restore the pref
    361  await SpecialPowers.popPrefEnv();
    362 });