tor-browser

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

racing-soft-navigations.html (8764B)


      1 <!doctype html>
      2 <html>
      3  <head>
      4    <meta charset="utf-8" />
      5    <title>Two soft navigations racing each other.</title>
      6    <script src="/resources/testharness.js"></script>
      7    <script src="/resources/testharnessreport.js"></script>
      8    <script src="/resources/testdriver.js"></script>
      9    <script src="/resources/testdriver-vendor.js"></script>
     10    <script></script>
     11  </head>
     12  <body>
     13    <div id="first_interaction">Click here!</div>
     14    <div id="second_interaction">Click here!</div>
     15 
     16    <script>
     17      const FIRST_URL = "first-url";
     18      const SECOND_URL = "second-url";
     19 
     20      const button1 = document.getElementById("first_interaction");
     21      const button2 = document.getElementById("second_interaction");
     22 
     23      async function updateUI() {
     24          const greeting = document.createElement("div");
     25          greeting.textContent = "Hello, World.";
     26          document.body.appendChild(greeting);
     27      }
     28 
     29      function updateUrl(t, url) {
     30        t.state.numPushStateCalls++;
     31        const actual_url = t.state.urlPrefix + url;
     32        history.pushState({}, "", actual_url);
     33      }
     34 
     35      async function waitForSoftNavEntry(t, count = 1) {
     36        return t.step_wait(() => t.state.softNavEntries.length >= count);
     37      }
     38 
     39      async function create_test(urlPrefix, callback) {
     40        return promise_test(async (t) => {
     41          const currentUrl = location.pathname.replace(/.*\//, "");
     42          assert_equals(currentUrl, "racing-soft-navigations.html");
     43 
     44          t.state = {};
     45          t.state.urlPrefix = urlPrefix;
     46          t.state.numPushStateCalls = 0;
     47          t.state.softNavEntries = [];
     48          const observer = new PerformanceObserver((list, observer) => {
     49            // If we get two soft-navs in one observer callback...
     50            // that is a sign that we emitted multiple for a single effect
     51            const entries = list.getEntries();
     52            assert_equals(entries.length, 1, "Expecting a single soft navigation");
     53            t.state.softNavEntries.push(entries[0]);
     54          });
     55          observer.observe({ type: 'soft-navigation' });
     56 
     57          // We have multiple test cases with side effects, so add some cleanup.
     58          t.add_cleanup(async () => {
     59            observer.disconnect();
     60 
     61            // Go back to the original URL
     62            for (let i = 0; i < t.state.numPushStateCalls; i++) {
     63              history.back();
     64              await new Promise(resolve => {
     65                addEventListener('popstate', resolve, {once: true});
     66              });
     67            }
     68          });
     69 
     70          return callback(t);
     71        }, "Racing multiple overlapping interactions and soft navs: " + urlPrefix);
     72      }
     73 
     74      async function expectationsMultipleInteractionTest(t, expected = [FIRST_URL, SECOND_URL]) {
     75        const count = expected.length;
     76        await t.step_wait(() => t.state.softNavEntries.length >= count, `Wait for ${count} soft navigation entries`);
     77 
     78        // Although we await at least `count` (above), we also assert exactly `count` (here)
     79        assert_equals(t.state.softNavEntries.length, count, `Expected ${count} soft navigation entries`);
     80 
     81        for (let i = 0; i < count; i++) {
     82          const entry = t.state.softNavEntries[i];
     83          const actual_expected_url = t.state.urlPrefix + expected[i];
     84          assert_equals(
     85            entry.name.replace(/.*\//, ""),
     86            actual_expected_url,
     87            "Expect to observe the first URL change.",
     88          );
     89        }
     90      }
     91 
     92      // The following tests will trigger two interaction back to back, and each
     93      // interaction will do a sequence of the following:
     94      // - Triggers event listener, which schedules async work
     95      // - updates URL
     96      // - updates UI
     97      // - yield, or timeout of some kind
     98      // - Emit a soft nav entry
     99      //
    100      // Because there are two interactions per test, we manipulate the
    101      // sequence of operations in various ways.
    102 
    103      // Baseline, non overlapping interactions.
    104      create_test("click1,url1,ui1,sn1,yield,click2,url2,ui2,sn2", async (t) => {
    105        button1.addEventListener('click', async () => {
    106          updateUrl(t, FIRST_URL);
    107          updateUI();
    108        }, { once: true });
    109 
    110        button2.addEventListener('click', async () => {
    111          updateUrl(t, SECOND_URL);
    112          updateUI();
    113        }, { once: true });
    114 
    115        if (test_driver) {
    116          test_driver.click(button1);
    117        }
    118 
    119        await waitForSoftNavEntry(t);
    120 
    121        if(test_driver) {
    122          test_driver.click(button2);
    123        }
    124 
    125        await expectationsMultipleInteractionTest(t);
    126      });
    127 
    128      // Both interactions start and yield (simulate network), then finish all
    129      // required effects and emit soft nav without overlap. First interaction
    130      // wins the "network" race.
    131      create_test("click1,yield,click2,yield,url1,ui1,sn1,yield,url2,ui2,sn2", async (t) => {
    132        button1.addEventListener('click', async () => {
    133          t.step_timeout(() => {
    134            updateUrl(t, FIRST_URL);
    135            updateUI();
    136          }, 0);
    137        }, { once: true });
    138 
    139        button2.addEventListener('click', async () => {
    140          await waitForSoftNavEntry(t);
    141 
    142          updateUrl(t, SECOND_URL);
    143          updateUI();
    144        }, { once: true });
    145 
    146        // Start both soft navigations in rapid succession.
    147        if (test_driver) {
    148          test_driver.click(button1);
    149          test_driver.click(button2);
    150        }
    151 
    152        await expectationsMultipleInteractionTest(t);
    153 
    154      });
    155 
    156      // Both interactions start and yield (simulate network), then finish all
    157      // required effects and emit soft nav without overlap. Second interaction
    158      // wins the "network" race.
    159      create_test("click1,yield,click2,yield,url2,ui2,sn2,yield,url1,ui1,sn1", async (t) => {
    160        button1.addEventListener('click', async () => {
    161          await waitForSoftNavEntry(t);
    162          // In this test, the first interaction sets the second URL
    163          updateUrl(t, FIRST_URL);
    164          updateUI();
    165        }, { once: true });
    166 
    167        button2.addEventListener('click', async () => {
    168          t.step_timeout(() => {
    169            updateUrl(t, SECOND_URL);
    170            updateUI();
    171          }, 0);
    172        }, { once: true });
    173 
    174        // Start both soft navigations in rapid succession.
    175        if (test_driver) {
    176          test_driver.click(button1);
    177          test_driver.click(button2);
    178        }
    179 
    180        await expectationsMultipleInteractionTest(t, [SECOND_URL, FIRST_URL]);
    181      });
    182 
    183      // Both interactions start, immediately update URL and yield (simulate
    184      // navigate interception), then finish all required effects later.
    185      // Only the second URL update emits a soft nav entry.
    186      create_test("click1,url1,yield,click2,url2,ui1,yield,ui2,sn2", async (t) => {
    187        let first_interaction_did_finish_paint = false;
    188        let second_interaction_did_run = false;
    189 
    190        button1.addEventListener('click', async () => {
    191          updateUrl(t, FIRST_URL);
    192 
    193          await t.step_wait(() => second_interaction_did_run);
    194 
    195          updateUI();
    196 
    197          await new Promise(r => requestAnimationFrame(r));
    198          await new Promise(r => t.step_timeout(r, 0));
    199          first_interaction_did_finish_paint = true;
    200        }, { once: true });
    201 
    202        button2.addEventListener('click', async () => {
    203          updateUrl(t, SECOND_URL);
    204          second_interaction_did_run = true;
    205 
    206          await t.step_wait(() => first_interaction_did_finish_paint);
    207 
    208          updateUI();
    209        }, { once: true });
    210 
    211        // Start both soft navigations in rapid succession.
    212        if (test_driver) {
    213          test_driver.click(button1);
    214          test_driver.click(button2);
    215        }
    216 
    217        await expectationsMultipleInteractionTest(t,[SECOND_URL]);
    218      });
    219 
    220      // Both interactions start, immediately update URL and yield (simulate
    221      // navigate interception), then finish all required effects later.
    222      // Only the second URL update emits a soft nav entry.
    223      create_test("click1,url1,yield,click2,url2,yield,ui2,sn2,yield,ui1", async (t) => {
    224        button1.addEventListener('click', async () => {
    225          updateUrl(t, FIRST_URL);
    226          await waitForSoftNavEntry(t);
    227          updateUI();
    228        }, { once: true });
    229 
    230        button2.addEventListener('click', async () => {
    231          updateUrl(t, SECOND_URL);
    232 
    233          t.step_timeout(async () => {
    234            updateUI();
    235          }, 100);
    236        }, { once: true });
    237 
    238        // Start both soft navigations in rapid succession.
    239        if (test_driver) {
    240          test_driver.click(button1);
    241          test_driver.click(button2);
    242        }
    243 
    244        await expectationsMultipleInteractionTest(t,[SECOND_URL]);
    245      });
    246 
    247    </script>
    248  </body>
    249 </html>