diff options
Diffstat (limited to 'testing/web-platform/tests/web-bundle/subresource-loading')
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> |