diff --git a/fetch/private-network-access/resources/support.sub.js b/fetch/private-network-access/resources/support.sub.js
index 8ffdf100ee8d81f..a104021d158d932 100644
--- a/fetch/private-network-access/resources/support.sub.js
+++ b/fetch/private-network-access/resources/support.sub.js
@@ -635,6 +635,23 @@ async function workerFetchTest(t, { source, target, expected }) {
assert_equals(message, expected.message, "response body");
}
+async function workerBlobFetchTest(t, { source, target, expected }) {
+ const targetUrl = preflightUrl(target);
+
+ const fetcherUrl = resolveUrl(
+ 'resources/worker-blob-fetcher.html', sourceResolveOptions(source));
+
+ const reply = futureMessage();
+ const iframe = await appendIframe(t, document, fetcherUrl);
+
+ iframe.contentWindow.postMessage({ url: targetUrl.href }, "*");
+
+ const { error, status, message } = await reply;
+ assert_equals(error, expected.error, "fetch error");
+ assert_equals(status, expected.status, "response status");
+ assert_equals(message, expected.message, "response body");
+}
+
async function sharedWorkerFetchTest(t, { source, target, expected }) {
const targetUrl = preflightUrl(target);
diff --git a/fetch/private-network-access/resources/worker-blob-fetcher.html b/fetch/private-network-access/resources/worker-blob-fetcher.html
new file mode 100644
index 000000000000000..18a454b7fa36288
--- /dev/null
+++ b/fetch/private-network-access/resources/worker-blob-fetcher.html
@@ -0,0 +1,45 @@
+
+
+
Worker Fetcher
+
diff --git a/fetch/private-network-access/worker-blob-fetch.window.js b/fetch/private-network-access/worker-blob-fetch.window.js
new file mode 100644
index 000000000000000..80374143a283f42
--- /dev/null
+++ b/fetch/private-network-access/worker-blob-fetch.window.js
@@ -0,0 +1,143 @@
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests check that fetches from within `Worker` scripts loaded from blob
+// URLs are subject to Private Network Access checks, just like fetches from
+// within documents.
+//
+// This file covers only those tests that must execute in a non-secure context.
+// Other tests are defined in: worker-fetch.https.window.js
+
+promise_test(t => workerBlobFetchTest(t, {
+ source: { server: Server.HTTP_LOCAL },
+ target: { server: Server.HTTP_LOCAL },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "local to local: success.");
+
+promise_test(t => workerBlobFetchTest(t, {
+ source: { server: Server.HTTP_PRIVATE },
+ target: {
+ server: Server.HTTP_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "private to local: failure.");
+
+promise_test(t => workerBlobFetchTest(t, {
+ source: { server: Server.HTTP_PRIVATE },
+ target: { server: Server.HTTP_PRIVATE },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "private to private: success.");
+
+promise_test(t => workerBlobFetchTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: {
+ server: Server.HTTP_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "public to local: failure.");
+
+promise_test(t => workerBlobFetchTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: {
+ server: Server.HTTP_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "public to private: failure.");
+
+promise_test(t => workerBlobFetchTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: { server: Server.HTTP_PUBLIC },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "public to public: success.");
+
+promise_test(t => workerBlobFetchTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTP_LOCAL,
+ behavior: { preflight: PreflightBehavior.optionalSuccess(token()) },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "treat-as-public to local: failure.");
+
+promise_test(t => workerBlobFetchTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTP_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "treat-as-public to private: failure.");
+
+promise_test(t => workerBlobFetchTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTP_PUBLIC,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "treat-as-public to public: success.");
+
+// The following tests verify that workers served over HTTPS are not allowed to
+// make private network requests because they are not secure contexts.
+
+promise_test(t => workerBlobFetchTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: {
+ server: Server.HTTP_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "private https to local: failure.");
+
+promise_test(t => workerBlobFetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTP_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "public https to private: failure.");
+
+promise_test(t => workerBlobFetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTP_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "public https to local: failure.");