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>
|