tor-browser

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

reuse-web-bundle-resource.https.tentative.html (11764B)


      1 <!DOCTYPE html>
      2 <title>script type="webbundle" reuses webbundle resources</title>
      3 <link
      4  rel="help"
      5  href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md"
      6 />
      7 <script src="/resources/testharness.js"></script>
      8 <script src="/resources/testharnessreport.js"></script>
      9 <script src="../resources/test-helpers.js"></script>
     10 
     11 <body>
     12  <script>
     13    setup(() => {
     14      assert_true(HTMLScriptElement.supports("webbundle"));
     15    });
     16 
     17    const wbn_url = "../resources/wbn/subresource.wbn";
     18    const wbn_suffix = "subresource.wbn";
     19    const resource1 = "root.js";
     20    const resource2 = "submodule.js";
     21 
     22    const resource1_url = `../resources/wbn/${resource1}`;
     23    const resource2_url = `../resources/wbn/${resource2}`;
     24 
     25    let script1;
     26    let script2;
     27 
     28    function cleanUp() {
     29      if (script1) {
     30        script1.remove();
     31      }
     32      if (script2) {
     33        script2.remove();
     34      }
     35    }
     36 
     37    async function assertResource1CanBeFetched() {
     38      const response = await fetch(resource1_url);
     39      const text = await response.text();
     40      assert_equals(text, "export * from './submodule.js';\n");
     41    }
     42 
     43    async function assertResource1CanNotBeFetched() {
     44      const response = await fetch(resource1_url);
     45      assert_equals(response.status, 404);
     46    }
     47 
     48    async function assertResource2CanBeFetched() {
     49      const response = await fetch(resource2_url);
     50      const text = await response.text();
     51      assert_equals(text, "export const result = 'OK';\n");
     52    }
     53 
     54    function createScriptWebBundle1() {
     55      return createWebBundleElement(wbn_url, /*resources=*/ [resource1]);
     56    }
     57 
     58    function createScriptWebBundle2(options) {
     59      return createWebBundleElement(
     60        wbn_url,
     61        /*resources=*/ [resource2],
     62        /*options=*/ options
     63      );
     64    }
     65 
     66    async function appendScriptWebBundle1AndFetchResource1() {
     67      clearWebBundleFetchCount();
     68      script1 = createScriptWebBundle1();
     69      document.body.append(script1);
     70      await assertResource1CanBeFetched();
     71      assert_equals(webBundleFetchCount(wbn_suffix), 1);
     72    }
     73 
     74    function clearWebBundleFetchCount() {
     75      performance.clearResourceTimings();
     76    }
     77 
     78    function webBundleFetchCount(web_bundle_suffix) {
     79      return performance
     80        .getEntriesByType("resource")
     81        .filter((e) => e.name.endsWith(web_bundle_suffix)).length;
     82    }
     83 
     84    promise_test(async (t) => {
     85      t.add_cleanup(cleanUp);
     86      await appendScriptWebBundle1AndFetchResource1();
     87      clearWebBundleFetchCount();
     88 
     89      // Append script2 without removing script1.
     90      // script2 should fetch the wbn again.
     91      script2 = createScriptWebBundle2();
     92      document.body.appendChild(script2);
     93 
     94      await assertResource1CanBeFetched();
     95      await assertResource2CanBeFetched();
     96      assert_equals(webBundleFetchCount(wbn_suffix), 1);
     97    }, "A webbundle should be fetched again when new script element is appended.");
     98 
     99    promise_test(async (t) => {
    100      t.add_cleanup(cleanUp);
    101      await appendScriptWebBundle1AndFetchResource1();
    102      clearWebBundleFetchCount();
    103 
    104      // Remove script1, then append script2
    105      // script2 should reuse webbundle resources.
    106      script1.remove();
    107      script2 = createScriptWebBundle2();
    108      document.body.append(script2);
    109 
    110      await assertResource1CanNotBeFetched();
    111      await assertResource2CanBeFetched();
    112      assert_equals(webBundleFetchCount(wbn_suffix), 0);
    113    }, "'remove(), then append()' should reuse webbundle resources");
    114 
    115    promise_test(async (t) => {
    116      t.add_cleanup(cleanUp);
    117      clearWebBundleFetchCount();
    118      script1 = createScriptWebBundle1();
    119      await addElementAndWaitForLoad(script1);
    120      clearWebBundleFetchCount();
    121 
    122      // Remove script1, then append script2
    123      // script2 should reuse webbundle resources.
    124      // And it should also fire a load event.
    125      script1.remove();
    126      script2 = createScriptWebBundle2();
    127      await addElementAndWaitForLoad(script2);
    128 
    129      await assertResource1CanNotBeFetched();
    130      await assertResource2CanBeFetched();
    131      assert_equals(webBundleFetchCount(wbn_suffix), 0);
    132    }, "'remove(), then append()' should reuse webbundle resources and both scripts should fire load events");
    133 
    134    promise_test(async (t) => {
    135      t.add_cleanup(cleanUp);
    136      script1 = createWebBundleElement("nonexistent.wbn", []);
    137      await addElementAndWaitForError(script1);
    138 
    139      // Remove script1, then append script2
    140      // script2 should reuse webbundle resources (but we don't verify that).
    141      // And it should also fire an error event.
    142      script1.remove();
    143      script2 = createWebBundleElement("nonexistent.wbn", []);
    144      await addElementAndWaitForError(script2);
    145    }, "'remove(), then append()' should reuse webbundle resources and both scripts should fire error events");
    146 
    147    promise_test(async (t) => {
    148      t.add_cleanup(cleanUp);
    149      await appendScriptWebBundle1AndFetchResource1();
    150      clearWebBundleFetchCount();
    151 
    152      // Remove script1, then append script2 with an explicit 'same-origin' credentials mode.
    153      script1.remove();
    154      script2 = createScriptWebBundle2({ credentials: "same-origin" });
    155      document.body.append(script2);
    156 
    157      await assertResource1CanNotBeFetched();
    158      await assertResource2CanBeFetched();
    159      assert_equals(webBundleFetchCount(wbn_suffix), 0);
    160    }, "Should reuse webbundle resources if a credential mode is same");
    161 
    162    promise_test(async (t) => {
    163      t.add_cleanup(cleanUp);
    164      await appendScriptWebBundle1AndFetchResource1();
    165      clearWebBundleFetchCount();
    166 
    167      // Remove script1, then append script2 with a different credentials mode.
    168      script1.remove();
    169      script2 = createScriptWebBundle2({ credentials: "omit" });
    170      document.body.append(script2);
    171 
    172      await assertResource1CanNotBeFetched();
    173      await assertResource2CanBeFetched();
    174      assert_equals(webBundleFetchCount(wbn_suffix), 1);
    175    }, "Should not reuse webbundle resources if a credentials mode is different (same-origin vs omit)");
    176 
    177    promise_test(async (t) => {
    178      t.add_cleanup(cleanUp);
    179      await appendScriptWebBundle1AndFetchResource1();
    180      clearWebBundleFetchCount();
    181 
    182      // Remove script1, then append script2 with a different credentials mode.
    183      script1.remove();
    184      script2 = createScriptWebBundle2({ credentials: "include" });
    185      document.body.append(script2);
    186 
    187      await assertResource1CanNotBeFetched();
    188      await assertResource2CanBeFetched();
    189      assert_equals(webBundleFetchCount(wbn_suffix), 1);
    190    }, "Should not reuse webbundle resources if a credential mode is different (same-origin vs include");
    191 
    192    promise_test(async (t) => {
    193      t.add_cleanup(cleanUp);
    194      await appendScriptWebBundle1AndFetchResource1();
    195      clearWebBundleFetchCount();
    196 
    197      // Remove script1, then append the removed one.
    198      script1.remove();
    199      document.body.append(script1);
    200 
    201      await assertResource1CanNotBeFetched();
    202      assert_equals(webBundleFetchCount(wbn_suffix), 0);
    203    }, "'remove(), then append()' for the same element should reuse webbundle resources");
    204 
    205    promise_test(async (t) => {
    206      t.add_cleanup(cleanUp);
    207      await appendScriptWebBundle1AndFetchResource1();
    208      clearWebBundleFetchCount();
    209 
    210      // Multiple 'remove(), then append()' for the same element.
    211      script1.remove();
    212      document.body.append(script1);
    213 
    214      script1.remove();
    215      document.body.append(script1);
    216 
    217      await assertResource1CanNotBeFetched();
    218      assert_equals(webBundleFetchCount(wbn_suffix), 0);
    219    }, "Multiple 'remove(), then append()' for the same element should reuse webbundle resources");
    220 
    221    promise_test(async (t) => {
    222      t.add_cleanup(cleanUp);
    223      await appendScriptWebBundle1AndFetchResource1();
    224      clearWebBundleFetchCount();
    225 
    226      // Remove script1.
    227      script1.remove();
    228 
    229      // Then append script2 in a separet task.
    230      await new Promise((resolve) => t.step_timeout(resolve, 0));
    231      script2 = createScriptWebBundle2();
    232      document.body.append(script2);
    233 
    234      await assertResource1CanNotBeFetched();
    235      await assertResource2CanBeFetched();
    236      assert_equals(webBundleFetchCount(wbn_suffix), 1);
    237    }, "'remove(), then append() in a separate task' should not reuse webbundle resources");
    238 
    239    promise_test(async (t) => {
    240      t.add_cleanup(cleanUp);
    241      await appendScriptWebBundle1AndFetchResource1();
    242      clearWebBundleFetchCount();
    243 
    244      // Use replaceWith() to replace script1 with script2.
    245      // script2 should reuse webbundle resources.
    246      script2 = createScriptWebBundle2();
    247      script1.replaceWith(script2);
    248 
    249      await assertResource1CanNotBeFetched();
    250      await assertResource2CanBeFetched();
    251 
    252      assert_equals(webBundleFetchCount(wbn_suffix), 0);
    253    }, "replaceWith() should reuse webbundle resources.");
    254 
    255    promise_test(async (t) => {
    256      t.add_cleanup(cleanUp);
    257      await appendScriptWebBundle1AndFetchResource1();
    258      clearWebBundleFetchCount();
    259 
    260      // Move script1 to another document. Then append script2.
    261      // script2 should reuse webbundle resources.
    262      const another_document = new Document();
    263      another_document.append(script1);
    264      script2 = createScriptWebBundle2();
    265      document.body.append(script2);
    266 
    267      await assertResource1CanNotBeFetched();
    268      await assertResource2CanBeFetched();
    269 
    270      assert_equals(webBundleFetchCount(wbn_suffix), 0);
    271 
    272      // TODO: Test the following cases:
    273      // - The resources are not loaded from the webbundle in the new Document
    274      // (Probably better to use a <iframe>.contentDocument)
    275      // - Even if we move the script element back to the original Document,
    276      // even immediately, the resources are not loaded from the webbundle in the
    277      // original Document.
    278    }, "append() should reuse webbundle resources even if the old script was moved to another document.");
    279 
    280    promise_test(async (t) => {
    281      t.add_cleanup(cleanUp);
    282      clearWebBundleFetchCount();
    283      script1 = createWebBundleElement(
    284        wbn_url + "?pipe=trickle(d0.1)",
    285        [resource1]
    286      );
    287      document.body.appendChild(script1);
    288 
    289      // While script1 is still loading, remove it and make script2
    290      // reuse the resources.
    291      script1.remove();
    292      script2 = createWebBundleElement(
    293        wbn_url + "?pipe=trickle(d0.1)",
    294        [resource2]
    295      );
    296      await addElementAndWaitForLoad(script2);
    297 
    298      assert_equals(webBundleFetchCount(wbn_suffix + "?pipe=trickle(d0.1)"), 1);
    299    }, "Even if the bundle is still loading, we should reuse the resources.");
    300 
    301    promise_test(async (t) => {
    302      t.add_cleanup(cleanUp);
    303      script1 = createScriptWebBundle1();
    304      document.body.appendChild(script1);
    305 
    306      // Don't wait for the load event for script1.
    307      script1.remove();
    308      script2 = createScriptWebBundle2();
    309 
    310      // Load event should be fired for script2 regardless of
    311      // whether script1 fired a load or not.
    312      await addElementAndWaitForLoad(script2);
    313    }, "When reusing the resources with script2, a load event should be fired regardless of if the script1 fired a load");
    314 
    315    promise_test(async (t) => {
    316      t.add_cleanup(cleanUp);
    317      script1 = createWebBundleElement("nonexistent.wbn", []);
    318      document.body.appendChild(script1);
    319 
    320      // Don't wait for the error event for script1.
    321      script1.remove();
    322      script2 = createWebBundleElement("nonexistent.wbn", []);
    323 
    324      // Error event should be fired for script2 regardless of
    325      // whether script1 fired an error event or not.
    326      await addElementAndWaitForError(script2);
    327    }, "When reusing the resources with script2, an error event should be fired regardless of if the script1 fired an error");
    328  </script>
    329 </body>