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