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
|
<!DOCTYPE html>
<title>Subresource loading with script type="webbundle"</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 type="webbundle">
{
"source": "../resources/wbn/subresource.wbn",
"resources": [
"https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/root.js",
"https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/submodule.js"
]
}
</script>
<script>
setup(() => {
assert_true(HTMLScriptElement.supports("webbundle"));
});
promise_test(async () => {
const module = await import(
"https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/root.js"
);
assert_equals(module.result, "OK");
}, "Subresource loading with WebBundle");
promise_test(async () => {
const response = await fetch(
"https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/root.js"
);
const text = await response.text();
assert_equals(text, "export * from './submodule.js';\n");
}, "Subresource loading with WebBundle (Fetch API)");
promise_test((t) => {
const url =
"/common/redirect.py?location=https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/root.js";
return promise_rejects_js(t, TypeError, import(url));
}, "Subresource loading with WebBundle shouldn't affect redirect");
promise_test(async () => {
const element = createWebBundleElement("../resources/wbn/dynamic1.wbn", [
"https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/dynamic/resource1.js",
"https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/dynamic/resource2.js",
"https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/dynamic/resource4.js",
]);
document.body.appendChild(element);
const module = await import(
"https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/dynamic/resource1.js"
);
assert_equals(module.result, "resource1 from dynamic1.wbn");
const new_element = removeAndAppendNewElementWithUpdatedRule(element, {
url: "../resources/wbn/dynamic2.wbn",
});
const module2 = await import(
"https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/dynamic/resource2.js"
);
assert_equals(module2.result, "resource2 from dynamic2.wbn");
// A resource not specified in the resources attribute, but in the bundle.
const module3 = await import(
"https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/dynamic/resource3.js"
);
assert_equals(module3.result, "resource3 from network");
document.body.removeChild(new_element);
const module4 = await import(
"https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/dynamic/resource4.js"
);
assert_equals(module4.result, "resource4 from network");
// Module scripts are stored to the Document's module map once loaded.
// So import()ing the same module script will reuse the previously loaded
// script.
const module_second = await import(
"https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/dynamic/resource1.js"
);
assert_equals(module_second.result, "resource1 from dynamic1.wbn");
}, "Dynamically adding / updating / removing the webbundle element.");
promise_test(async () => {
const classic_script_url =
"https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/dynamic/classic_script.js";
const element = createWebBundleElement("../resources/wbn/dynamic1.wbn", [
classic_script_url,
]);
document.body.appendChild(element);
assert_equals(
await loadScriptAndWaitReport(classic_script_url),
"classic script from dynamic1.wbn"
);
const new_element = removeAndAppendNewElementWithUpdatedRule(element, {
url: "../resources/wbn/dynamic2.wbn",
});
// Loading the classic script should not reuse the previously loaded
// script. So in this case, the script must be loaded from dynamic2.wbn.
assert_equals(
await loadScriptAndWaitReport(classic_script_url),
"classic script from dynamic2.wbn"
);
document.body.removeChild(new_element);
// And in this case, the script must be loaded from network.
assert_equals(
await loadScriptAndWaitReport(classic_script_url),
"classic script from network"
);
}, "Dynamically loading classic script from web bundle");
promise_test(async (t) => {
// To avoid caching mechanism, this test is using fetch() API with
// { cache: 'no-store' } to load the resource.
const classic_script_url =
"https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/dynamic/classic_script.js";
assert_equals(
await (await fetch(classic_script_url)).text(),
"window.report_result('classic script from network');\n"
);
const element1 = createWebBundleElement("../resources/wbn/dynamic1.wbn", [
classic_script_url,
]);
document.body.appendChild(element1);
t.add_cleanup(() => {
if (element1.parentElement)
element1.parentElement.removeChild(element1);
});
assert_equals(
await (await fetch(classic_script_url, { cache: "no-store" })).text(),
"window.report_result('classic script from dynamic1.wbn');\n"
);
const element2 = createWebBundleElement("../resources/wbn/dynamic2.wbn", [
classic_script_url,
]);
document.body.appendChild(element2);
t.add_cleanup(() => {
if (element2.parentElement)
element2.parentElement.removeChild(element2);
});
assert_equals(
await (await fetch(classic_script_url, { cache: "no-store" })).text(),
"window.report_result('classic script from dynamic2.wbn');\n"
);
document.body.removeChild(element2);
assert_equals(
await (await fetch(classic_script_url, { cache: "no-store" })).text(),
"window.report_result('classic script from dynamic1.wbn');\n"
);
document.body.removeChild(element1);
assert_equals(
await (await fetch(classic_script_url, { cache: "no-store" })).text(),
"window.report_result('classic script from network');\n"
);
}, "Multiple web bundle elements. The last added element must be refered.");
promise_test(async () => {
const classic_script_url =
"https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/dynamic/classic_script.js";
const scope =
"https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/dynamic/";
const element = createWebBundleElement(
"../resources/wbn/dynamic1.wbn",
[],
{ scopes: [scope] }
);
document.body.appendChild(element);
assert_equals(
await loadScriptAndWaitReport(classic_script_url),
"classic script from dynamic1.wbn"
);
const new_element = removeAndAppendNewElementWithUpdatedRule(element, {
url: "../resources/wbn/dynamic2.wbn",
});
// Loading the classic script should not reuse the previously loaded
// script. So in this case, the script must be loaded from dynamic2.wbn.
assert_equals(
await loadScriptAndWaitReport(classic_script_url),
"classic script from dynamic2.wbn"
);
// Changes the scope not to hit the classic_script.js.
const new_element2 = removeAndAppendNewElementWithUpdatedRule(
new_element,
{ scopes: [scope + "dummy"] }
);
// And in this case, the script must be loaded from network.
assert_equals(
await loadScriptAndWaitReport(classic_script_url),
"classic script from network"
);
// Adds the scope to hit the classic_script.js.
const new_element3 = removeAndAppendNewElementWithUpdatedRule(
new_element2,
{ scopes: [scope + "dummy", scope + "classic_"] }
);
assert_equals(
await loadScriptAndWaitReport(classic_script_url),
"classic script from dynamic2.wbn"
);
document.body.removeChild(new_element3);
// And in this case, the script must be loaded from network.
assert_equals(
await loadScriptAndWaitReport(classic_script_url),
"classic script from network"
);
}, "Dynamically loading classic script from web bundle with scopes");
promise_test(() => {
return addWebBundleElementAndWaitForLoad(
"../resources/wbn/dynamic1.wbn?test-event",
/*resources=*/ [],
{ crossOrigin: undefined }
);
}, "The webbundle element fires a load event on load success");
promise_test((t) => {
return addWebBundleElementAndWaitForError(
"../resources/wbn/nonexistent.wbn",
/*resources=*/ [],
{ crossOrigin: undefined }
);
}, "The webbundle element fires an error event on load failure");
promise_test(async () => {
const module_script_url =
"https://www1.{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/dynamic/resource1.js";
const element = createWebBundleElement(
"../resources/wbn/dynamic1-crossorigin.wbn",
[module_script_url]
);
document.body.appendChild(element);
const module = await import(module_script_url);
assert_equals(module.result, "resource1 from network");
}, "Subresource URL must be same-origin with bundle URL");
promise_test(async () => {
const url = "uuid-in-package:020111b3-437a-4c5c-ae07-adb6bbffb720";
const element = createWebBundleElement(
"../resources/wbn/uuid-in-package.wbn",
[url]
);
document.body.appendChild(element);
assert_equals(await loadScriptAndWaitReport(url), "OK");
document.body.removeChild(element);
}, "Subresource loading with uuid-in-package: URL with resources attribute");
promise_test(async () => {
const url = "uuid-in-package:020111b3-437a-4c5c-ae07-adb6bbffb720";
const element = createWebBundleElement(
"../resources/wbn/uuid-in-package.wbn",
[],
{ scopes: ["uuid-in-package:"] }
);
document.body.appendChild(element);
assert_equals(await loadScriptAndWaitReport(url), "OK");
document.body.removeChild(element);
}, "Subresource loading with uuid-in-package: URL with scopes attribute");
async function loadScriptAndWaitReport(script_url) {
const result_promise = new Promise((resolve) => {
// This function will be called from script.js
window.report_result = resolve;
});
const script = document.createElement("script");
script.src = script_url;
document.body.appendChild(script);
return result_promise;
}
function removeAndAppendNewElementWithUpdatedRule(element, new_rule) {
const new_element = createNewWebBundleElementWithUpdatedRule(
element,
new_rule
);
element.remove();
document.body.appendChild(new_element);
return new_element;
}
</script>
</body>
|