summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/web-bundle/subresource-loading
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/web-bundle/subresource-loading')
-rw-r--r--testing/web-platform/tests/web-bundle/subresource-loading/accept-header.https.tentative.html22
-rw-r--r--testing/web-platform/tests/web-bundle/subresource-loading/coep.https.tentative.html127
-rw-r--r--testing/web-platform/tests/web-bundle/subresource-loading/coep.https.tentative.html.headers2
-rw-r--r--testing/web-platform/tests/web-bundle/subresource-loading/corp.https.tentative.html73
-rw-r--r--testing/web-platform/tests/web-bundle/subresource-loading/credentials.https.tentative.sub.html173
-rw-r--r--testing/web-platform/tests/web-bundle/subresource-loading/csp-allowed.https.tentative.html89
-rw-r--r--testing/web-platform/tests/web-bundle/subresource-loading/csp-blocked.https.tentative.html162
-rw-r--r--testing/web-platform/tests/web-bundle/subresource-loading/csp-blocked.https.tentative.html.sub.headers6
-rw-r--r--testing/web-platform/tests/web-bundle/subresource-loading/csp-blockes-bundle.https.tentative.html66
-rw-r--r--testing/web-platform/tests/web-bundle/subresource-loading/element-removal.https.tentative.html32
-rw-r--r--testing/web-platform/tests/web-bundle/subresource-loading/invalid-json.https.tentative.html47
-rw-r--r--testing/web-platform/tests/web-bundle/subresource-loading/nested-bundle.https.tentative.html36
-rw-r--r--testing/web-platform/tests/web-bundle/subresource-loading/network-error.https.tentative.sub.html31
-rw-r--r--testing/web-platform/tests/web-bundle/subresource-loading/not-found.https.tentative.html33
-rw-r--r--testing/web-platform/tests/web-bundle/subresource-loading/path-restriction.https.tentative.html52
-rw-r--r--testing/web-platform/tests/web-bundle/subresource-loading/relative-url-in-web-bundle-cors.https.tentative.sub.html60
-rw-r--r--testing/web-platform/tests/web-bundle/subresource-loading/relative-url-in-web-bundle.https.tentative.html60
-rw-r--r--testing/web-platform/tests/web-bundle/subresource-loading/relative-url-resources.https.tentative.html80
-rw-r--r--testing/web-platform/tests/web-bundle/subresource-loading/relative-url-scopes.https.tentative.html85
-rw-r--r--testing/web-platform/tests/web-bundle/subresource-loading/relative-url-static-element.https.tentative.html34
-rw-r--r--testing/web-platform/tests/web-bundle/subresource-loading/relative-url-with-base.https.tentative.html67
-rw-r--r--testing/web-platform/tests/web-bundle/subresource-loading/request-destination.https.tentative.sub.html49
-rw-r--r--testing/web-platform/tests/web-bundle/subresource-loading/resource-timing-attributes-consistent.https.tentative.sub.html88
-rw-r--r--testing/web-platform/tests/web-bundle/subresource-loading/resource-timing.https.tentative.html55
-rw-r--r--testing/web-platform/tests/web-bundle/subresource-loading/resources/check-cookie-and-return-bundle.py25
-rw-r--r--testing/web-platform/tests/web-bundle/subresource-loading/resources/service-worker-controlled-iframe.html1
-rw-r--r--testing/web-platform/tests/web-bundle/subresource-loading/resources/service-worker-for-request-monitor.js12
-rw-r--r--testing/web-platform/tests/web-bundle/subresource-loading/reuse-web-bundle-resource.https.tentative.html329
-rw-r--r--testing/web-platform/tests/web-bundle/subresource-loading/service-worker-controlled.https.tentative.html138
-rw-r--r--testing/web-platform/tests/web-bundle/subresource-loading/static-element-with-base.https.tentative.sub.html65
-rw-r--r--testing/web-platform/tests/web-bundle/subresource-loading/static-element.https.tentative.sub.html68
-rw-r--r--testing/web-platform/tests/web-bundle/subresource-loading/subframe-from-web-bundle.https.tentative.html134
-rw-r--r--testing/web-platform/tests/web-bundle/subresource-loading/subresource-load.https.tentative.sub.html294
-rw-r--r--testing/web-platform/tests/web-bundle/subresource-loading/supports-webbundle.https.tentative.html15
34 files changed, 2610 insertions, 0 deletions
diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/accept-header.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/accept-header.https.tentative.html
new file mode 100644
index 0000000000..68b13e53c6
--- /dev/null
+++ b/testing/web-platform/tests/web-bundle/subresource-loading/accept-header.https.tentative.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<title>Accept: request header in webbundle requests</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"));
+ });
+ promise_test(async () => {
+ await addWebBundleElementAndWaitForLoad(
+ "../resources/check-accept-header-and-return-bundle.py",
+ /*resources=*/ []
+ );
+ }, "Accept: header in a request for a bundle should contain application/webbundle MIME type.");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/coep.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/coep.https.tentative.html
new file mode 100644
index 0000000000..4029fc6f81
--- /dev/null
+++ b/testing/web-platform/tests/web-bundle/subresource-loading/coep.https.tentative.html
@@ -0,0 +1,127 @@
+<!DOCTYPE html>
+<title>COEP for WebBundle subresource loading</title>
+<link
+ rel="help"
+ href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md"
+/>
+<link
+ rel="help"
+ href="https://html.spec.whatwg.org/multipage/origin.html#coep"
+/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/test-helpers.js"></script>
+
+<body>
+ <!--
+ This wpt should run on an origin different from https://www1.web-platform.test:8444/,
+ from where cross-orign WebBundles are served.
+
+ This test uses a cross-origin WebBundle,
+ https://www1.web-platform.test:8444/web-bundle/resources/wbn/cors/corp.wbn,
+ which is served with an Access-Control-Allow-Origin response header.
+
+ `corp.wbn` includes three subresources:
+ a. `no-corp.js`, which doesn't include a Cross-Origin-Resource-Policy response header.
+ b. `corp-same-origin.js`, which includes a Cross-Origin-Resource-Policy: same-origin response header.
+ c. `corp-cross-origin.js`, which includes a Cross-Origin-Resource-Policy: cross-origin response header.
+ -->
+ <script type="webbundle">
+ {
+ "source": "https://www1.web-platform.test:8444/web-bundle/resources/wbn/cors/corp.wbn",
+ "resources": [
+ "https://www1.web-platform.test:8444/web-bundle/resources/wbn/cors/no-corp.js",
+ "https://www1.web-platform.test:8444/web-bundle/resources/wbn/cors/corp-same-origin.js",
+ "https://www1.web-platform.test:8444/web-bundle/resources/wbn/cors/corp-cross-origin.js",
+ "uuid-in-package:5eafff38-e0a0-4661-bde0-434255aa9d93",
+ "uuid-in-package:7e13b47a-8b91-4a0e-997c-993a5e2f3a34",
+ "uuid-in-package:86d5b696-8867-4454-8b07-51239a0817f7"
+ ]
+ }
+ </script>
+ <script>
+ setup(() => {
+ assert_true(HTMLScriptElement.supports("webbundle"));
+ });
+
+ async function expectCOEPReport(func) {
+ const reportsPromise = new Promise((resolve) => {
+ const observer = new ReportingObserver((reports) => {
+ observer.disconnect();
+ resolve(reports.map((r) => r.toJSON()));
+ });
+ observer.observe();
+ });
+
+ await func();
+
+ const reports = await reportsPromise;
+ assert_equals(reports.length, 1);
+ assert_equals(reports[0].type, "coep");
+ assert_equals(reports[0].url, location.href);
+ return reports[0];
+ }
+
+ const prefix =
+ "https://www1.web-platform.test:8444/web-bundle/resources/wbn/cors/";
+ const no_corp_url = "uuid-in-package:5eafff38-e0a0-4661-bde0-434255aa9d93";
+ const corp_same_origin_url =
+ "uuid-in-package:7e13b47a-8b91-4a0e-997c-993a5e2f3a34";
+ const corp_cross_origin_url =
+ "uuid-in-package:86d5b696-8867-4454-8b07-51239a0817f7";
+
+ promise_test(async () => {
+ const report = await expectCOEPReport(async () => {
+ await addScriptAndWaitForError(prefix + "no-corp.js");
+ });
+ assert_equals(report.body.blockedURL, prefix + "no-corp.js");
+ assert_equals(report.body.type, "corp");
+ assert_equals(report.body.disposition, "enforce");
+ assert_equals(report.body.destination, "script");
+ }, "Cross-origin subresource without Cross-Origin-Resource-Policy: header should be blocked and generate a report.");
+
+ promise_test(async () => {
+ await addScriptAndWaitForError(prefix + "corp-same-origin.js");
+ }, "Cross-origin subresource with Cross-Origin-Resource-Policy: same-origin should be blocked.");
+
+ promise_test(async () => {
+ await addScriptAndWaitForExecution(prefix + "corp-cross-origin.js");
+ }, "Cross-origin subresource with Cross-Origin-Resource-Policy: cross-origin should be loaded.");
+
+ promise_test(async () => {
+ const report = await expectCOEPReport(async () => {
+ const iframe = document.createElement("iframe");
+ iframe.src = no_corp_url;
+ document.body.appendChild(iframe);
+ });
+
+ assert_equals(report.body.blockedURL, no_corp_url);
+ assert_equals(report.body.type, "corp");
+ assert_equals(report.body.disposition, "enforce");
+ assert_equals(report.body.destination, "iframe");
+ }, "uuid-in-package iframe without Cross-Origin-Resource-Policy: header should be blocked and generate a report.");
+
+ promise_test(async () => {
+ const report = await expectCOEPReport(async () => {
+ const iframe = document.createElement("iframe");
+ iframe.src = corp_same_origin_url;
+ document.body.appendChild(iframe);
+ });
+
+ assert_equals(report.body.blockedURL, corp_same_origin_url);
+ assert_equals(report.body.type, "corp");
+ assert_equals(report.body.disposition, "enforce");
+ assert_equals(report.body.destination, "iframe");
+ }, "uuid-in-package iframe with Cross-Origin-Resource-Policy: same-origin should be blocked and generate a report.");
+
+ promise_test(async () => {
+ const iframe = document.createElement("iframe");
+ iframe.src = corp_cross_origin_url;
+ await addElementAndWaitForLoad(iframe);
+ assert_equals(
+ await evalInIframe(iframe, "location.href"),
+ corp_cross_origin_url
+ );
+ }, "uuid-in-package iframe with Cross-Origin-Resource-Policy: cross-origin should not be blocked.");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/coep.https.tentative.html.headers b/testing/web-platform/tests/web-bundle/subresource-loading/coep.https.tentative.html.headers
new file mode 100644
index 0000000000..4e798cd9f5
--- /dev/null
+++ b/testing/web-platform/tests/web-bundle/subresource-loading/coep.https.tentative.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Embedder-Policy: require-corp
+Cross-Origin-Resource-Policy: cross-origin
diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/corp.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/corp.https.tentative.html
new file mode 100644
index 0000000000..ce18544b0b
--- /dev/null
+++ b/testing/web-platform/tests/web-bundle/subresource-loading/corp.https.tentative.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<title>CORP for WebBundle subresource loading</title>
+<link
+ rel="help"
+ href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md#cors-and-corp-for-subresource-requests"
+/>
+<link
+ rel="help"
+ href="https://fetch.spec.whatwg.org/#cross-origin-resource-policy-header"
+/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/test-helpers.js"></script>
+
+<body>
+ <!--
+ This wpt should run on an origin different from https://www1.web-platform.test:8444/,
+ from where cross-orign WebBundles are served.
+
+ This test uses a cross-origin WebBundle,
+ https://www1.web-platform.test:8444/web-bundle/resources/wbn/cors/corp.wbn,
+ which is served with an Access-Control-Allow-Origin response header.
+
+ `corp.wbn` includes three subresources:
+ a. `no-corp.js`, which doesn't include a Cross-Origin-Resource-Policy response header.
+ b. `corp-same-origin.js`, which includes a Cross-Origin-Resource-Policy: same-origin response header.
+ c. `corp-cross-origin.js`, which includes a Cross-Origin-Resource-Policy: cross-origin response header.
+ -->
+ <script type="webbundle">
+ {
+ "source": "https://www1.web-platform.test:8444/web-bundle/resources/wbn/cors/corp.wbn",
+ "resources": [
+ "https://www1.web-platform.test:8444/web-bundle/resources/wbn/cors/no-corp.js",
+ "https://www1.web-platform.test:8444/web-bundle/resources/wbn/cors/corp-same-origin.js",
+ "https://www1.web-platform.test:8444/web-bundle/resources/wbn/cors/corp-cross-origin.js",
+ "uuid-in-package:5eafff38-e0a0-4661-bde0-434255aa9d93",
+ "uuid-in-package:7e13b47a-8b91-4a0e-997c-993a5e2f3a34",
+ "uuid-in-package:86d5b696-8867-4454-8b07-51239a0817f7"
+ ]
+ }
+ </script>
+ <script>
+ setup(() => {
+ assert_true(HTMLScriptElement.supports("webbundle"));
+ });
+
+ promise_test(async () => {
+ const prefix =
+ "https://www1.web-platform.test:8444/web-bundle/resources/wbn/cors/";
+ await addScriptAndWaitForExecution(prefix + "no-corp.js");
+ await addScriptAndWaitForError(prefix + "corp-same-origin.js");
+ await addScriptAndWaitForExecution(prefix + "corp-cross-origin.js");
+ }, "Subresource loading from WebBundles should respect Cross-Origin-Resource-Policy header.");
+
+ promise_test(async () => {
+ const no_corp_url = "uuid-in-package:5eafff38-e0a0-4661-bde0-434255aa9d93";
+ const corp_same_origin_url =
+ "uuid-in-package:7e13b47a-8b91-4a0e-997c-993a5e2f3a34";
+ const corp_cross_origin_url =
+ "uuid-in-package:86d5b696-8867-4454-8b07-51239a0817f7";
+ await iframeLocationTest(no_corp_url);
+ await iframeLocationTest(corp_same_origin_url);
+ await iframeLocationTest(corp_cross_origin_url);
+ }, "uuid-in-package iframes should not be blocked regardless of the Cross-Origin-Resource-Policy header, if Cross-Origin-Embedder-Policy is not set.");
+
+ async function iframeLocationTest(url) {
+ const iframe = document.createElement("iframe");
+ iframe.src = url;
+ await addElementAndWaitForLoad(iframe);
+ assert_equals(await evalInIframe(iframe, "location.href"), url);
+ }
+ </script>
+</body>
diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/credentials.https.tentative.sub.html b/testing/web-platform/tests/web-bundle/subresource-loading/credentials.https.tentative.sub.html
new file mode 100644
index 0000000000..37efc37e6d
--- /dev/null
+++ b/testing/web-platform/tests/web-bundle/subresource-loading/credentials.https.tentative.sub.html
@@ -0,0 +1,173 @@
+<!DOCTYPE html>
+<title>Credentials in WebBundle subresource loading</title>
+<link
+ rel="help"
+ href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md#requests-mode-and-credentials-mode"
+/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/test-helpers.js"></script>
+<body>
+ <script>
+ // In this wpt, we test a request's credential mode, which controls
+ // whether UA sends a credential or not to fetch a bundle.
+
+ // If UA sends a credential, check-cookie-and-return-{cross-oriigin}-bundle.py
+ // returns a valid format webbundle. Then, a subresource fetch should be successful.
+ // Otherwise, a subresource fetch should be rejected.
+
+ setup(() => {
+ assert_true(HTMLScriptElement.supports("webbundle"));
+ });
+
+ document.cookie = "milk=1; path=/";
+
+ // Make sure to set a cookie for a cross-origin domain from where a cross
+ // origin bundle is served.
+ const setCookiePromise = fetch(
+ "https://{{domains[www1]}}:{{ports[https][0]}}/cookies/resources/set-cookie.py?name=milk&path=/web-bundle/resources/",
+ {
+ mode: "no-cors",
+ credentials: "include",
+ }
+ );
+
+ const same_origin_bundle = "../resources/check-cookie-and-return-bundle.py";
+ const cross_origin_bundle =
+ "https://{{domains[www1]}}:{{ports[https][0]}}/web-bundle/resources/check-cookie-and-return-bundle.py?bundle=cross-origin";
+
+ const same_origin_bundle_subresource = "../resources/wbn/root.js";
+ const cross_origin_bundle_subresource =
+ "https://{{domains[www1]}}:{{ports[https][0]}}/web-bundle/resources/wbn/simple-cross-origin.txt";
+
+ async function assertSubresourceCanBeFetched() {
+ const response = await fetch(same_origin_bundle_subresource);
+ const text = await response.text();
+ assert_equals(text, "export * from './submodule.js';\n");
+ }
+
+ async function assertCrossOriginSubresourceCanBeFetched() {
+ const response = await fetch(cross_origin_bundle_subresource);
+ const text = await response.text();
+ assert_equals(text, "hello from simple-cross-origin.txt");
+ }
+
+ function createScriptWebBundle(credentials) {
+ const options = {};
+ if (credentials) {
+ options.credentials = credentials;
+ }
+ return createWebBundleElement(
+ same_origin_bundle,
+ [same_origin_bundle_subresource],
+ options
+ );
+ }
+
+ function createScriptWebBundleCrossOrigin(credentials) {
+ const options = {};
+ if (credentials) {
+ options.credentials = credentials;
+ }
+ return createWebBundleElement(
+ cross_origin_bundle,
+ [cross_origin_bundle_subresource],
+ options
+ );
+ }
+
+ promise_test(async (t) => {
+ const script = createScriptWebBundle();
+ document.body.append(script);
+ t.add_cleanup(() => script.remove());
+
+ await assertSubresourceCanBeFetched();
+ }, "The default should send a credential to a same origin bundle");
+
+ promise_test(async (t) => {
+ const script = createScriptWebBundle("invalid");
+ document.body.append(script);
+ t.add_cleanup(() => script.remove());
+
+ await assertSubresourceCanBeFetched();
+ }, "An invalid value should send a credential to a same origin bundle");
+
+ promise_test(async (t) => {
+ const script = createScriptWebBundle("omit");
+ document.body.append(script);
+ t.add_cleanup(() => script.remove());
+
+ return promise_rejects_js(
+ t,
+ TypeError,
+ fetch(same_origin_bundle_subresource)
+ );
+ }, "'omit' should not send a credential to a same origin bundle");
+
+ promise_test(async (t) => {
+ const script = createScriptWebBundle("same-origin");
+ document.body.append(script);
+ t.add_cleanup(() => script.remove());
+
+ await assertSubresourceCanBeFetched();
+ }, "'same-origin' should send a credential to a same origin bundle");
+
+ promise_test(async (t) => {
+ const script = createScriptWebBundle("include");
+ document.body.append(script);
+ t.add_cleanup(() => script.remove());
+
+ await assertSubresourceCanBeFetched();
+ }, "'include' should send a credential to a same origin bundle");
+
+ promise_test(async (t) => {
+ await setCookiePromise;
+
+ const script = createScriptWebBundleCrossOrigin("omit");
+ document.body.append(script);
+ t.add_cleanup(() => script.remove());
+
+ return promise_rejects_js(
+ t,
+ TypeError,
+ fetch(cross_origin_bundle_subresource)
+ );
+ }, "'omit' should not send a credential to a cross origin bundle");
+
+ promise_test(async (t) => {
+ await setCookiePromise;
+
+ const script = createScriptWebBundleCrossOrigin("same-origin");
+ document.body.append(script);
+ t.add_cleanup(() => script.remove());
+
+ return promise_rejects_js(
+ t,
+ TypeError,
+ fetch(cross_origin_bundle_subresource)
+ );
+ }, "'same-origin' should not send a credential to a cross origin bundle");
+
+ promise_test(async (t) => {
+ await setCookiePromise;
+
+ const script = createScriptWebBundleCrossOrigin("include");
+ document.body.append(script);
+ t.add_cleanup(() => script.remove());
+
+ await assertCrossOriginSubresourceCanBeFetched();
+ }, "'include' should send a credential to a cross origin bundle");
+
+ promise_test(async (t) => {
+ const script = createScriptWebBundleCrossOrigin("invalid");
+ document.body.append(script);
+ t.add_cleanup(() => script.remove());
+
+ return promise_rejects_js(
+ t,
+ TypeError,
+ fetch(cross_origin_bundle_subresource)
+ );
+ }, "An invalid value should not send a credential to a cross origin bundle");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/csp-allowed.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/csp-allowed.https.tentative.html
new file mode 100644
index 0000000000..55498eaa4e
--- /dev/null
+++ b/testing/web-platform/tests/web-bundle/subresource-loading/csp-allowed.https.tentative.html
@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+<title>CSP for subresource WebBundle (allowed cases)</title>
+<link
+ rel="help"
+ href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md"
+/>
+<meta
+ http-equiv="Content-Security-Policy"
+ content="
+ script-src
+ https://web-platform.test:8444/web-bundle/resources/wbn/uuid-in-package.wbn
+ https://web-platform.test:8444/resources/testharness.js
+ https://web-platform.test:8444/resources/testharnessreport.js
+ 'unsafe-inline';
+ img-src
+ https://web-platform.test:8444/web-bundle/resources/wbn/pass.png;
+ frame-src
+ https://web-platform.test:8444/web-bundle/resources/wbn/uuid-in-package.wbn"
+/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+ <script type="webbundle">
+ {
+ "source": "../resources/wbn/subresource.wbn",
+ "resources": ["https://web-platform.test:8444/web-bundle/resources/wbn/pass.png"]
+ }
+ </script>
+ <script type="webbundle">
+ {
+ "source": "../resources/wbn/uuid-in-package.wbn",
+ "resources": ["uuid-in-package:020111b3-437a-4c5c-ae07-adb6bbffb720",
+ "uuid-in-package:429fcc4e-0696-4bad-b099-ee9175f023ae"
+ ]
+ }
+ </script>
+ <script>
+ promise_test(() => {
+ return new Promise((resolve, reject) => {
+ const img = document.createElement("img");
+ img.src =
+ "https://web-platform.test:8444/web-bundle/resources/wbn/pass.png";
+ img.onload = resolve;
+ img.onerror = reject;
+ document.body.appendChild(img);
+ });
+ }, "URL matching of CSP should be done based on the subresource URL " +
+ "when the subresource URL is HTTPS URL.");
+
+ promise_test(async () => {
+ const result = await new Promise((resolve) => {
+ // This function will be called from the script.
+ window.report_result = resolve;
+ const script = document.createElement("script");
+ script.src = "uuid-in-package:020111b3-437a-4c5c-ae07-adb6bbffb720";
+ document.body.appendChild(script);
+ });
+ assert_equals(result, "OK");
+ }, "URL matching of script-src CSP should be done based on the bundle URL " +
+ "when the subresource URL is uuid-in-package: URL.");
+
+ promise_test(async () => {
+ const frame_url = "uuid-in-package:429fcc4e-0696-4bad-b099-ee9175f023ae";
+ const iframe = document.createElement("iframe");
+ iframe.src = frame_url;
+ const load_promise = new Promise((resolve) => {
+ iframe.addEventListener("load", resolve);
+ });
+ document.body.appendChild(iframe);
+ await load_promise;
+ assert_equals(await evalInIframe(iframe, "location.href"), frame_url);
+ }, "URL matching of frame-src CSP should be done based on the bundle URL " +
+ "when the frame URL is uuid-in-package: URL.");
+
+ async function evalInIframe(iframe, code) {
+ const message_promise = new Promise((resolve) => {
+ window.addEventListener(
+ "message",
+ (e) => {
+ resolve(e.data);
+ },
+ { once: true }
+ );
+ });
+ iframe.contentWindow.postMessage(code, "*");
+ return message_promise;
+ }
+ </script>
+</body>
diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/csp-blocked.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/csp-blocked.https.tentative.html
new file mode 100644
index 0000000000..6700533b58
--- /dev/null
+++ b/testing/web-platform/tests/web-bundle/subresource-loading/csp-blocked.https.tentative.html
@@ -0,0 +1,162 @@
+<!DOCTYPE html>
+<title>CSP for subresource WebBundle (blocked cases)</title>
+<link
+ rel="help"
+ href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md"
+/>
+<meta
+ http-equiv="Content-Security-Policy"
+ content="
+ script-src
+ urn:
+ https://web-platform.test:8444/resources/testharness.js
+ https://web-platform.test:8444/resources/testharnessreport.js
+ 'unsafe-inline';
+ img-src
+ https://web-platform.test:8444/web-bundle/resources/wbn/subresource.wbn;
+ frame-src
+ urn:;
+ report-to
+ csp-group"
+/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+ <script type="webbundle">
+ {
+ "source": "../resources/wbn/subresource.wbn",
+ "resources": ["https://web-platform.test:8444/web-bundle/resources/wbn/fail.png"]
+ }
+ </script>
+ <script type="webbundle">
+ {
+ "source": "../resources/wbn/uuid-in-package.wbn",
+ "resources": ["uuid-in-package:020111b3-437a-4c5c-ae07-adb6bbffb720",
+ "uuid-in-package:429fcc4e-0696-4bad-b099-ee9175f023ae"]
+ }
+ </script>
+ <script>
+ const uuid_bundle_url =
+ "https://web-platform.test:8444/web-bundle/resources/wbn/uuid-in-package.wbn";
+
+ function expect_violation() {
+ return new Promise((resolve) => {
+ document.addEventListener(
+ "securitypolicyviolation",
+ (e) => {
+ e.stopPropagation();
+ resolve(e);
+ },
+ { once: true }
+ );
+ });
+ }
+
+ function getReportID() {
+ const cookies = document.cookie.split(";");
+ for (var i = 0; i < cookies.length; i++) {
+ const name_value = cookies[i].split("=");
+ const cookieName = name_value[0].trim();
+ if (cookieName === "csp-blocked-report-id") {
+ return name_value[1].trim();
+ }
+ }
+ }
+
+ function sortReportsByEffectiveDirective(reports) {
+ reports.sort(
+ (report1, report2) =>
+ report1.body.effectiveDirective.localeCompare(
+ report2.body.effectiveDirective
+ ) || report1.body.blockedURL.localeCompare(report2.body.blockedURL)
+ );
+ }
+
+ promise_test(async () => {
+ const p = expect_violation();
+ const img = document.createElement("img");
+ const error_promise = new Promise((resolve) => {
+ img.onerror = resolve;
+ });
+ img.src =
+ "https://web-platform.test:8444/web-bundle/resources/wbn/fail.png";
+ document.body.appendChild(img);
+ const e = await p;
+ assert_equals(e.blockedURI, img.src);
+ await error_promise;
+ }, "URL matching of CSP should be done based on the subresource URL, " +
+ "not on the bundle URL, when the subresource URL is HTTPS URL.");
+
+ const testCases = [
+ {
+ prefix: "uuid-in-package:",
+ bundle_url: uuid_bundle_url,
+ },
+ ];
+ for (const params of testCases) {
+ promise_test(async () => {
+ const urn_uuid = params.prefix + "020111b3-437a-4c5c-ae07-adb6bbffb720";
+ const p = expect_violation();
+ const script = document.createElement("script");
+ script.src = urn_uuid;
+ document.body.appendChild(script);
+ const e = await p;
+ // Currently Chromium is reporting the bundle URL.
+ // TODO(crbug.com/1208659): Consider deeper integration with CSP for
+ // providing the both URLs.
+ assert_equals(e.blockedURI, params.bundle_url);
+ assert_equals(e.violatedDirective, "script-src-elem");
+ }, "URL matching of script-src CSP should be done based on the bundle URL " +
+ `when the subresource URL is ${params.prefix} URL.`);
+
+ promise_test(async () => {
+ const urn_uuid = params.prefix + "429fcc4e-0696-4bad-b099-ee9175f023ae";
+ const p = expect_violation();
+ const iframe = document.createElement("iframe");
+ iframe.src = urn_uuid;
+ const load_promise = new Promise((resolve) => {
+ iframe.addEventListener("load", resolve);
+ });
+ document.body.appendChild(iframe);
+ const e = await p;
+ // Currently Chromium is reporting the bundle URL.
+ // TODO(crbug.com/1208659): Consider deeper integration with CSP for
+ // providing the both URLs.
+ assert_equals(e.blockedURI, params.bundle_url);
+ assert_equals(e.violatedDirective, "frame-src");
+
+ // Make sure that the blocked iframe load is finished.
+ await load_promise;
+
+ // The blocked iframe is cross-origin. So accessing
+ // iframe.contentWindow.location should throw a SecurityError.
+ assert_throws_dom("SecurityError", () => {
+ iframe.contentWindow.location.href;
+ });
+ }, "URL matching of frame-src CSP should be done based on the bundle URL " +
+ `when the frame URL is ${params.prefix} URL.`);
+ }
+
+ promise_test(async () => {
+ const retrieve_report_url =
+ "/reporting/resources/report.py?op=retrieve_report&timeout=3&reportID=" +
+ getReportID();
+ const reports = await (await fetch(retrieve_report_url)).json();
+ sortReportsByEffectiveDirective(reports);
+
+ assert_equals(reports.length, 3, "Report count.");
+
+ assert_equals(reports[0].body.blockedURL, uuid_bundle_url);
+ assert_equals(reports[0].body.effectiveDirective, "frame-src");
+
+ assert_equals(
+ reports[1].body.blockedURL,
+ "https://web-platform.test:8444/web-bundle/resources/wbn/fail.png"
+ );
+ assert_equals(reports[1].body.effectiveDirective, "img-src");
+
+ assert_equals(reports[2].body.blockedURL, uuid_bundle_url);
+ assert_equals(reports[2].body.effectiveDirective, "script-src-elem");
+ }, "Check the CSP violation reports.");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/csp-blocked.https.tentative.html.sub.headers b/testing/web-platform/tests/web-bundle/subresource-loading/csp-blocked.https.tentative.html.sub.headers
new file mode 100644
index 0000000000..ac826f8c48
--- /dev/null
+++ b/testing/web-platform/tests/web-bundle/subresource-loading/csp-blocked.https.tentative.html.sub.headers
@@ -0,0 +1,6 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Cache-Control: post-check=0, pre-check=0, false
+Pragma: no-cache
+Set-Cookie: csp-blocked-report-id={{$id:uuid()}}; Path=/web-bundle/subresource-loading/
+Report-To: { "group": "csp-group", "max_age": 10886400, "endpoints": [{ "url": "https://{{host}}:{{ports[https][0]}}/reporting/resources/report.py?op=put&reportID={{$id}}" }] }
diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/csp-blockes-bundle.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/csp-blockes-bundle.https.tentative.html
new file mode 100644
index 0000000000..06cef8c118
--- /dev/null
+++ b/testing/web-platform/tests/web-bundle/subresource-loading/csp-blockes-bundle.https.tentative.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<title>CSP blocks WebBundle</title>
+<link
+ rel="help"
+ href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md"
+/>
+<meta
+ http-equiv="Content-Security-Policy"
+ content="
+ default-src
+ https://web-platform.test:8444/web-bundle/resources/wbn/relative-url-file.js
+ https://web-platform.test:8444/resources/testharness.js
+ https://web-platform.test:8444/resources/testharnessreport.js
+ https://web-platform.test:8444/web-bundle/resources/test-helpers.js
+ 'unsafe-inline';
+ img-src
+ https://web-platform.test:8444/web-bundle/resources/wbn/pass.png;"
+/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/test-helpers.js"></script>
+<body>
+ <script>
+ // This bundle should be blocked because its URL is not listed in CSP directive.
+ const bundle_url =
+ "https://web-platform.test:8444/web-bundle/resources/wbn/relative-url.wbn";
+
+ const subresource_url =
+ "https://web-platform.test:8444/web-bundle/resources/wbn/relative-url-file.js";
+
+ promise_test(() => {
+ // if a WebBundle is blocked by CSP,
+ // - A request for the WebBundle should fail.
+ // - A subresource request associated with the bundle should fail.
+ // - A window.load should be fired. In other words, any request shouldn't remain
+ // pending forever.
+
+ const window_load = new Promise((resolve) => {
+ window.addEventListener("load", () => {
+ resolve();
+ });
+ });
+
+ const script_webbundle = createWebBundleElement(bundle_url, [
+ subresource_url,
+ ]);
+ const webbundle_error = new Promise((resolve) => {
+ script_webbundle.addEventListener("error", () => {
+ resolve();
+ });
+ });
+ document.body.appendChild(script_webbundle);
+
+ const script_js = document.createElement("script");
+ script_js.src = subresource_url;
+ const script_js_error = new Promise((resolve) => {
+ script_js.addEventListener("error", () => {
+ resolve();
+ });
+ });
+ document.body.appendChild(script_js);
+
+ return Promise.all([window_load, webbundle_error, script_js_error]);
+ }, "WebBundle and subresource loadings should fail when CSP blocks a WebBundle");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/element-removal.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/element-removal.https.tentative.html
new file mode 100644
index 0000000000..87ab8a9f15
--- /dev/null
+++ b/testing/web-platform/tests/web-bundle/subresource-loading/element-removal.https.tentative.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<title>
+ On-going subresource loading should fail immediately when the web bundle
+ element is removed
+</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"));
+ });
+
+ promise_test(async () => {
+ const element = createWebBundleElement(
+ "/xhr/resources/delay.py?ms=100000",
+ ["/xhr/resources/dummy"]
+ );
+ document.body.appendChild(element);
+ const waitUntilFail = new Promise((resolve) => {
+ fetch("/xhr/resources/dummy").then(() => {}, resolve);
+ });
+ document.body.removeChild(element);
+ await waitUntilFail;
+ }, "On-going subresource loading should fail immediately when the element is " + "removed.");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/invalid-json.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/invalid-json.https.tentative.html
new file mode 100644
index 0000000000..f4f9405e2d
--- /dev/null
+++ b/testing/web-platform/tests/web-bundle/subresource-loading/invalid-json.https.tentative.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<title>WebBundle subresource loading with script API and invalid JSON</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>
+<body>
+ <script>
+ setup(
+ () => {
+ assert_true(HTMLScriptElement.supports("webbundle"));
+ },
+ { allow_uncaught_exception: true }
+ );
+ promise_test((t) => {
+ const script = document.createElement("script");
+ script.type = "webbundle";
+ script.textContent = "invalid json";
+ return new Promise((resolve, reject) => {
+ script.onload = () => reject(script);
+ script.onerror = () => reject(script);
+ window.onerror = function (message, url, line, col, error) {
+ assert_equals(error.name, "SyntaxError");
+ resolve(script);
+ };
+ document.body.appendChild(script);
+ });
+ }, "Invalid JSON rule should throw a SyntaxError on the window.");
+ promise_test((t) => {
+ const script = document.createElement("script");
+ script.type = "webbundle";
+ const json_rule = { resources: [] };
+ script.textContent = JSON.stringify(json_rule);
+ return new Promise((resolve, reject) => {
+ script.onload = () => reject(script);
+ script.onerror = () => reject(script);
+ window.onerror = function (message, url, line, col, error) {
+ assert_equals(error.name, "TypeError");
+ resolve(script);
+ };
+ document.body.appendChild(script);
+ });
+ }, "JSON rule with a type error should throw a TypeError on the window.");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/nested-bundle.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/nested-bundle.https.tentative.html
new file mode 100644
index 0000000000..2e5b102e68
--- /dev/null
+++ b/testing/web-platform/tests/web-bundle/subresource-loading/nested-bundle.https.tentative.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<title>A nested bundle is not supported</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": "/web-bundle/resources/wbn/nested-main.wbn",
+ "resources": ["/web-bundle/resources/wbn/nested-sub.wbn"]
+ }
+ </script>
+ <script>
+ setup(() => {
+ assert_true(HTMLScriptElement.supports("webbundle"));
+ });
+
+ promise_test(async () => {
+ const response = await fetch("/web-bundle/resources/wbn/nested-sub.wbn");
+ assert_true(response.ok);
+ }, "A nested bundle can be fetched");
+
+ promise_test(async () => {
+ await addWebBundleElementAndWaitForError(
+ "/web-bundle/resources/wbn/nested-sub.wbn",
+ ["/web-bundle/resources/wbn/root.js"]
+ );
+ const response = await fetch("/web-bundle/resources/wbn/root.js");
+ assert_false(response.ok);
+ }, "Subresources in a nested bundle should not be loaded");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/network-error.https.tentative.sub.html b/testing/web-platform/tests/web-bundle/subresource-loading/network-error.https.tentative.sub.html
new file mode 100644
index 0000000000..3ebab3fa90
--- /dev/null
+++ b/testing/web-platform/tests/web-bundle/subresource-loading/network-error.https.tentative.sub.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<title>Web Bundle fetching failed due to a network error</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"));
+ });
+
+ // This test uses a non-existing WebBundle from a non-existent host, which makes
+ // Web Bundle fetching fail due to a network error. The intent of is to check if
+ // failing to fetch a WebBundle also makes subresource fetch requests fail.
+ promise_test(async () => {
+ const prefix = "https://{{hosts[][nonexistent]}}/";
+ const resources = [prefix + "resource.js"];
+ await addWebBundleElementAndWaitForError(
+ prefix + "non-existing.wbn",
+ resources
+ );
+
+ // Can not fetch a subresource because Web Bundle fetch failed.
+ await fetchAndWaitForReject(prefix + "resource.js");
+ }, "Subresource fetch requests for non-existing Web Bundle should fail.");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/not-found.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/not-found.https.tentative.html
new file mode 100644
index 0000000000..efcb6bc9fb
--- /dev/null
+++ b/testing/web-platform/tests/web-bundle/subresource-loading/not-found.https.tentative.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<title>Web Bundle fetching failed due to not found error</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"));
+ });
+
+ // This test uses a non-existing WebBundle,
+ // /web-bundle/resources/wbn/cors/non-existing.wbn.
+ // The intent of this test is to check if failing to fetch a WebBundle due to
+ // not found error also makes subresource fetch requests fail.
+ promise_test(async () => {
+ const prefix = "/web-bundle/resources/wbn/";
+ const resources = [prefix + "resource.js"];
+ const element = await createWebBundleElement(
+ prefix + "non-existing.wbn",
+ resources
+ );
+ document.body.appendChild(element);
+
+ // Can not fetch a subresource because Web Bundle fetch failed.
+ await fetchAndWaitForReject(prefix + "resource.js");
+ }, "Subresource fetch requests for non-existing Web Bundle should fail.");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/path-restriction.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/path-restriction.https.tentative.html
new file mode 100644
index 0000000000..1d7b6f204b
--- /dev/null
+++ b/testing/web-platform/tests/web-bundle/subresource-loading/path-restriction.https.tentative.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<title>Path restriction on subresource loading with WebBundles</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>
+<body>
+ <script type="webbundle">
+ {
+ "source": "../resources/wbn/path-restriction.wbn",
+ "resources": [
+ "/web-bundle/resources/wbn/resource.js",
+ "/web-bundle/resources/wbn/sub/resource.js",
+ "/web-bundle/resources/wbn-resource.js",
+ "/web-bundle/resources/wbn1/resource.js",
+ "/web-bundle/resources/other/resource.js",
+ "/web-bundle/resources/resource.js"
+ ]
+ }
+ </script>
+ <script>
+ setup(() => {
+ assert_true(HTMLScriptElement.supports("webbundle"));
+ });
+
+ promise_test(async () => {
+ const resources = [
+ "/web-bundle/resources/wbn/resource.js",
+ "/web-bundle/resources/wbn/sub/resource.js",
+ ];
+ for (const resource of resources) {
+ const response = await fetch(resource);
+ assert_true(response.ok, resource + " should be loaded");
+ }
+ }, "Subresources should be loaded.");
+
+ promise_test(async () => {
+ const resources = [
+ "/web-bundle/resources/wbn-resource.js",
+ "/web-bundle/resources/wbn1/resource.js",
+ "/web-bundle/resources/other/resource.js",
+ "/web-bundle/resources/resource.js",
+ ];
+ for (const resource of resources) {
+ const response = await fetch(resource);
+ assert_false(response.ok, resource + " should not be loaded");
+ }
+ }, "Subresources should not be loaded due to path restriction.");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-in-web-bundle-cors.https.tentative.sub.html b/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-in-web-bundle-cors.https.tentative.sub.html
new file mode 100644
index 0000000000..32f333b67e
--- /dev/null
+++ b/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-in-web-bundle-cors.https.tentative.sub.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<title>Relative Url in cross origin web bundle</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 loaded_scripts = [];
+ function scriptLoaded(file) {
+ loaded_scripts.push(file);
+ }
+ const failed_scripts = [];
+ function scriptFailed(file) {
+ failed_scripts.push(file);
+ }
+ </script>
+ <script type="webbundle">
+ {
+ "source": "//{{domains[www1]}}:{{ports[https][0]}}/web-bundle/resources/wbn/relative-url.wbn",
+ "resources": [
+ "//{{domains[www1]}}:{{ports[https][0]}}/web-bundle/resources/wbn/relative-url-file.js",
+ "//{{domains[www1]}}:{{ports[https][0]}}/web-bundle/resources/wbn/relative-url/start-with-double-slash-cors.js",
+ "//{{domains[www1]}}:{{ports[https][0]}}/web-bundle/resources/wbn/relative-url/start-with-slash.js",
+ "//{{domains[www1]}}:{{ports[https][0]}}/web-bundle/resources/wbn/relative-url/subdirectory-path.js",
+ "//{{domains[www1]}}:{{ports[https][0]}}/web-bundle/resources/wbn/starts-with-two-dots.js",
+ "//{{domains[www1]}}:{{ports[https][0]}}/web-bundle/resources/starts-with-two-dots-out-of-scope.js"
+ ]
+ }
+ </script>
+ <script src="//{{domains[www1]}}:{{ports[https][0]}}/web-bundle/resources/wbn/relative-url-file.js"></script>
+ <script src="//{{domains[www1]}}:{{ports[https][0]}}/web-bundle/resources/wbn/relative-url/start-with-double-slash-cors.js"></script>
+ <script src="//{{domains[www1]}}:{{ports[https][0]}}/web-bundle/resources/wbn/relative-url/start-with-slash.js"></script>
+ <script src="//{{domains[www1]}}:{{ports[https][0]}}/web-bundle/resources/wbn/relative-url/subdirectory-path.js"></script>
+ <script src="//{{domains[www1]}}:{{ports[https][0]}}/web-bundle/resources/wbn/starts-with-two-dots.js"></script>
+ <script
+ src="//{{domains[www1]}}:{{ports[https][0]}}/web-bundle/resources/starts-with-two-dots-out-of-scope.js"
+ onerror="scriptFailed('starts-with-two-dots-out-of-scope.js')"
+ ></script>
+ <script>
+ promise_test(async (t) => {
+ assert_array_equals(loaded_scripts, [
+ "relative-url-file.js",
+ "start-with-double-slash-cors.js",
+ "start-with-slash.js",
+ "subdirectory-path.js",
+ "starts-with-two-dots.js",
+ ]);
+ assert_array_equals(failed_scripts, [
+ "starts-with-two-dots-out-of-scope.js",
+ ]);
+ }, "Relative Url in web bundle.");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-in-web-bundle.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-in-web-bundle.https.tentative.html
new file mode 100644
index 0000000000..0b7f63c21c
--- /dev/null
+++ b/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-in-web-bundle.https.tentative.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<title>Relative Url in web bundle</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 loaded_scripts = [];
+ function scriptLoaded(file) {
+ loaded_scripts.push(file);
+ }
+ const failed_scripts = [];
+ function scriptFailed(file) {
+ failed_scripts.push(file);
+ }
+ </script>
+ <script type="webbundle">
+ {
+ "source": "../resources/wbn/relative-url.wbn",
+ "resources": [
+ "relative-url-file.js",
+ "relative-url/start-with-double-slash.js",
+ "relative-url/start-with-slash.js",
+ "relative-url/subdirectory-path.js",
+ "starts-with-two-dots.js",
+ "starts-with-two-dots-out-of-scope.js"
+ ]
+ }
+ </script>
+ <script src="../resources/wbn/relative-url-file.js"></script>
+ <script src="../resources/wbn/relative-url/start-with-double-slash.js"></script>
+ <script src="../resources/wbn/relative-url/start-with-slash.js"></script>
+ <script src="../resources/wbn/relative-url/subdirectory-path.js"></script>
+ <script src="../resources/wbn/starts-with-two-dots.js"></script>
+ <script
+ src="../resources/starts-with-two-dots-out-of-scope.js"
+ onerror="scriptFailed('starts-with-two-dots-out-of-scope.js')"
+ ></script>
+ <script>
+ promise_test(async (t) => {
+ assert_array_equals(loaded_scripts, [
+ "relative-url-file.js",
+ "start-with-double-slash.js",
+ "start-with-slash.js",
+ "subdirectory-path.js",
+ "starts-with-two-dots.js",
+ ]);
+ assert_array_equals(failed_scripts, [
+ "starts-with-two-dots-out-of-scope.js",
+ ]);
+ }, "Relative Url in web bundle.");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-resources.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-resources.https.tentative.html
new file mode 100644
index 0000000000..1b79b157c4
--- /dev/null
+++ b/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-resources.https.tentative.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<title>Subresource loading using relative URLs in the '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>
+
+<script>
+ setup(() => {
+ assert_true(HTMLScriptElement.supports("webbundle"));
+ });
+</script>
+<body>
+ <script>
+ let script;
+
+ function cleanUp() {
+ if (script) {
+ script.remove();
+ }
+ }
+
+ function createScriptWebBundle(resource) {
+ return createWebBundleElement(
+ "../resources/wbn/subresource.wbn",
+ /*resources=*/ [resource]
+ );
+ }
+
+ async function assertResourceCanBeFetched() {
+ const response = await fetch(`../resources/wbn/root.js`);
+ const text = await response.text();
+ assert_equals(text, "export * from './submodule.js';\n");
+ }
+
+ async function assertResourceNotFound() {
+ const response = await fetch(`../resources/wbn/root.js`);
+ const status = await response.status;
+ assert_equals(status, 404);
+ }
+
+ promise_test(async (t) => {
+ t.add_cleanup(cleanUp);
+ script = createScriptWebBundle("root.js");
+ document.body.append(script);
+ await assertResourceCanBeFetched();
+ }, "A relative URL, 'root.js', should be resolved on the bundle's URL");
+
+ promise_test(async (t) => {
+ t.add_cleanup(cleanUp);
+ script = createScriptWebBundle("./root.js");
+ document.body.append(script);
+ await assertResourceCanBeFetched();
+ }, "Use './root.js', starting with dot");
+
+ promise_test(async (t) => {
+ t.add_cleanup(cleanUp);
+ script = createScriptWebBundle("../wbn/root.js");
+ document.body.append(script);
+ await assertResourceCanBeFetched();
+ }, "Use '../wbn/root.js', starting with two dots");
+
+ promise_test(async (t) => {
+ t.add_cleanup(cleanUp);
+ script = createScriptWebBundle("/web-bundle/resources/wbn/root.js");
+ document.body.append(script);
+ await assertResourceCanBeFetched();
+ }, "Use '/web-bundle/resources/wbn/root.js', starting with slash");
+
+ promise_test(async (t) => {
+ t.add_cleanup(cleanUp);
+ script = createScriptWebBundle("unrelated-relative-url.js");
+ document.body.append(script);
+ await assertResourceNotFound();
+ }, "A resource should not be found");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-scopes.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-scopes.https.tentative.html
new file mode 100644
index 0000000000..40a49d55d3
--- /dev/null
+++ b/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-scopes.https.tentative.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<title>Subresource loading using relative URLs in the 'scopes'</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>
+
+<script>
+ setup(() => {
+ assert_true(HTMLScriptElement.supports("webbundle"));
+ });
+</script>
+<body>
+ <script>
+ let script;
+
+ function cleanUp() {
+ if (script) {
+ script.remove();
+ }
+ }
+
+ function createScriptWebBundle(scope) {
+ return createWebBundleElement(
+ "../resources/wbn/relative-url.wbn",
+ /*resources=*/ [],
+ { scopes: [scope] }
+ );
+ }
+
+ async function assertResourceCanBeFetched() {
+ const response = await fetch(
+ "../resources/wbn/relative-url/subdirectory-path.js"
+ );
+ const text = await response.text();
+ assert_equals(text, "scriptLoaded('subdirectory-path.js');");
+ }
+
+ async function assertResourceNotFound() {
+ const response = await fetch(
+ "../resources/wbn/relative-url/subdirectory-path.js"
+ );
+ const status = await response.status;
+ assert_equals(status, 404);
+ }
+
+ promise_test(async (t) => {
+ t.add_cleanup(cleanUp);
+ script = createScriptWebBundle("relative-url");
+ document.body.append(script);
+ await assertResourceCanBeFetched();
+ }, "A relative URL, 'relative-url', should be resolved on the bundle's URL");
+
+ promise_test(async (t) => {
+ t.add_cleanup(cleanUp);
+ script = createScriptWebBundle("./relative-url");
+ document.body.append(script);
+ await assertResourceCanBeFetched();
+ }, "Use './relative-url', starting with dot");
+
+ promise_test(async (t) => {
+ t.add_cleanup(cleanUp);
+ script = createScriptWebBundle("../wbn/relative-url");
+ document.body.append(script);
+ await assertResourceCanBeFetched();
+ }, "Use '../wbn/relative-url', starting with two dots");
+
+ promise_test(async (t) => {
+ t.add_cleanup(cleanUp);
+ script = createScriptWebBundle("/web-bundle/resources/wbn/relative-url");
+ document.body.append(script);
+ await assertResourceCanBeFetched();
+ }, "Use '/web-bundle/resources/wbn/relative-url', starting with slash");
+
+ promise_test(async (t) => {
+ t.add_cleanup(cleanUp);
+ script = createScriptWebBundle("unrelated-scope");
+ document.body.append(script);
+ await assertResourceNotFound();
+ }, "A resource should not be found");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-static-element.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-static-element.https.tentative.html
new file mode 100644
index 0000000000..55030e234b
--- /dev/null
+++ b/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-static-element.https.tentative.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>WebBundle subresource loading with relative URLs for static elements</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>
+<body>
+ <script type="webbundle">
+ {
+ "source": "../resources/wbn/static-element.wbn",
+ "resources": [
+ "static-element/resources/script.js"
+ ],
+ "scopes": [
+ "static-element/scopes"
+ ]
+ }
+ </script>
+ <script src="../resources/wbn/static-element/resources/script.js"></script>
+ <script src="../resources/wbn/static-element/scopes/script.js"></script>
+ <script src="../resources/wbn/static-element/out-of-scope/script.js"></script>
+ <script>
+ setup(() => {
+ assert_true(HTMLScriptElement.supports('webbundle'));
+ });
+ promise_test(async () => {
+ assert_equals(resources_script_result, 'loaded from webbundle');
+ assert_equals(scopes_script_result, 'loaded from webbundle');
+ assert_equals(out_of_scope_script_result, 'loaded from network');
+ }, 'Subresources from static elements should be loaded from web bundle.');
+ </script>
+</body>
diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-with-base.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-with-base.https.tentative.html
new file mode 100644
index 0000000000..3c7e1a380c
--- /dev/null
+++ b/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-with-base.https.tentative.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<title>
+ Subresource loading using relative URLs in the 'resources' attribute with a
+ base element
+</title>
+<base href="../resources/wbn/" />
+<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>
+
+<body>
+ <script type="webbundle">
+ {
+ "source": "static-element.wbn",
+ "resources": ["static-element/resources/script.js"]
+ }
+ </script>
+ <script id="script" src="static-element/resources/script.js"></script>
+
+ <script type="webbundle">
+ {
+ "source": "dynamic1.wbn",
+ "scopes": ["dynamic/resource"]
+ }
+ </script>
+
+ <script>
+ setup(() => {
+ assert_true(HTMLScriptElement.supports("webbundle"));
+ });
+
+ test(() => {
+ assert_equals(resources_script_result, "loaded from webbundle");
+ }, "A subresource script.js should be loaded from WebBundle using the relative " + "URL and a base element.");
+
+ promise_test(async () => {
+ const module = await import(
+ "/web-bundle/resources/wbn/dynamic/resource1.js"
+ );
+ assert_equals(module.result, "resource1 from dynamic1.wbn");
+ const module2 = await import(
+ "/web-bundle/resources/wbn/dynamic/resource2.js"
+ );
+ assert_equals(module2.result, "resource2 from dynamic1.wbn");
+ const module3 = await import(
+ "/web-bundle/resources/wbn/dynamic/resource3.js"
+ );
+ assert_equals(module3.result, "resource3 from dynamic1.wbn");
+ const module4 = await import(
+ "/web-bundle/resources/wbn/dynamic/resource4.js"
+ );
+ assert_equals(module4.result, "resource4 from dynamic1.wbn");
+ 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 = "/web-bundle/resources/wbn/dynamic/classic_script.js";
+ document.body.appendChild(script);
+ assert_equals(await result_promise, "classic script from network");
+ }, "Subresources that start with 'resource' should be loaded from dynamic1.wbn while others from network.");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/request-destination.https.tentative.sub.html b/testing/web-platform/tests/web-bundle/subresource-loading/request-destination.https.tentative.sub.html
new file mode 100644
index 0000000000..da12f3d042
--- /dev/null
+++ b/testing/web-platform/tests/web-bundle/subresource-loading/request-destination.https.tentative.sub.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<title>
+ Request's destination must be "webbundle" with the script-based API
+</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"));
+ });
+
+ // check-sec-fetch-dest-header-and-return-bundle.py returns a valid format
+ // bundle only if a 'Sec-Fetch-Dest: webbundle' header is present in a request.
+ // Otherwise, returns an empty body with 400 status code.
+ //
+ // In this wpt, we assume that a <script> element fires a load event correctly if
+ // a valid format webbundle is returned.
+
+ const same_origin_bundle =
+ "../resources/check-sec-fetch-dest-header-and-return-bundle.py";
+ const cross_origin_bundle =
+ "https://{{domains[www1]}}:{{ports[https][0]}}/web-bundle/resources/check-sec-fetch-dest-header-and-return-bundle.py";
+
+ promise_test(async () => {
+ for (const bundle of [same_origin_bundle, cross_origin_bundle]) {
+ const element = createWebBundleElement(bundle, /*resources=*/ []);
+ await addElementAndWaitForLoad(element);
+ element.remove();
+ }
+ }, '"Sec-Fetch-Dest: webbundle" header must be present in a request for a bundle'
+ + " with <script type=webbundle>.");
+
+ promise_test(async () => {
+ const res = await fetch(same_origin_bundle);
+ assert_false(res.ok);
+ }, '"Sec-Fetch-Dest: webbundle" header must not be present in a fetch request' + " for a same-origin resource.");
+
+ promise_test(async () => {
+ const res = await fetch(cross_origin_bundle);
+ assert_false(res.ok);
+ }, '"Sec-Fetch-Dest: webbundle" header must not be present in a fetch request' + " for a cross-origin resource.");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/resource-timing-attributes-consistent.https.tentative.sub.html b/testing/web-platform/tests/web-bundle/subresource-loading/resource-timing-attributes-consistent.https.tentative.sub.html
new file mode 100644
index 0000000000..3231d69ae9
--- /dev/null
+++ b/testing/web-platform/tests/web-bundle/subresource-loading/resource-timing-attributes-consistent.https.tentative.sub.html
@@ -0,0 +1,88 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>
+ Resource timing attributes are consistent for the same-origin subresources.
+</title>
+<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"));
+ });
+
+ promise_test(async (t) => {
+ const bundle_url =
+ "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/dynamic1.wbn?pipe=trickle(d0.5)";
+ const script_url =
+ "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/dynamic/resource1.js";
+ const element = createWebBundleElement(
+ "../resources/wbn/dynamic1.wbn?pipe=trickle(d0.5)",
+ /*resources=*/ [script_url]
+ );
+ document.body.appendChild(element);
+ var script_entries = 0;
+ var web_bundle_entries = 0;
+ var web_bundle_entry, script_entry;
+ const promise = new Promise((resolve) => {
+ new PerformanceObserver(
+ t.step_func((entryList) => {
+ var entries = entryList.getEntriesByType("resource");
+ for (var i = 0; i < entries.length; ++i) {
+ if (entries[i].name === script_url) {
+ script_entry = entries[i];
+ script_entries++;
+ }
+
+ if (entries[i].name === bundle_url) {
+ web_bundle_entry = entries[i];
+ web_bundle_entries++;
+ }
+ }
+
+ if (web_bundle_entries > 0 && script_entries > 0) {
+ // Check timestamps.
+ assert_greater_than_equal(
+ script_entry.responseStart,
+ script_entry.requestStart + 500
+ );
+ assert_greater_than_equal(
+ script_entry.responseStart,
+ web_bundle_entry.responseStart
+ );
+ assert_greater_than_equal(
+ script_entry.responseEnd,
+ script_entry.responseStart
+ );
+ assert_greater_than_equal(
+ script_entry.requestStart,
+ script_entry.connectEnd
+ );
+ assert_greater_than_equal(
+ script_entry.responseEnd,
+ script_entry.responseStart
+ );
+ // Check sizes.
+ assert_greater_than(script_entry.encodedBodySize, 0);
+ assert_equals(
+ script_entry.transferSize,
+ script_entry.encodedBodySize + 300
+ );
+ assert_equals(
+ script_entry.encodedBodySize,
+ script_entry.decodedBodySize
+ );
+ resolve();
+ }
+ })
+ ).observe({ entryTypes: ["resource"] });
+ });
+ const script = document.createElement("script");
+ script.type = "module";
+ script.src = script_url;
+ document.body.appendChild(script);
+ return promise;
+ }, "Timestamp attributes filled in resource timing entries should be consistent.");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/resource-timing.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/resource-timing.https.tentative.html
new file mode 100644
index 0000000000..a2fe38de0f
--- /dev/null
+++ b/testing/web-platform/tests/web-bundle/subresource-loading/resource-timing.https.tentative.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Resource timing entries present for uuid-in-package resources</title>
+<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"));
+ });
+
+ promise_test(async (t) => {
+ const frame_id = "uuid-in-package:429fcc4e-0696-4bad-b099-ee9175f023ae";
+ const script_id = "uuid-in-package:020111b3-437a-4c5c-ae07-adb6bbffb720";
+ const element = createWebBundleElement(
+ "../resources/wbn/uuid-in-package.wbn",
+ /*resources=*/ [frame_id, script_id]
+ );
+ document.body.appendChild(element);
+ let iframe_entries = 0;
+ let script_entries = 0;
+ // Declare the report_result function as outputting into stderr
+ // because it is used in the WebBundle script to report the script load.
+ window.report_result = console.error;
+ const promise = new Promise((resolve) => {
+ new PerformanceObserver(
+ t.step_func((entryList) => {
+ let entries = entryList.getEntriesByType("resource");
+ for (let i = 0; i < entries.length; ++i) {
+ // Ignore any entries for the test harness files if present.
+ if (/testharness(report)?\.js/.test(entries[i].name)) {
+ continue;
+ }
+
+ if (entries[i].name === frame_id) ++iframe_entries;
+ if (entries[i].name === script_id) ++script_entries;
+ }
+ if (iframe_entries == 1 && script_entries == 1) {
+ resolve();
+ }
+ })
+ ).observe({ entryTypes: ["resource"] });
+ });
+ // Add iframe and the script so we get the ResourceTiming
+ const iframe = document.createElement("iframe");
+ iframe.src = frame_id;
+ document.body.appendChild(iframe);
+ const script = document.createElement("script");
+ script.src = script_id;
+ document.body.appendChild(script);
+ return promise;
+ }, "Each uuid-in-package resource should have exactly 1 ResourceTiming entry.");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/resources/check-cookie-and-return-bundle.py b/testing/web-platform/tests/web-bundle/subresource-loading/resources/check-cookie-and-return-bundle.py
new file mode 100644
index 0000000000..0d4f14ecb7
--- /dev/null
+++ b/testing/web-platform/tests/web-bundle/subresource-loading/resources/check-cookie-and-return-bundle.py
@@ -0,0 +1,25 @@
+import os
+
+
+def main(request, response):
+ origin = request.headers.get(b"origin")
+
+ if origin is not None:
+ response.headers.set(b"Access-Control-Allow-Origin", origin)
+ response.headers.set(b"Access-Control-Allow-Methods", b"GET")
+ response.headers.set(b"Access-Control-Allow-Credentials", b"true")
+
+ headers = [
+ (b"Content-Type", b"application/webbundle"),
+ (b"X-Content-Type-Options", b"nosniff"),
+ ]
+
+ cookie = request.cookies.first(b"milk", None)
+ if (cookie is not None) and cookie.value == b"1":
+ with open(
+ os.path.join(os.path.dirname(__file__), "../../resources/wbn/subresource.wbn"),
+ "rb",
+ ) as f:
+ return (200, headers, f.read())
+ else:
+ return (400, [], "")
diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/resources/service-worker-controlled-iframe.html b/testing/web-platform/tests/web-bundle/subresource-loading/resources/service-worker-controlled-iframe.html
new file mode 100644
index 0000000000..c8b7661f42
--- /dev/null
+++ b/testing/web-platform/tests/web-bundle/subresource-loading/resources/service-worker-controlled-iframe.html
@@ -0,0 +1 @@
+<body></body>
diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/resources/service-worker-for-request-monitor.js b/testing/web-platform/tests/web-bundle/subresource-loading/resources/service-worker-for-request-monitor.js
new file mode 100644
index 0000000000..f67ac70686
--- /dev/null
+++ b/testing/web-platform/tests/web-bundle/subresource-loading/resources/service-worker-for-request-monitor.js
@@ -0,0 +1,12 @@
+
+let request_urls = [];
+
+self.addEventListener('fetch', e => {
+ request_urls.push(e.request.url);
+ e.respondWith(fetch(e.request));
+});
+
+self.addEventListener('message', e => {
+ e.source.postMessage(request_urls);
+ request_urls = [];
+});
diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/reuse-web-bundle-resource.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/reuse-web-bundle-resource.https.tentative.html
new file mode 100644
index 0000000000..81d87bcbf0
--- /dev/null
+++ b/testing/web-platform/tests/web-bundle/subresource-loading/reuse-web-bundle-resource.https.tentative.html
@@ -0,0 +1,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>
diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/service-worker-controlled.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/service-worker-controlled.https.tentative.html
new file mode 100644
index 0000000000..d5c2a06837
--- /dev/null
+++ b/testing/web-platform/tests/web-bundle/subresource-loading/service-worker-controlled.https.tentative.html
@@ -0,0 +1,138 @@
+<!DOCTYPE html>
+<title>
+ Web Bundle fetching and the inner resouirce fetching should skip service
+ worker
+</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"));
+ });
+
+ async function registerServiceWorkerAndReturnActiveWorker(
+ t,
+ script,
+ scope
+ ) {
+ const reg = await navigator.serviceWorker.register(script, {
+ scope: scope,
+ });
+ t.add_cleanup(() => reg.unregister());
+ if (reg.active) return reg.active;
+ const worker = reg.installing || reg.waiting;
+ await new Promise((resolve) => {
+ worker.addEventListener("statechange", (event) => {
+ if (event.target.state == "activated") resolve();
+ });
+ });
+ return worker;
+ }
+
+ async function getRequestedUrls(worker) {
+ return new Promise((resolve) => {
+ navigator.serviceWorker.addEventListener(
+ "message",
+ (e) => {
+ resolve(e.data);
+ },
+ { once: true }
+ );
+ worker.postMessage(null);
+ });
+ }
+
+ promise_test(async (t) => {
+ const iframe_path = "./resources/service-worker-controlled-iframe.html";
+ const iframe_url = new URL(iframe_path, location).href;
+
+ // Register a service worker.
+ const worker = await registerServiceWorkerAndReturnActiveWorker(
+ t,
+ "./resources/service-worker-for-request-monitor.js",
+ iframe_path
+ );
+
+ // Load an iframe which is controlled by the service worker.
+ const iframe = await new Promise((resolve) => {
+ const frame = document.createElement("iframe");
+ t.add_cleanup(() => frame.remove());
+ frame.src = iframe_url;
+ frame.onload = () => {
+ resolve(frame);
+ };
+ document.body.appendChild(frame);
+ });
+ // The iframe request should be intercepted by the service worker.
+ assert_array_equals(await getRequestedUrls(worker), [iframe_url]);
+
+ // Add a web bundle element in the service worker controlled iframe.
+ const frame_id = "uuid-in-package:429fcc4e-0696-4bad-b099-ee9175f023ae";
+ const script_id = "uuid-in-package:020111b3-437a-4c5c-ae07-adb6bbffb720";
+
+ const element = createWebBundleElement(
+ "../../resources/wbn/uuid-in-package.wbn",
+ /*resources=*/ [frame_id, script_id]
+ );
+
+ const element_load_promise = new Promise((resolve) => {
+ element.addEventListener("load", () => {
+ resolve();
+ });
+ });
+ iframe.contentDocument.body.appendChild(element);
+ await element_load_promise;
+ // The web bundle request should not be intercepted by the service worker.
+ assert_array_equals(await getRequestedUrls(worker), []);
+
+ // Add a uuid-in-package URL script element in the service worker
+ // controlled iframe.
+ const result_promise = new Promise((resolve) => {
+ // window.report_result() method will be called by the injected script.
+ iframe.contentWindow.report_result = resolve;
+ });
+ const script = iframe.contentDocument.createElement("script");
+ script.src = script_id;
+ iframe.contentDocument.body.appendChild(script);
+ assert_equals(await result_promise, "OK");
+ // The urn uuld URL script request should not be intercepted by the
+ // service worker.
+ assert_array_equals(await getRequestedUrls(worker), []);
+
+ // Add a uuid-in-package URL iframe element in the service worker controlled
+ // iframe.
+ const inner_iframe = iframe.contentDocument.createElement("iframe");
+ inner_iframe.src = frame_id;
+ const load_promise = new Promise((resolve) => {
+ inner_iframe.addEventListener("load", () => {
+ resolve();
+ });
+ });
+ iframe.contentDocument.body.appendChild(inner_iframe);
+ await load_promise;
+ // The urn uuld URL iframe request should not intercepted by the service
+ // worker.
+ assert_array_equals(await getRequestedUrls(worker), []);
+
+ // Check if the uuid-in-package URL iframe element is loaded correctly.
+ const message_promise = new Promise((resolve) => {
+ window.addEventListener(
+ "message",
+ (e) => {
+ resolve(e.data);
+ },
+ { once: true }
+ );
+ });
+ // location.href is evaluated in the uuid-in-package URL iframe element.
+ inner_iframe.contentWindow.postMessage("location.href", "*");
+ assert_equals(await message_promise, frame_id);
+ }, "Both Web Bundle request and Subresource fetch requests inside the Web " + "Bundle should skip the service worker.");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/static-element-with-base.https.tentative.sub.html b/testing/web-platform/tests/web-bundle/subresource-loading/static-element-with-base.https.tentative.sub.html
new file mode 100644
index 0000000000..886d9cb783
--- /dev/null
+++ b/testing/web-platform/tests/web-bundle/subresource-loading/static-element-with-base.https.tentative.sub.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<title>
+ WebBundle subresource loading for static elements with a base element
+</title>
+<link
+ rel="help"
+ href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md"
+/>
+<base href="../resources/wbn/static-element/" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+ <script type="webbundle">
+ {
+ "source": "../static-element.wbn",
+ "resources": [
+ "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/static-element/resources/script.js",
+ "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/static-element/resources/style.css",
+ "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/static-element/resources/style-imported-from-file.css",
+ "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/static-element/resources/style-imported-from-tag.css"
+ ],
+ "scopes": [
+ "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/static-element/scopes/"
+ ]
+ }
+ </script>
+ <style type="text/css">
+ @import "resources/style-imported-from-tag.css";
+ @import "scopes/style-imported-from-tag.css";
+ </style>
+ <link href="resources/style.css" rel="stylesheet" />
+ <link href="scopes/style.css" rel="stylesheet" />
+ <script src="resources/script.js"></script>
+ <script src="scopes/script.js"></script>
+ <script src="out-of-scope/script.js"></script>
+
+ <script>
+ setup(() => {
+ assert_true(HTMLScriptElement.supports("webbundle"));
+ });
+
+ promise_test(async () => {
+ assert_equals(resources_script_result, "loaded from webbundle");
+ assert_equals(scopes_script_result, "loaded from webbundle");
+ assert_equals(out_of_scope_script_result, "loaded from network");
+
+ ["resources_", "scopes_"].forEach((type) => {
+ [
+ "style_target",
+ "style_imported_from_file_target",
+ "style_imported_from_tag_target",
+ ].forEach((target) => {
+ const element = document.createElement("div");
+ element.id = type + target;
+ document.body.appendChild(element);
+ assert_equals(
+ window.getComputedStyle(element).color,
+ "rgb(0, 0, 255)",
+ element.id + " color must be blue"
+ );
+ });
+ });
+ }, "Subresources from static elements should be loaded from web bundle.");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/static-element.https.tentative.sub.html b/testing/web-platform/tests/web-bundle/subresource-loading/static-element.https.tentative.sub.html
new file mode 100644
index 0000000000..198c8f8e5c
--- /dev/null
+++ b/testing/web-platform/tests/web-bundle/subresource-loading/static-element.https.tentative.sub.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<title>WebBundle subresource loading for static elements</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>
+<body>
+ <script type="webbundle">
+ {
+ "source": "../resources/wbn/static-element.wbn",
+ "resources": [
+ "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/static-element/resources/script.js",
+ "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/static-element/resources/style.css",
+ "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/static-element/resources/style-imported-from-file.css",
+ "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/static-element/resources/style-imported-from-tag.css"
+ ],
+ "scopes": [
+ "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/static-element/scopes/"
+ ]
+ }
+ </script>
+ <style type="text/css">
+ @import "../resources/wbn/static-element/resources/style-imported-from-tag.css";
+ @import "../resources/wbn/static-element/scopes/style-imported-from-tag.css";
+ </style>
+ <link
+ href="../resources/wbn/static-element/resources/style.css"
+ rel="stylesheet"
+ />
+ <link
+ href="../resources/wbn/static-element/scopes/style.css"
+ rel="stylesheet"
+ />
+ <script src="../resources/wbn/static-element/resources/script.js"></script>
+ <script src="../resources/wbn/static-element/scopes/script.js"></script>
+ <script src="../resources/wbn/static-element/out-of-scope/script.js"></script>
+
+ <script>
+ setup(() => {
+ assert_true(HTMLScriptElement.supports("webbundle"));
+ });
+
+ promise_test(async () => {
+ assert_equals(resources_script_result, "loaded from webbundle");
+ assert_equals(scopes_script_result, "loaded from webbundle");
+ assert_equals(out_of_scope_script_result, "loaded from network");
+
+ ["resources_", "scopes_"].forEach((type) => {
+ [
+ "style_target",
+ "style_imported_from_file_target",
+ "style_imported_from_tag_target",
+ ].forEach((target) => {
+ const element = document.createElement("div");
+ element.id = type + target;
+ document.body.appendChild(element);
+ assert_equals(
+ window.getComputedStyle(element).color,
+ "rgb(0, 0, 255)",
+ element.id + " color must be blue"
+ );
+ });
+ });
+ }, "Subresources from static elements should be loaded from web bundle.");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/subframe-from-web-bundle.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/subframe-from-web-bundle.https.tentative.html
new file mode 100644
index 0000000000..9e08ccdd29
--- /dev/null
+++ b/testing/web-platform/tests/web-bundle/subresource-loading/subframe-from-web-bundle.https.tentative.html
@@ -0,0 +1,134 @@
+<!DOCTYPE html>
+<title>Subframe loading from Web Bundles</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"));
+ });
+
+ promise_test(async (t) => {
+ const bundle_url = "../resources/wbn/uuid-in-package.wbn";
+ const frame_url = "uuid-in-package:429fcc4e-0696-4bad-b099-ee9175f023ae";
+ const iframe = await createWebBundleElementAndIframe(
+ t,
+ bundle_url,
+ frame_url
+ );
+ // The iframe is cross-origin. So accessing iframe.contentWindow.location
+ // should throw a SecurityError.
+ assert_throws_dom("SecurityError", () => {
+ iframe.contentWindow.location.href;
+ });
+ }, "The uuid-in-package: URL iframe must be cross-origin.");
+
+ uuid_iframe_test(
+ "location.href",
+ "uuid-in-package:429fcc4e-0696-4bad-b099-ee9175f023ae",
+ "location.href in opaque-origin iframe."
+ );
+
+ uuid_iframe_test(
+ "(" +
+ (() => {
+ try {
+ let result = window.localStorage;
+ return "no error";
+ } catch (e) {
+ return e.name;
+ }
+ }).toString() +
+ ")()",
+ "SecurityError",
+ "Accesing window.localStorage should throw a SecurityError."
+ );
+
+ uuid_iframe_test(
+ "(" +
+ (() => {
+ try {
+ let result = window.sessionStorage;
+ return "no error";
+ } catch (e) {
+ return e.name;
+ }
+ }).toString() +
+ ")()",
+ "SecurityError",
+ "Accesing window.sessionStorage should throw a SecurityError."
+ );
+
+ uuid_iframe_test(
+ "(" +
+ (() => {
+ try {
+ let result = document.cookie;
+ return "no error";
+ } catch (e) {
+ return e.name;
+ }
+ }).toString() +
+ ")()",
+ "SecurityError",
+ "Accesing document.cookie should throw a SecurityError."
+ );
+
+ uuid_iframe_test(
+ "(" +
+ (() => {
+ try {
+ let request = window.indexedDB.open("db");
+ return "no error";
+ } catch (e) {
+ return e.name;
+ }
+ }).toString() +
+ ")()",
+ "SecurityError",
+ "Opening an indexedDB should throw a SecurityError."
+ );
+
+ uuid_iframe_test(
+ "window.caches === undefined",
+ true,
+ "window.caches should be undefined."
+ );
+
+ function uuid_iframe_test(code, expected, name) {
+ promise_test(async (t) => {
+ const bundle_url = "../resources/wbn/uuid-in-package.wbn";
+ const frame_url =
+ "uuid-in-package:429fcc4e-0696-4bad-b099-ee9175f023ae";
+ const iframe = await createWebBundleElementAndIframe(
+ t,
+ bundle_url,
+ frame_url
+ );
+ assert_equals(await evalInIframe(iframe, code), expected);
+ }, name + "uuid-in-package");
+ }
+
+ async function createWebBundleElementAndIframe(t, bundle_url, frame_url) {
+ const element = createWebBundleElement(bundle_url, [frame_url]);
+ document.body.appendChild(element);
+ const iframe = document.createElement("iframe");
+ t.add_cleanup(() => {
+ document.body.removeChild(element);
+ document.body.removeChild(iframe);
+ });
+ iframe.src = frame_url;
+ const load_promise = new Promise((resolve) => {
+ iframe.addEventListener("load", resolve);
+ });
+ document.body.appendChild(iframe);
+ await load_promise;
+ return iframe;
+ }
+ </script>
+</body>
diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/subresource-load.https.tentative.sub.html b/testing/web-platform/tests/web-bundle/subresource-loading/subresource-load.https.tentative.sub.html
new file mode 100644
index 0000000000..10c207020c
--- /dev/null
+++ b/testing/web-platform/tests/web-bundle/subresource-loading/subresource-load.https.tentative.sub.html
@@ -0,0 +1,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>
diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/supports-webbundle.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/supports-webbundle.https.tentative.html
new file mode 100644
index 0000000000..9beae287a0
--- /dev/null
+++ b/testing/web-platform/tests/web-bundle/subresource-loading/supports-webbundle.https.tentative.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>HTMLScriptElement.supports webbundle</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(() => {
+ assert_true(HTMLScriptElement.supports('webbundle'));
+}, 'HTMLScriptElement.supports returns true for \'webbundle\'');
+
+test(() => {
+ assert_false(HTMLScriptElement.supports(' webbundle'));
+ assert_false(HTMLScriptElement.supports('webbundle '));
+ assert_false(HTMLScriptElement.supports('WEBBUNDLE'));
+}, 'HTMLScriptElement.supports returns false for unsupported types');
+</script>