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>