diff --git a/common/refresh.py b/common/refresh.py
new file mode 100644
index 00000000000000..0d309900d473c6
--- /dev/null
+++ b/common/refresh.py
@@ -0,0 +1,11 @@
+def main(request, response):
+ """
+ Respond with a blank HTML document and a `Refresh` header which describes
+ an immediate redirect to the URL specified by the requests `location` query
+ string parameter
+ """
+ headers = [
+ (b'Content-Type', b'text/html'),
+ (b'Refresh', b'0; URL=' + request.GET.first(b'location'))
+ ]
+ return (200, headers, b'')
diff --git a/fetch/api/resources/redirect.py b/fetch/api/resources/redirect.py
index 2ff8b78f09539b..d52ab5f3eee757 100644
--- a/fetch/api/resources/redirect.py
+++ b/fetch/api/resources/redirect.py
@@ -35,6 +35,8 @@ def main(request, response):
if b"redirect_status" in request.GET:
status = int(request.GET[b'redirect_status'])
+ elif b"redirect_status" in request.POST:
+ status = int(request.POST[b'redirect_status'])
stashed_data[b'count'] += 1
diff --git a/fetch/metadata/download.https.sub.html b/fetch/metadata/download.https.sub.html
deleted file mode 100644
index a95d9d65f00580..00000000000000
--- a/fetch/metadata/download.https.sub.html
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/fetch/metadata/favicon.https.sub.html b/fetch/metadata/favicon.https.sub.html
deleted file mode 100644
index 50ea5b27415826..00000000000000
--- a/fetch/metadata/favicon.https.sub.html
+++ /dev/null
@@ -1,67 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/fetch/metadata/fetch-via-serviceworker--fallback.https.sub.html b/fetch/metadata/fetch-via-serviceworker--fallback.https.sub.html
deleted file mode 100644
index 9f494461372d3a..00000000000000
--- a/fetch/metadata/fetch-via-serviceworker--fallback.https.sub.html
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/fetch/metadata/fetch-via-serviceworker--respondWith.https.sub.html b/fetch/metadata/fetch-via-serviceworker--respondWith.https.sub.html
deleted file mode 100644
index 03d5fd1cc31b6f..00000000000000
--- a/fetch/metadata/fetch-via-serviceworker--respondWith.https.sub.html
+++ /dev/null
@@ -1,51 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/fetch/metadata/fetch.sub.html b/fetch/metadata/fetch.sub.html
deleted file mode 100644
index 1659a9fb6bf274..00000000000000
--- a/fetch/metadata/fetch.sub.html
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
-
-
diff --git a/fetch/metadata/font.https.sub.html b/fetch/metadata/font.https.sub.html
deleted file mode 100644
index 2c705085d6a30c..00000000000000
--- a/fetch/metadata/font.https.sub.html
+++ /dev/null
@@ -1,78 +0,0 @@
-
-
-
-
-
-
-
- 1
- 2
- 3
-
-
-
-
-
-
-
-
-
-
-
diff --git a/fetch/metadata/form.https.sub.html b/fetch/metadata/form.https.sub.html
deleted file mode 100644
index 2e6332c747f554..00000000000000
--- a/fetch/metadata/form.https.sub.html
+++ /dev/null
@@ -1,85 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/fetch/metadata/generated/appcache-manifest.https.sub.html b/fetch/metadata/generated/appcache-manifest.https.sub.html
new file mode 100644
index 00000000000000..cf322fd34bc9ab
--- /dev/null
+++ b/fetch/metadata/generated/appcache-manifest.https.sub.html
@@ -0,0 +1,341 @@
+
+
+
+
+ HTTP headers on request for Appcache manifest
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/audioworklet.https.sub.html b/fetch/metadata/generated/audioworklet.https.sub.html
new file mode 100644
index 00000000000000..64fb7607e26a17
--- /dev/null
+++ b/fetch/metadata/generated/audioworklet.https.sub.html
@@ -0,0 +1,271 @@
+
+
+
+
+ HTTP headers on request for AudioWorklet module
+
+
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/css-font-face.https.sub.tentative.html b/fetch/metadata/generated/css-font-face.https.sub.tentative.html
new file mode 100644
index 00000000000000..332effeb1f80a5
--- /dev/null
+++ b/fetch/metadata/generated/css-font-face.https.sub.tentative.html
@@ -0,0 +1,230 @@
+
+
+
+
+ HTTP headers on request for CSS font-face
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/css-font-face.sub.tentative.html b/fetch/metadata/generated/css-font-face.sub.tentative.html
new file mode 100644
index 00000000000000..8a0b90cee103db
--- /dev/null
+++ b/fetch/metadata/generated/css-font-face.sub.tentative.html
@@ -0,0 +1,196 @@
+
+
+
+
+ HTTP headers on request for CSS font-face
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/css-images.https.sub.tentative.html b/fetch/metadata/generated/css-images.https.sub.tentative.html
new file mode 100644
index 00000000000000..3fa240192894e0
--- /dev/null
+++ b/fetch/metadata/generated/css-images.https.sub.tentative.html
@@ -0,0 +1,1384 @@
+
+
+
+
+
+ HTTP headers on request for CSS image-accepting properties
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/css-images.sub.tentative.html b/fetch/metadata/generated/css-images.sub.tentative.html
new file mode 100644
index 00000000000000..f1ef27cf08730e
--- /dev/null
+++ b/fetch/metadata/generated/css-images.sub.tentative.html
@@ -0,0 +1,1099 @@
+
+
+
+
+
+ HTTP headers on request for CSS image-accepting properties
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/element-a.https.sub.html b/fetch/metadata/generated/element-a.https.sub.html
new file mode 100644
index 00000000000000..dffd36c73ee373
--- /dev/null
+++ b/fetch/metadata/generated/element-a.https.sub.html
@@ -0,0 +1,482 @@
+
+
+
+
+
+ HTTP headers on request for HTML "a" element navigation
+
+
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/element-a.sub.html b/fetch/metadata/generated/element-a.sub.html
new file mode 100644
index 00000000000000..0661de3c871bb4
--- /dev/null
+++ b/fetch/metadata/generated/element-a.sub.html
@@ -0,0 +1,342 @@
+
+
+
+
+
+ HTTP headers on request for HTML "a" element navigation
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/element-area.https.sub.html b/fetch/metadata/generated/element-area.https.sub.html
new file mode 100644
index 00000000000000..be3f5f9b621176
--- /dev/null
+++ b/fetch/metadata/generated/element-area.https.sub.html
@@ -0,0 +1,482 @@
+
+
+
+
+
+ HTTP headers on request for HTML "area" element navigation
+
+
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/element-area.sub.html b/fetch/metadata/generated/element-area.sub.html
new file mode 100644
index 00000000000000..5f5c338324f001
--- /dev/null
+++ b/fetch/metadata/generated/element-area.sub.html
@@ -0,0 +1,342 @@
+
+
+
+
+
+ HTTP headers on request for HTML "area" element navigation
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/element-audio.https.sub.html b/fetch/metadata/generated/element-audio.https.sub.html
new file mode 100644
index 00000000000000..a9d951233e296f
--- /dev/null
+++ b/fetch/metadata/generated/element-audio.https.sub.html
@@ -0,0 +1,325 @@
+
+
+
+
+ HTTP headers on request for HTML "audio" element source
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/element-audio.sub.html b/fetch/metadata/generated/element-audio.sub.html
new file mode 100644
index 00000000000000..2b62632ac2eb8c
--- /dev/null
+++ b/fetch/metadata/generated/element-audio.sub.html
@@ -0,0 +1,229 @@
+
+
+
+
+ HTTP headers on request for HTML "audio" element source
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/element-embed.https.sub.html b/fetch/metadata/generated/element-embed.https.sub.html
new file mode 100644
index 00000000000000..819bed888ee4cb
--- /dev/null
+++ b/fetch/metadata/generated/element-embed.https.sub.html
@@ -0,0 +1,224 @@
+
+
+
+
+ HTTP headers on request for HTML "embed" element source
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/element-embed.sub.html b/fetch/metadata/generated/element-embed.sub.html
new file mode 100644
index 00000000000000..b6e14a55e4a549
--- /dev/null
+++ b/fetch/metadata/generated/element-embed.sub.html
@@ -0,0 +1,190 @@
+
+
+
+
+ HTTP headers on request for HTML "embed" element source
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/element-frame.https.sub.html b/fetch/metadata/generated/element-frame.https.sub.html
new file mode 100644
index 00000000000000..17504ff5635c4b
--- /dev/null
+++ b/fetch/metadata/generated/element-frame.https.sub.html
@@ -0,0 +1,309 @@
+
+
+
+
+ HTTP headers on request for HTML "frame" element source
+
+
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/element-frame.sub.html b/fetch/metadata/generated/element-frame.sub.html
new file mode 100644
index 00000000000000..2d9a7ec97d7808
--- /dev/null
+++ b/fetch/metadata/generated/element-frame.sub.html
@@ -0,0 +1,250 @@
+
+
+
+
+ HTTP headers on request for HTML "frame" element source
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/element-iframe.https.sub.html b/fetch/metadata/generated/element-iframe.https.sub.html
new file mode 100644
index 00000000000000..fba1c8b9e02f19
--- /dev/null
+++ b/fetch/metadata/generated/element-iframe.https.sub.html
@@ -0,0 +1,309 @@
+
+
+
+
+ HTTP headers on request for HTML "frame" element source
+
+
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/element-iframe.sub.html b/fetch/metadata/generated/element-iframe.sub.html
new file mode 100644
index 00000000000000..6f71cc0d254737
--- /dev/null
+++ b/fetch/metadata/generated/element-iframe.sub.html
@@ -0,0 +1,250 @@
+
+
+
+
+ HTTP headers on request for HTML "frame" element source
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/element-img-environment-change.https.sub.html b/fetch/metadata/generated/element-img-environment-change.https.sub.html
new file mode 100644
index 00000000000000..a19aa117c4f46e
--- /dev/null
+++ b/fetch/metadata/generated/element-img-environment-change.https.sub.html
@@ -0,0 +1,357 @@
+
+
+
+
+ HTTP headers on image request triggered by change to environment
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/element-img-environment-change.sub.html b/fetch/metadata/generated/element-img-environment-change.sub.html
new file mode 100644
index 00000000000000..96658726ba0c91
--- /dev/null
+++ b/fetch/metadata/generated/element-img-environment-change.sub.html
@@ -0,0 +1,270 @@
+
+
+
+
+ HTTP headers on image request triggered by change to environment
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/element-img.https.sub.html b/fetch/metadata/generated/element-img.https.sub.html
new file mode 100644
index 00000000000000..51d6e082b0e336
--- /dev/null
+++ b/fetch/metadata/generated/element-img.https.sub.html
@@ -0,0 +1,645 @@
+
+
+
+
+ HTTP headers on request for HTML "img" element source
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/element-img.sub.html b/fetch/metadata/generated/element-img.sub.html
new file mode 100644
index 00000000000000..5a4b152c5525b3
--- /dev/null
+++ b/fetch/metadata/generated/element-img.sub.html
@@ -0,0 +1,456 @@
+
+
+
+
+ HTTP headers on request for HTML "img" element source
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/element-input-image.https.sub.html b/fetch/metadata/generated/element-input-image.https.sub.html
new file mode 100644
index 00000000000000..7fa674043e2517
--- /dev/null
+++ b/fetch/metadata/generated/element-input-image.https.sub.html
@@ -0,0 +1,229 @@
+
+
+
+
+ HTTP headers on request for HTML "input" element with type="button"
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/element-input-image.sub.html b/fetch/metadata/generated/element-input-image.sub.html
new file mode 100644
index 00000000000000..fb2a146b199dba
--- /dev/null
+++ b/fetch/metadata/generated/element-input-image.sub.html
@@ -0,0 +1,184 @@
+
+
+
+
+ HTTP headers on request for HTML "input" element with type="button"
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/element-link-icon.https.sub.html b/fetch/metadata/generated/element-link-icon.https.sub.html
new file mode 100644
index 00000000000000..b2449607553e41
--- /dev/null
+++ b/fetch/metadata/generated/element-link-icon.https.sub.html
@@ -0,0 +1,371 @@
+
+
+
+
+
+ HTTP headers on request for HTML "link" element with rel="icon"
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/element-link-icon.sub.html b/fetch/metadata/generated/element-link-icon.sub.html
new file mode 100644
index 00000000000000..e9226c190a5089
--- /dev/null
+++ b/fetch/metadata/generated/element-link-icon.sub.html
@@ -0,0 +1,279 @@
+
+
+
+
+
+ HTTP headers on request for HTML "link" element with rel="icon"
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/element-link-prefetch.https.optional.sub.html b/fetch/metadata/generated/element-link-prefetch.https.optional.sub.html
new file mode 100644
index 00000000000000..bdd684a2679968
--- /dev/null
+++ b/fetch/metadata/generated/element-link-prefetch.https.optional.sub.html
@@ -0,0 +1,559 @@
+
+
+
+
+
+ HTTP headers on request for HTML "link" element with rel="prefetch"
+
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/element-link-prefetch.optional.sub.html b/fetch/metadata/generated/element-link-prefetch.optional.sub.html
new file mode 100644
index 00000000000000..c2244883cc9077
--- /dev/null
+++ b/fetch/metadata/generated/element-link-prefetch.optional.sub.html
@@ -0,0 +1,275 @@
+
+
+
+
+
+ HTTP headers on request for HTML "link" element with rel="prefetch"
+
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/element-meta-refresh.https.optional.sub.html b/fetch/metadata/generated/element-meta-refresh.https.optional.sub.html
new file mode 100644
index 00000000000000..3a1a8eb49af62a
--- /dev/null
+++ b/fetch/metadata/generated/element-meta-refresh.https.optional.sub.html
@@ -0,0 +1,276 @@
+
+
+
+
+ HTTP headers on request for HTML "meta" element with http-equiv="refresh"
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/element-meta-refresh.optional.sub.html b/fetch/metadata/generated/element-meta-refresh.optional.sub.html
new file mode 100644
index 00000000000000..df3e92e2c8fdeb
--- /dev/null
+++ b/fetch/metadata/generated/element-meta-refresh.optional.sub.html
@@ -0,0 +1,225 @@
+
+
+
+
+ HTTP headers on request for HTML "meta" element with http-equiv="refresh"
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/element-picture.https.sub.html b/fetch/metadata/generated/element-picture.https.sub.html
new file mode 100644
index 00000000000000..ba6636a019a304
--- /dev/null
+++ b/fetch/metadata/generated/element-picture.https.sub.html
@@ -0,0 +1,997 @@
+
+
+
+
+ HTTP headers on request for HTML "picture" element source
+
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/element-picture.sub.html b/fetch/metadata/generated/element-picture.sub.html
new file mode 100644
index 00000000000000..64f851c682b466
--- /dev/null
+++ b/fetch/metadata/generated/element-picture.sub.html
@@ -0,0 +1,721 @@
+
+
+
+
+ HTTP headers on request for HTML "picture" element source
+
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/element-script.https.sub.html b/fetch/metadata/generated/element-script.https.sub.html
new file mode 100644
index 00000000000000..dcdcba2792d5c3
--- /dev/null
+++ b/fetch/metadata/generated/element-script.https.sub.html
@@ -0,0 +1,593 @@
+
+
+
+
+ HTTP headers on request for HTML "script" element source
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/element-script.sub.html b/fetch/metadata/generated/element-script.sub.html
new file mode 100644
index 00000000000000..a2526698fbdaa4
--- /dev/null
+++ b/fetch/metadata/generated/element-script.sub.html
@@ -0,0 +1,488 @@
+
+
+
+
+ HTTP headers on request for HTML "script" element source
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/element-video-poster.https.sub.html b/fetch/metadata/generated/element-video-poster.https.sub.html
new file mode 100644
index 00000000000000..5805b46bd0cd67
--- /dev/null
+++ b/fetch/metadata/generated/element-video-poster.https.sub.html
@@ -0,0 +1,243 @@
+
+
+
+
+ HTTP headers on request for HTML "video" element "poster"
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/element-video-poster.sub.html b/fetch/metadata/generated/element-video-poster.sub.html
new file mode 100644
index 00000000000000..e6cc5ee7e0695d
--- /dev/null
+++ b/fetch/metadata/generated/element-video-poster.sub.html
@@ -0,0 +1,198 @@
+
+
+
+
+ HTTP headers on request for HTML "video" element "poster"
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/element-video.https.sub.html b/fetch/metadata/generated/element-video.https.sub.html
new file mode 100644
index 00000000000000..971360dceea4a0
--- /dev/null
+++ b/fetch/metadata/generated/element-video.https.sub.html
@@ -0,0 +1,325 @@
+
+
+
+
+ HTTP headers on request for HTML "video" element source
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/element-video.sub.html b/fetch/metadata/generated/element-video.sub.html
new file mode 100644
index 00000000000000..9707413ab68b17
--- /dev/null
+++ b/fetch/metadata/generated/element-video.sub.html
@@ -0,0 +1,229 @@
+
+
+
+
+ HTTP headers on request for HTML "video" element source
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/fetch-via-serviceworker.https.sub.html b/fetch/metadata/generated/fetch-via-serviceworker.https.sub.html
new file mode 100644
index 00000000000000..1fd67e912d6326
--- /dev/null
+++ b/fetch/metadata/generated/fetch-via-serviceworker.https.sub.html
@@ -0,0 +1,683 @@
+
+
+
+
+ HTTP headers on request using the "fetch" API and passing through a Serive Worker
+
+
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/fetch.https.sub.html b/fetch/metadata/generated/fetch.https.sub.html
new file mode 100644
index 00000000000000..dde1daede4ec3c
--- /dev/null
+++ b/fetch/metadata/generated/fetch.https.sub.html
@@ -0,0 +1,302 @@
+
+
+
+
+ HTTP headers on request using the "fetch" API
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/fetch.sub.html b/fetch/metadata/generated/fetch.sub.html
new file mode 100644
index 00000000000000..d28ea9bb9004f2
--- /dev/null
+++ b/fetch/metadata/generated/fetch.sub.html
@@ -0,0 +1,220 @@
+
+
+
+
+ HTTP headers on request using the "fetch" API
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/form-submission.https.sub.html b/fetch/metadata/generated/form-submission.https.sub.html
new file mode 100644
index 00000000000000..a0f683add1789e
--- /dev/null
+++ b/fetch/metadata/generated/form-submission.https.sub.html
@@ -0,0 +1,521 @@
+
+
+
+
+ HTTP headers on request for HTML form navigation
+
+
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/form-submission.sub.html b/fetch/metadata/generated/form-submission.sub.html
new file mode 100644
index 00000000000000..c4a5e2fa7ba208
--- /dev/null
+++ b/fetch/metadata/generated/form-submission.sub.html
@@ -0,0 +1,399 @@
+
+
+
+
+ HTTP headers on request for HTML form navigation
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/header-link.https.sub.html b/fetch/metadata/generated/header-link.https.sub.html
new file mode 100644
index 00000000000000..09f011389559bf
--- /dev/null
+++ b/fetch/metadata/generated/header-link.https.sub.html
@@ -0,0 +1,529 @@
+
+
+
+
+ HTTP headers on request for HTTP "Link" header
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/header-link.https.sub.tentative.html b/fetch/metadata/generated/header-link.https.sub.tentative.html
new file mode 100644
index 00000000000000..307c37fbf77c9a
--- /dev/null
+++ b/fetch/metadata/generated/header-link.https.sub.tentative.html
@@ -0,0 +1,51 @@
+
+
+
+
+ HTTP headers on request for HTTP "Link" header
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/header-link.sub.html b/fetch/metadata/generated/header-link.sub.html
new file mode 100644
index 00000000000000..8b6cdae0edad7b
--- /dev/null
+++ b/fetch/metadata/generated/header-link.sub.html
@@ -0,0 +1,460 @@
+
+
+
+
+ HTTP headers on request for HTTP "Link" header
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/header-refresh.https.optional.sub.html b/fetch/metadata/generated/header-refresh.https.optional.sub.html
new file mode 100644
index 00000000000000..c303d871413d5c
--- /dev/null
+++ b/fetch/metadata/generated/header-refresh.https.optional.sub.html
@@ -0,0 +1,272 @@
+
+
+
+
+ HTTP headers on request for HTTP "Refresh" header
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/header-refresh.optional.sub.html b/fetch/metadata/generated/header-refresh.optional.sub.html
new file mode 100644
index 00000000000000..56f24663e1d019
--- /dev/null
+++ b/fetch/metadata/generated/header-refresh.optional.sub.html
@@ -0,0 +1,221 @@
+
+
+
+
+ HTTP headers on request for HTTP "Refresh" header
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/script-module-import-dynamic.https.sub.html b/fetch/metadata/generated/script-module-import-dynamic.https.sub.html
new file mode 100644
index 00000000000000..72d60fc30cb5f0
--- /dev/null
+++ b/fetch/metadata/generated/script-module-import-dynamic.https.sub.html
@@ -0,0 +1,254 @@
+
+
+
+
+ HTTP headers on request for dynamic ECMAScript module import
+
+
+
+
+
diff --git a/fetch/metadata/generated/script-module-import-dynamic.sub.html b/fetch/metadata/generated/script-module-import-dynamic.sub.html
new file mode 100644
index 00000000000000..088720c23e0db4
--- /dev/null
+++ b/fetch/metadata/generated/script-module-import-dynamic.sub.html
@@ -0,0 +1,214 @@
+
+
+
+
+ HTTP headers on request for dynamic ECMAScript module import
+
+
+
+
+
diff --git a/fetch/metadata/generated/script-module-import-static.https.sub.html b/fetch/metadata/generated/script-module-import-static.https.sub.html
new file mode 100644
index 00000000000000..cea3464f80701a
--- /dev/null
+++ b/fetch/metadata/generated/script-module-import-static.https.sub.html
@@ -0,0 +1,288 @@
+
+
+
+
+ HTTP headers on request for static ECMAScript module import
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/script-module-import-static.sub.html b/fetch/metadata/generated/script-module-import-static.sub.html
new file mode 100644
index 00000000000000..0f94f71cf6f186
--- /dev/null
+++ b/fetch/metadata/generated/script-module-import-static.sub.html
@@ -0,0 +1,246 @@
+
+
+
+
+ HTTP headers on request for static ECMAScript module import
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/serviceworker.https.sub.html b/fetch/metadata/generated/serviceworker.https.sub.html
new file mode 100644
index 00000000000000..12e37369a422c5
--- /dev/null
+++ b/fetch/metadata/generated/serviceworker.https.sub.html
@@ -0,0 +1,170 @@
+
+
+
+
+
+ HTTP headers on request for Service Workers
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/svg-image.https.sub.html b/fetch/metadata/generated/svg-image.https.sub.html
new file mode 100644
index 00000000000000..b059eb31456dfc
--- /dev/null
+++ b/fetch/metadata/generated/svg-image.https.sub.html
@@ -0,0 +1,367 @@
+
+
+
+
+
+ HTTP headers on request for SVG "image" element source
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/svg-image.sub.html b/fetch/metadata/generated/svg-image.sub.html
new file mode 100644
index 00000000000000..a28bbb12eb71ec
--- /dev/null
+++ b/fetch/metadata/generated/svg-image.sub.html
@@ -0,0 +1,265 @@
+
+
+
+
+
+ HTTP headers on request for SVG "image" element source
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/window-history.https.sub.html b/fetch/metadata/generated/window-history.https.sub.html
new file mode 100644
index 00000000000000..c2b3079a6d3670
--- /dev/null
+++ b/fetch/metadata/generated/window-history.https.sub.html
@@ -0,0 +1,237 @@
+
+
+
+
+ HTTP headers on request for navigation via the HTML History API
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/window-history.sub.html b/fetch/metadata/generated/window-history.sub.html
new file mode 100644
index 00000000000000..333d90c28609bd
--- /dev/null
+++ b/fetch/metadata/generated/window-history.sub.html
@@ -0,0 +1,360 @@
+
+
+
+
+
+ HTTP headers on request for navigation via the HTML History API
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/window-location.https.sub.html b/fetch/metadata/generated/window-location.https.sub.html
new file mode 100644
index 00000000000000..4a0d2fdc068078
--- /dev/null
+++ b/fetch/metadata/generated/window-location.https.sub.html
@@ -0,0 +1,1184 @@
+
+
+
+
+
+ HTTP headers on request for navigation via the HTML Location API
+
+
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/window-location.sub.html b/fetch/metadata/generated/window-location.sub.html
new file mode 100644
index 00000000000000..bb3e6805cb0114
--- /dev/null
+++ b/fetch/metadata/generated/window-location.sub.html
@@ -0,0 +1,894 @@
+
+
+
+
+
+ HTTP headers on request for navigation via the HTML Location API
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/worker-dedicated-constructor.https.sub.html b/fetch/metadata/generated/worker-dedicated-constructor.https.sub.html
new file mode 100644
index 00000000000000..86f1760755453c
--- /dev/null
+++ b/fetch/metadata/generated/worker-dedicated-constructor.https.sub.html
@@ -0,0 +1,118 @@
+
+
+
+
+ HTTP headers on request for dedicated worker via the "Worker" constructor
+
+
+
+
+
diff --git a/fetch/metadata/generated/worker-dedicated-constructor.sub.html b/fetch/metadata/generated/worker-dedicated-constructor.sub.html
new file mode 100644
index 00000000000000..69ac7682a5c5ff
--- /dev/null
+++ b/fetch/metadata/generated/worker-dedicated-constructor.sub.html
@@ -0,0 +1,204 @@
+
+
+
+
+ HTTP headers on request for dedicated worker via the "Worker" constructor
+
+
+
+
+
diff --git a/fetch/metadata/generated/worker-dedicated-importscripts.https.sub.html b/fetch/metadata/generated/worker-dedicated-importscripts.https.sub.html
new file mode 100644
index 00000000000000..0cd9f35d582535
--- /dev/null
+++ b/fetch/metadata/generated/worker-dedicated-importscripts.https.sub.html
@@ -0,0 +1,268 @@
+
+
+
+
+ HTTP headers on request for dedicated worker via the "importScripts" API
+
+
+
+
+
diff --git a/fetch/metadata/generated/worker-dedicated-importscripts.sub.html b/fetch/metadata/generated/worker-dedicated-importscripts.sub.html
new file mode 100644
index 00000000000000..0555bbaf432696
--- /dev/null
+++ b/fetch/metadata/generated/worker-dedicated-importscripts.sub.html
@@ -0,0 +1,228 @@
+
+
+
+
+ HTTP headers on request for dedicated worker via the "importScripts" API
+
+
+
+
+
diff --git a/fetch/metadata/history.https.sub.html b/fetch/metadata/history.https.sub.html
deleted file mode 100644
index f0119e6c754d36..00000000000000
--- a/fetch/metadata/history.https.sub.html
+++ /dev/null
@@ -1,79 +0,0 @@
-
-
-
-
-
-
diff --git a/fetch/metadata/iframe.https.sub.html b/fetch/metadata/iframe.https.sub.html
deleted file mode 100644
index 1c793fd7e45990..00000000000000
--- a/fetch/metadata/iframe.https.sub.html
+++ /dev/null
@@ -1,85 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/fetch/metadata/iframe.sub.html b/fetch/metadata/iframe.sub.html
deleted file mode 100644
index e0d5b0b8cb824e..00000000000000
--- a/fetch/metadata/iframe.sub.html
+++ /dev/null
@@ -1,82 +0,0 @@
-
-
-
-
-
-
diff --git a/fetch/metadata/img.https.sub.html b/fetch/metadata/img.https.sub.html
deleted file mode 100644
index 93acffef11ae23..00000000000000
--- a/fetch/metadata/img.https.sub.html
+++ /dev/null
@@ -1,75 +0,0 @@
-
-
-
-
-
-
-
diff --git a/fetch/metadata/prefetch.https.sub.html b/fetch/metadata/prefetch.https.sub.html
deleted file mode 100644
index a0ff73c0d0868e..00000000000000
--- a/fetch/metadata/prefetch.https.sub.html
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-
-
-
-
diff --git a/fetch/metadata/redirect/cross-site-redirect.https.sub.html b/fetch/metadata/redirect/cross-site-redirect.https.sub.html
deleted file mode 100644
index 827eb982e47f3f..00000000000000
--- a/fetch/metadata/redirect/cross-site-redirect.https.sub.html
+++ /dev/null
@@ -1,77 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/fetch/metadata/redirect/multiple-redirect-cross-site.https.sub.html b/fetch/metadata/redirect/multiple-redirect-cross-site.https.sub.html
deleted file mode 100644
index fc986aaf4fdcff..00000000000000
--- a/fetch/metadata/redirect/multiple-redirect-cross-site.https.sub.html
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/fetch/metadata/redirect/multiple-redirect-https-downgrade-upgrade-prefetch.optional.sub.html b/fetch/metadata/redirect/multiple-redirect-https-downgrade-upgrade-prefetch.optional.sub.html
deleted file mode 100644
index 970eb3373a04b6..00000000000000
--- a/fetch/metadata/redirect/multiple-redirect-https-downgrade-upgrade-prefetch.optional.sub.html
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/fetch/metadata/redirect/multiple-redirect-https-downgrade-upgrade.sub.html b/fetch/metadata/redirect/multiple-redirect-https-downgrade-upgrade.sub.html
index 907cf5c617df56..0f8f3200165c63 100644
--- a/fetch/metadata/redirect/multiple-redirect-https-downgrade-upgrade.sub.html
+++ b/fetch/metadata/redirect/multiple-redirect-https-downgrade-upgrade.sub.html
@@ -6,68 +6,13 @@
-
-Downgraded then upgraded font
-
-
diff --git a/fetch/metadata/redirect/multiple-redirect-same-site.https.sub.html b/fetch/metadata/redirect/multiple-redirect-same-site.https.sub.html
deleted file mode 100644
index f5483ac3fa2f3f..00000000000000
--- a/fetch/metadata/redirect/multiple-redirect-same-site.https.sub.html
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/fetch/metadata/redirect/redirect-http-upgrade-prefetch.optional.sub.html b/fetch/metadata/redirect/redirect-http-upgrade-prefetch.optional.sub.html
deleted file mode 100644
index c69f0e92592723..00000000000000
--- a/fetch/metadata/redirect/redirect-http-upgrade-prefetch.optional.sub.html
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/fetch/metadata/redirect/redirect-http-upgrade.sub.html b/fetch/metadata/redirect/redirect-http-upgrade.sub.html
index 133576cb0d7236..fa765b66d03898 100644
--- a/fetch/metadata/redirect/redirect-http-upgrade.sub.html
+++ b/fetch/metadata/redirect/redirect-http-upgrade.sub.html
@@ -6,62 +6,12 @@
-
- Upgraded font
-
-
-
-
diff --git a/fetch/metadata/redirect/redirect-https-downgrade-prefetch.optional.sub.html b/fetch/metadata/redirect/redirect-https-downgrade-prefetch.optional.sub.html
deleted file mode 100644
index a446cbad840515..00000000000000
--- a/fetch/metadata/redirect/redirect-https-downgrade-prefetch.optional.sub.html
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/fetch/metadata/redirect/redirect-https-downgrade.sub.html b/fetch/metadata/redirect/redirect-https-downgrade.sub.html
index 9eb04822b683bf..4e5a48e6f6e72e 100644
--- a/fetch/metadata/redirect/redirect-https-downgrade.sub.html
+++ b/fetch/metadata/redirect/redirect-https-downgrade.sub.html
@@ -6,64 +6,12 @@
-
- Downgraded font
-
-
-
diff --git a/fetch/metadata/redirect/same-origin-redirect.https.sub.html b/fetch/metadata/redirect/same-origin-redirect.https.sub.html
deleted file mode 100644
index 38921a71314a54..00000000000000
--- a/fetch/metadata/redirect/same-origin-redirect.https.sub.html
+++ /dev/null
@@ -1,80 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/fetch/metadata/redirect/same-site-redirect.https.sub.html b/fetch/metadata/redirect/same-site-redirect.https.sub.html
deleted file mode 100644
index f8709a14c0f0ff..00000000000000
--- a/fetch/metadata/redirect/same-site-redirect.https.sub.html
+++ /dev/null
@@ -1,80 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/fetch/metadata/resources/appcache-iframe.sub.html b/fetch/metadata/resources/appcache-iframe.sub.html
new file mode 100644
index 00000000000000..cea9a4feaec4d7
--- /dev/null
+++ b/fetch/metadata/resources/appcache-iframe.sub.html
@@ -0,0 +1,15 @@
+
+
+
+
diff --git a/fetch/metadata/resources/es-module.sub.js b/fetch/metadata/resources/es-module.sub.js
new file mode 100644
index 00000000000000..f9668a3dc67807
--- /dev/null
+++ b/fetch/metadata/resources/es-module.sub.js
@@ -0,0 +1 @@
+import '{{GET[moduleId]}}';
diff --git a/fetch/metadata/resources/fetch-via-serviceworker--respondWith--frame.html b/fetch/metadata/resources/fetch-via-serviceworker--respondWith--frame.html
deleted file mode 100644
index 98798025005497..00000000000000
--- a/fetch/metadata/resources/fetch-via-serviceworker--respondWith--frame.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-Page Title
diff --git a/fetch/metadata/resources/fetch-via-serviceworker--fallback--frame.html b/fetch/metadata/resources/fetch-via-serviceworker-frame.html
similarity index 100%
rename from fetch/metadata/resources/fetch-via-serviceworker--fallback--frame.html
rename to fetch/metadata/resources/fetch-via-serviceworker-frame.html
diff --git a/fetch/metadata/resources/go-back.html b/fetch/metadata/resources/go-back.html
deleted file mode 100644
index 002c4cea473885..00000000000000
--- a/fetch/metadata/resources/go-back.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/fetch/metadata/resources/header-link.py b/fetch/metadata/resources/header-link.py
new file mode 100644
index 00000000000000..de891163a33b17
--- /dev/null
+++ b/fetch/metadata/resources/header-link.py
@@ -0,0 +1,15 @@
+def main(request, response):
+ """
+ Respond with a blank HTML document and a `Link` header which describes
+ a link relation specified by the requests `location` and `rel` query string
+ parameters
+ """
+ headers = [
+ (b'Content-Type', b'text/html'),
+ (
+ b'Link',
+ b'<' + request.GET.first(b'location') + b'>; rel=' + request.GET.first(b'rel')
+ )
+ ]
+ return (200, headers, b'')
+
diff --git a/fetch/metadata/resources/helper.sub.js b/fetch/metadata/resources/helper.sub.js
new file mode 100644
index 00000000000000..fd179fe6f25d6a
--- /dev/null
+++ b/fetch/metadata/resources/helper.sub.js
@@ -0,0 +1,67 @@
+'use strict';
+
+/**
+ * Construct a URL which, when followed, will trigger redirection through zero
+ * or more specified origins and ultimately resolve in the Python handler
+ * `record-headers.py`.
+ *
+ * @param {string} key - the WPT server "stash" name where the request's
+ * headers should be stored
+ * @param {string[]} [origins] - zero or more origin names through which the
+ * request should pass; see the function
+ * implementation for a completel list of names
+ * and corresponding origins; If specified, the
+ * final origin will be used to access the
+ * `record-headers.py` hander.
+ * @param {object} [params] - a collection of key-value pairs to include as
+ * URL "search" parameters in the final request to
+ * `record-headers.py`
+ *
+ * @returns {string} an absolute URL
+ */
+function makeRequestURL(key, origins, params) {
+ const byName = {
+ httpOrigin: 'http://{{host}}:{{ports[http][0]}}',
+ httpSameSite: 'http://{{hosts[][www]}}:{{ports[http][0]}}',
+ httpCrossSite: 'http://{{hosts[alt][]}}:{{ports[http][0]}}',
+ httpsOrigin: 'https://{{host}}:{{ports[https][0]}}',
+ httpsSameSite: 'https://{{hosts[][www]}}:{{ports[https][0]}}',
+ httpsCrossSite: 'https://{{hosts[alt][]}}:{{ports[https][0]}}'
+ };
+ const redirectPath = '/fetch/api/resources/redirect.py?location=';
+ const path = '/fetch/metadata/resources/record-headers.py?key=' + key;
+
+ let requestUrl = path;
+ if (params) {
+ requestUrl += '&' + new URLSearchParams(params).toString();
+ }
+
+ if (origins && origins.length) {
+ requestUrl = byName[origins.pop()] + requestUrl;
+
+ while (origins.length) {
+ requestUrl = byName[origins.pop()] + redirectPath +
+ encodeURIComponent(requestUrl);
+ }
+ } else {
+ requestUrl = byName.httpsOrigin + requestUrl;
+ }
+
+ return requestUrl;
+}
+
+function retrieve(key, options) {
+ return fetch('/fetch/metadata/resources/record-headers.py?retrieve&key=' + key)
+ .then((response) => {
+ if (response.status === 204 && options && options.poll) {
+ return new Promise((resolve) => setTimeout(resolve, 300))
+ .then(() => retrieve(key, options));
+ }
+
+ if (response.status !== 200) {
+ throw new Error('Failed to query for recorded headers.');
+ }
+
+ return response.text().then((text) => JSON.parse(text));
+ });
+}
diff --git a/fetch/metadata/resources/message-opener.html b/fetch/metadata/resources/message-opener.html
new file mode 100644
index 00000000000000..eb2af7b250b9e7
--- /dev/null
+++ b/fetch/metadata/resources/message-opener.html
@@ -0,0 +1,17 @@
+
diff --git a/fetch/metadata/resources/post-to-owner-onload.py b/fetch/metadata/resources/post-to-owner-onload.py
deleted file mode 100644
index 8459edbd68a085..00000000000000
--- a/fetch/metadata/resources/post-to-owner-onload.py
+++ /dev/null
@@ -1,34 +0,0 @@
-import json
-
-from wptserve.utils import isomorphic_decode
-
-def main(request, response):
- headers = [
- (b"Content-Type", b"text/html"),
- (b"Cache-Control", b"no-cache, no-store, must-revalidate")
- ]
- key = request.GET.first(b"key", None)
-
- body = u"""
-
-
-
- """ % (json.dumps({
- u"dest": isomorphic_decode(request.headers.get(b"sec-fetch-dest", b"")),
- u"mode": isomorphic_decode(request.headers.get(b"sec-fetch-mode", b"")),
- u"site": isomorphic_decode(request.headers.get(b"sec-fetch-site", b"")),
- u"user": isomorphic_decode(request.headers.get(b"sec-fetch-user", b"")),
- }), json.dumps(key))
- return headers, body
diff --git a/fetch/metadata/resources/record-header.py b/fetch/metadata/resources/record-header.py
index f2e7273c53c075..a6b52e1e4b992e 100644
--- a/fetch/metadata/resources/record-header.py
+++ b/fetch/metadata/resources/record-header.py
@@ -138,3 +138,7 @@ def main(request, response):
"""
+
+ if key.startswith(b"script"):
+ response.headers.set(b"Content-Type", b"application/javascript")
+ return b"void 0;"
diff --git a/fetch/metadata/resources/record-headers.py b/fetch/metadata/resources/record-headers.py
new file mode 100644
index 00000000000000..0362fe228c203a
--- /dev/null
+++ b/fetch/metadata/resources/record-headers.py
@@ -0,0 +1,73 @@
+import os
+import uuid
+import hashlib
+import time
+import json
+
+
+def bytes_to_strings(d):
+ # Recursively convert bytes to strings in `d`.
+ if not isinstance(d, dict):
+ if isinstance(d, (tuple,list,set)):
+ v = [bytes_to_strings(x) for x in d]
+ return v
+ else:
+ if isinstance(d, bytes):
+ d = d.decode()
+ return d
+
+ result = {}
+ for k,v in d.items():
+ if isinstance(k, bytes):
+ k = k.decode()
+ if isinstance(v, dict):
+ v = bytes_to_strings(v)
+ elif isinstance(v, (tuple,list,set)):
+ v = [bytes_to_strings(x) for x in v]
+ elif isinstance(v, bytes):
+ v = v.decode()
+ result[k] = v
+ return result
+
+
+def main(request, response):
+ # This condition avoids false positives from CORS preflight checks, where the
+ # request under test may be followed immediately by a request to the same URL
+ # using a different HTTP method.
+ if b'requireOPTIONS' in request.GET and request.method != b'OPTIONS':
+ return
+
+ if b'key' in request.GET:
+ key = request.GET[b'key']
+ elif b'key' in request.POST:
+ key = request.POST[b'key']
+
+ ## Convert the key from String to UUID valid String ##
+ testId = hashlib.md5(key).hexdigest()
+
+ ## Handle the header retrieval request ##
+ if b'retrieve' in request.GET:
+ recorded_headers = request.server.stash.take(testId)
+
+ if recorded_headers is None:
+ return (204, [], b'')
+
+ return (200, [], recorded_headers)
+
+ ## Record incoming fetch metadata header value
+ else:
+ try:
+ request.server.stash.put(testId, json.dumps(bytes_to_strings(request.headers)))
+ except KeyError:
+ ## The header is already recorded or it doesn't exist
+ pass
+
+ ## Prevent the browser from caching returned responses and allow CORS ##
+ response.headers.set(b"Access-Control-Allow-Origin", b"*")
+ response.headers.set(b"Cache-Control", b"no-cache, no-store, must-revalidate")
+ response.headers.set(b"Pragma", b"no-cache")
+ response.headers.set(b"Expires", b"0")
+ if b"mime" in request.GET:
+ response.headers.set(b"Content-Type", request.GET.first(b"mime"))
+
+ return request.GET.first(b"body", request.POST.first(b"body", b""))
diff --git a/fetch/metadata/resources/redirectTestHelper.sub.js b/fetch/metadata/resources/redirectTestHelper.sub.js
index 0aec19d4ee78b1..1bfbbae70c2221 100644
--- a/fetch/metadata/resources/redirectTestHelper.sub.js
+++ b/fetch/metadata/resources/redirectTestHelper.sub.js
@@ -38,56 +38,9 @@ function downgradeRedirectTo(partialPath) {
return secureRedirectURL + encodeURIComponent(insecureTestURL + partialPath);
}
-// Helper to test the behavior of the `prefetch` Link type [1]. Because the the
-// behavior under test is optional [2], this function should only be used in
-// tests which have been denoted as "optional" [3].
-//
-// [1] https://html.spec.whatwg.org/#link-type-prefetch
-// [2] https://w3c.github.io/resource-hints/#load-and-error-events
-// [3] https://web-platform-tests.org/writing-tests/file-names.html
-function testPrefetch(nonce, testNamePrefix, urlHelperMethod, expectedResults) {
- async_test(t => {
- let key = 'prefetch' + nonce;
- let e = document.createElement('link');
- e.rel = 'prefetch';
- e.crossOrigin = 'anonymous';
- e.href = urlHelperMethod('resources/record-header.py?file=' + key) + '&simple=true';
- e.onload = t.step_func(e => {
- let expectation = { ...expectedResults };
- if (expectation['mode'] != '')
- expectation['mode'] = 'cors';
- fetch('/fetch/metadata/resources/record-header.py?retrieve=true&file=' + key)
- .then(t.step_func(response => response.text()))
- .then(t.step_func_done(text => assert_header_equals(text, expectation, testNamePrefix + ' prefetch => No headers')))
- .catch(t.unreached_func('Fetching and verifying the results should succeed.'));
- });
- e.onerror = t.unreached_func();
- document.head.appendChild(e);
- }, testNamePrefix + ' prefetch => No headers');
-}
-
// Helper to run common redirect test cases that don't require special setup on
// the test page itself.
function RunCommonRedirectTests(testNamePrefix, urlHelperMethod, expectedResults) {
- async_test(t => {
- let i = document.createElement('iframe');
- i.src = urlHelperMethod('resources/post-to-owner.py?iframe-navigation' + nonce);
- window.addEventListener('message', t.step_func(e => {
- if (e.source != i.contentWindow) {
- return;
- }
- let expectation = { ...expectedResults };
- if (expectation['mode'] != '')
- expectation['mode'] = 'navigate';
- if (expectation['dest'] == 'font')
- expectation['dest'] = 'iframe';
- assert_header_equals(e.data, expectation, testNamePrefix + ' iframe');
- t.done();
- }));
-
- document.body.appendChild(i);
- }, testNamePrefix + ' iframe');
-
async_test(t => {
let testWindow = window.open(urlHelperMethod('resources/post-to-owner.py?top-level-navigation' + nonce));
t.add_cleanup(_ => testWindow.close());
@@ -127,18 +80,6 @@ function RunCommonRedirectTests(testNamePrefix, urlHelperMethod, expectedResults
});
}, testNamePrefix + ' embed');
- promise_test(t => {
- let key = 'fetch-redirect' + nonce;
- let expectation = { ...expectedResults };
- if (expectation['mode'] != '')
- expectation['mode'] = 'cors';
- if (expectation['dest'] == 'font')
- expectation['dest'] = 'empty';
- return fetch(urlHelperMethod('resources/echo-as-json.py?' + key))
- .then(r => r.json())
- .then(j => {assert_header_equals(j, expectation, testNamePrefix + ' fetch() api');});
- }, testNamePrefix + ' fetch() api');
-
promise_test(t => {
return new Promise((resolve, reject) => {
let key = 'object-https-redirect' + nonce;
diff --git a/fetch/metadata/script.https.sub.html b/fetch/metadata/script.https.sub.html
deleted file mode 100644
index f66f663433443e..00000000000000
--- a/fetch/metadata/script.https.sub.html
+++ /dev/null
@@ -1,64 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/fetch/metadata/script.sub.html b/fetch/metadata/script.sub.html
deleted file mode 100644
index 9d6059abc177eb..00000000000000
--- a/fetch/metadata/script.sub.html
+++ /dev/null
@@ -1,49 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/fetch/metadata/serviceworker.https.sub.html b/fetch/metadata/serviceworker.https.sub.html
deleted file mode 100644
index 51bc7f29e59308..00000000000000
--- a/fetch/metadata/serviceworker.https.sub.html
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/fetch/metadata/tools/README.md b/fetch/metadata/tools/README.md
new file mode 100644
index 00000000000000..678c09ac80005a
--- /dev/null
+++ b/fetch/metadata/tools/README.md
@@ -0,0 +1,147 @@
+# Fetch Metadata test generation framework
+
+This directory defines a command-line tool for procedurally generating WPT
+tests.
+
+## Motivation
+
+Many features of the web platform involve the browser making one or more HTTP
+requests to remote servers. Only some aspects of these requests are specified
+within the standard that defines the relevant feature. Other aspects are
+specified by external standards which span the entire platform (e.g. [Fetch
+Metadata Request Headers](https://w3c.github.io/webappsec-fetch-metadata/)).
+
+This state of affairs makes it difficult to maintain test coverage for two
+reasons:
+
+- When a new feature introduces a new kind of web request, it must be verified
+ to integrate with every cross-cutting standard.
+- When a new cross-cutting standard is introduced, it must be verified to
+ integrate with every kind of web request.
+
+The tool in this directory attempts to reduce this tension. It allows
+maintainers to express instructions for making web requests in an abstract
+sense. These generic instructions can be reused by to produce a different suite
+of tests for each cross-cutting feature.
+
+When a new kind of request is proposed, a single generic template can be
+defined here. This will provide the maintainers of all cross-cutting features
+with clear instruction on how to extend their test suite with the new feature.
+
+Similarly, when a new cross-cutting feature is proposed, the authors can use
+this tool to build a test suite which spans the entire platform.
+
+## Build script
+
+To generate the Fetch Metadata tests, use the `build.sh` script, which installs
+the dependencies and runs `generate.py` with the Fetch Metadata tests
+configuration file `fetch-metadata.conf.yml`, in a virtualenv.
+
+ $ build.sh
+
+If you don't want to use virtualenv, see the next two sections.
+
+## Setup instructions
+
+- [Install Python 3](https://www.python.org/download/releases/3.0/)
+- Install Python dependencies with the following command (issued from the
+ directory containing this document):
+
+ $ pip install -r ./requirements.txt
+
+## Execution
+
+Execute the following command to generate tests, making sure to replace
+`./path/to/configuration-file.yml` with a filesystem path to a valid
+configuration file (described in greater detail below):
+
+ $ python3 ./generate.py ./path/to/configuration-file.yml
+
+## Configuration
+
+The test generation tool requires a YAML-formatted configuration file as its
+input. The file should define a dictionary with the following keys:
+
+- `templates` - a string describing the filesystem path from which template
+ files should be loaded
+- `output_directory` - a string describing the filesystem path where the
+ generated test files should be written
+- `cases` - a list of dictionaries describing how the test templates should be
+ expanded with individual subtests; each dictionary should have the following
+ keys:
+ - `all_subtests` - properties which should be defined for every expansion
+ - `common_axis` - a list of dictionaries
+ - `template_axes` - a dictionary relating template names to properties that
+ should be used when expanding that particular template
+
+Internally, the tool creates a set of "subtests" for each template. This set is
+the Cartesian product of the `common_axis` and the given template's entry in
+the `template_axes` dictionary. It uses this set of subtests to expand the
+template, creating an output file. Refer to the next section for a concrete
+example of how the expansion is performed.
+
+In general, the tool will output a single file for each template. However, the
+`filename_flags` attribute has special semantics. It is used to separate
+subtests for the same template file. This is intended to accommodate [the
+web-platform-test's filename-based
+conventions](https://web-platform-tests.org/writing-tests/file-names.html).
+
+For instance, when `.https` is present in a test file's name, the WPT test
+harness will load that test using the HTTPS protocol. Subtests which include
+the value `https` in the `filename_flags` property will be expanded using the
+appropriate template but written to a distinct file whose name includes
+`.https`.
+
+The generation tool requires that the configuration file references every
+template in the `templates` directory. Because templates and configuration
+files may be contributed by different people, this requirement ensures that
+configuration authors are aware of all available templates. Some templates may
+not be relevant for some features; in those cases, the configuration file can
+include an empty array for the template's entry in the `template_axes`
+dictionary (as in `template3.html` in the example which follows).
+
+## Expansion example
+
+In the following example configuration file, `a`, `b`, `s`, `w`, `x`, `y`, and
+`z` all represent associative arrays.
+
+```yaml
+templates: path/to/templates
+output_directory: path/to/output
+cases:
+ - every_subtest: s
+ common_axis: [a, b]
+ template_axes:
+ template1.html: [w]
+ template2.html: [x, y, z]
+ template3.html: []
+```
+
+When run with such a configuration file, the tool would generate two files,
+expanded with data as described below (where `(a, b)` represents the union of
+`a` and `b`):
+
+ template1.html: [(a, w), (b, w)]
+ template2.html: [(a, x), (b, x), (a, y), (b, y), (a, z), (b, z)]
+ template3.html: (zero tests; not expanded)
+
+## Design Considerations
+
+**Efficiency of generated output** The tool is capable of generating a large
+number of tests given a small amount of input. Naively structured, this could
+result in test suites which take large amount of time and computational
+resources to complete. The tool has been designed to help authors structure the
+generated output to reduce these resource requirements.
+
+**Literalness of generated output** Because the generated output is how most
+people will interact with the tests, it is important that it be approachable.
+This tool avoids outputting abstractions which would frustrate attempts to read
+the source code or step through its execution environment.
+
+**Simplicity** The test generation logic itself was written to be approachable.
+This makes it easier to anticipate how the tool will behave with new input, and
+it lowers the bar for others to contribute improvements.
+
+Non-goals include conciseness of template files (verbosity makes the potential
+expansions more predictable) and conciseness of generated output (verbosity
+aids in the interpretation of results).
diff --git a/fetch/metadata/tools/build.sh b/fetch/metadata/tools/build.sh
new file mode 100755
index 00000000000000..a6f5afffe0d13e
--- /dev/null
+++ b/fetch/metadata/tools/build.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env sh
+set -ex
+
+cd "${0%/*}"
+virtualenv -p python3 .virtualenv
+.virtualenv/bin/pip install -r ./requirements.txt
+cd ..
+tools/.virtualenv/bin/python ./tools/generate.py ./tools/fetch-metadata.conf.yml
diff --git a/fetch/metadata/tools/fetch-metadata.conf.yml b/fetch/metadata/tools/fetch-metadata.conf.yml
new file mode 100644
index 00000000000000..b277bcb7b53d54
--- /dev/null
+++ b/fetch/metadata/tools/fetch-metadata.conf.yml
@@ -0,0 +1,806 @@
+---
+templates: templates
+output_directory: ../generated
+cases:
+ - all_subtests:
+ expected: NULL
+ filename_flags: []
+ common_axis:
+ - headerName: sec-fetch-site
+ origins: [httpOrigin]
+ description: Not sent to non-trustworthy same-origin destination
+ - headerName: sec-fetch-site
+ origins: [httpSameSite]
+ description: Not sent to non-trustworthy same-site destination
+ - headerName: sec-fetch-site
+ origins: [httpCrossSite]
+ description: Not sent to non-trustworthy cross-site destination
+ - headerName: sec-fetch-mode
+ origins: [httpOrigin]
+ description: Not sent to non-trustworthy same-origin destination
+ - headerName: sec-fetch-mode
+ origins: [httpSameSite]
+ description: Not sent to non-trustworthy same-site destination
+ - headerName: sec-fetch-mode
+ origins: [httpCrossSite]
+ description: Not sent to non-trustworthy cross-site destination
+ - headerName: sec-fetch-dest
+ origins: [httpOrigin]
+ description: Not sent to non-trustworthy same-origin destination
+ - headerName: sec-fetch-dest
+ origins: [httpSameSite]
+ description: Not sent to non-trustworthy same-site destination
+ - headerName: sec-fetch-dest
+ origins: [httpCrossSite]
+ description: Not sent to non-trustworthy cross-site destination
+ - headerName: sec-fetch-user
+ origins: [httpOrigin]
+ description: Not sent to non-trustworthy same-origin destination
+ - headerName: sec-fetch-user
+ origins: [httpSameSite]
+ description: Not sent to non-trustworthy same-site destination
+ - headerName: sec-fetch-user
+ origins: [httpCrossSite]
+ description: Not sent to non-trustworthy cross-site destination
+ template_axes:
+ # Unused
+ appcache-manifest.sub.https.html: []
+ # The `audioWorklet` interface is only available in secure contexts
+ # https://webaudio.github.io/web-audio-api/#BaseAudioContext
+ audioworklet.https.sub.html: []
+ # Service workers are only available in secure context
+ fetch-via-serviceworker.https.sub.html: []
+ # Service workers are only available in secure context
+ serviceworker.https.sub.html: []
+
+ css-images.sub.html:
+ - filename_flags: [tentative]
+ css-font-face.sub.html:
+ - filename_flags: [tentative]
+ element-a.sub.html: [{}]
+ element-area.sub.html: [{}]
+ element-audio.sub.html: [{}]
+ element-embed.sub.html: [{}]
+ element-frame.sub.html: [{}]
+ element-iframe.sub.html: [{}]
+ element-img.sub.html:
+ - sourceAttr: src
+ - sourceAttr: srcset
+ element-img-environment-change.sub.html: [{}]
+ element-input-image.sub.html: [{}]
+ element-link-icon.sub.html: [{}]
+ element-link-prefetch.optional.sub.html: [{}]
+ element-meta-refresh.optional.sub.html: [{}]
+ element-picture.sub.html: [{}]
+ element-script.sub.html:
+ - {}
+ - elementAttrs: { type: module }
+ element-video.sub.html: [{}]
+ element-video-poster.sub.html: [{}]
+ fetch.sub.html: [{}]
+ form-submission.sub.html:
+ - method: GET
+ - method: POST
+ header-link.sub.html:
+ - rel: icon
+ - rel: stylesheet
+ header-refresh.optional.sub.html: [{}]
+ window-location.sub.html: [{}]
+ script-module-import-dynamic.sub.html: [{}]
+ script-module-import-static.sub.html: [{}]
+ svg-image.sub.html: [{}]
+ window-history.sub.html: [{}]
+ worker-dedicated-importscripts.sub.html: [{}]
+ worker-dedicated-constructor.sub.html: [{}]
+
+ # Sec-Fetch-Site - direct requests
+ - all_subtests:
+ headerName: sec-fetch-site
+ filename_flags: [https]
+ common_axis:
+ - description: Same origin
+ origins: [httpsOrigin]
+ expected: same-origin
+ - description: Cross-site
+ origins: [httpsCrossSite]
+ expected: cross-site
+ - description: Same site
+ origins: [httpsSameSite]
+ expected: same-site
+ template_axes:
+ # Unused
+ # - the request mode of all "classic" worker scripts is set to
+ # "same-origin"
+ # https://html.spec.whatwg.org/#fetch-a-classic-worker-script
+ # - the request mode of all "top-level "module" worker scripts is set to
+ # "same-origin":
+ # https://html.spec.whatwg.org/#fetch-a-single-module-script
+ worker-dedicated-constructor.sub.html: []
+
+ appcache-manifest.sub.https.html: [{}]
+ audioworklet.https.sub.html: [{}]
+ css-images.sub.html:
+ - filename_flags: [tentative]
+ css-font-face.sub.html:
+ - filename_flags: [tentative]
+ element-a.sub.html: [{}]
+ element-area.sub.html: [{}]
+ element-audio.sub.html: [{}]
+ element-embed.sub.html: [{}]
+ element-frame.sub.html: [{}]
+ element-iframe.sub.html: [{}]
+ element-img.sub.html:
+ - sourceAttr: src
+ - sourceAttr: srcset
+ element-img-environment-change.sub.html: [{}]
+ element-input-image.sub.html: [{}]
+ element-link-icon.sub.html: [{}]
+ element-link-prefetch.optional.sub.html: [{}]
+ element-meta-refresh.optional.sub.html: [{}]
+ element-picture.sub.html: [{}]
+ element-script.sub.html:
+ - {}
+ - elementAttrs: { type: module }
+ element-video.sub.html: [{}]
+ element-video-poster.sub.html: [{}]
+ fetch.sub.html: [{ init: { mode: no-cors } }]
+ fetch-via-serviceworker.https.sub.html: [{ init: { mode: no-cors } }]
+ form-submission.sub.html:
+ - method: GET
+ - method: POST
+ header-link.sub.html:
+ - rel: icon
+ - rel: stylesheet
+ header-refresh.optional.sub.html: [{}]
+ window-location.sub.html: [{}]
+ script-module-import-dynamic.sub.html: [{}]
+ script-module-import-static.sub.html: [{}]
+ serviceworker.https.sub.html: [{}]
+ svg-image.sub.html: [{}]
+ window-history.sub.html: [{}]
+ worker-dedicated-importscripts.sub.html: [{}]
+
+ # Sec-Fetch-Site - redirection from HTTP
+ - all_subtests:
+ headerName: sec-fetch-site
+ filename_flags: []
+ common_axis:
+ - description: HTTPS downgrade (header not sent)
+ origins: [httpsOrigin, httpOrigin]
+ expected: NULL
+ - description: HTTPS upgrade
+ origins: [httpOrigin, httpsOrigin]
+ expected: cross-site
+ - description: HTTPS downgrade-upgrade
+ origins: [httpsOrigin, httpOrigin, httpsOrigin]
+ expected: cross-site
+ template_axes:
+ # Unused
+ # The `audioWorklet` interface is only available in secure contexts
+ # https://webaudio.github.io/web-audio-api/#BaseAudioContext
+ audioworklet.https.sub.html: []
+ # Service workers are only available in secure context
+ fetch-via-serviceworker.https.sub.html: []
+ # Service workers' redirect mode is "error"
+ serviceworker.https.sub.html: []
+ # Interstitial locations in an HTTP redirect chain are not added to the
+ # session history, so these requests cannot be initiated using the
+ # History API.
+ window-history.sub.html: []
+ # Unused
+ # - the request mode of all "classic" worker scripts is set to
+ # "same-origin"
+ # https://html.spec.whatwg.org/#fetch-a-classic-worker-script
+ # - the request mode of all "top-level "module" worker scripts is set to
+ # "same-origin":
+ # https://html.spec.whatwg.org/#fetch-a-single-module-script
+ worker-dedicated-constructor.sub.html: []
+
+ appcache-manifest.sub.https.html: [{}]
+ css-images.sub.html:
+ - filename_flags: [tentative]
+ css-font-face.sub.html:
+ - filename_flags: [tentative]
+ element-a.sub.html: [{}]
+ element-area.sub.html: [{}]
+ element-audio.sub.html: [{}]
+ element-embed.sub.html: [{}]
+ element-frame.sub.html: [{}]
+ element-iframe.sub.html: [{}]
+ element-img.sub.html:
+ - sourceAttr: src
+ - sourceAttr: srcset
+ element-img-environment-change.sub.html: [{}]
+ element-input-image.sub.html: [{}]
+ element-link-icon.sub.html: [{}]
+ element-link-prefetch.optional.sub.html: [{}]
+ element-meta-refresh.optional.sub.html: [{}]
+ element-picture.sub.html: [{}]
+ element-script.sub.html:
+ - {}
+ - elementAttrs: { type: module }
+ element-video.sub.html: [{}]
+ element-video-poster.sub.html: [{}]
+ fetch.sub.html: [{}]
+ form-submission.sub.html:
+ - method: GET
+ - method: POST
+ header-link.sub.html:
+ - rel: icon
+ - rel: stylesheet
+ header-refresh.optional.sub.html: [{}]
+ window-location.sub.html: [{}]
+ script-module-import-dynamic.sub.html: [{}]
+ script-module-import-static.sub.html: [{}]
+ svg-image.sub.html: [{}]
+ worker-dedicated-importscripts.sub.html: [{}]
+
+ # Sec-Fetch-Site - redirection from HTTPS
+ - all_subtests:
+ headerName: sec-fetch-site
+ filename_flags: [https]
+ common_axis:
+ - description: Same-Origin -> Cross-Site -> Same-Origin redirect
+ origins: [httpsOrigin, httpsCrossSite, httpsOrigin]
+ expected: cross-site
+ - description: Same-Origin -> Same-Site -> Same-Origin redirect
+ origins: [httpsOrigin, httpsSameSite, httpsOrigin]
+ expected: same-site
+ - description: Cross-Site -> Same Origin
+ origins: [httpsCrossSite, httpsOrigin]
+ expected: cross-site
+ - description: Cross-Site -> Same-Site
+ origins: [httpsCrossSite, httpsSameSite]
+ expected: cross-site
+ - description: Cross-Site -> Cross-Site
+ origins: [httpsCrossSite, httpsCrossSite]
+ expected: cross-site
+ - description: Same-Origin -> Same Origin
+ origins: [httpsOrigin, httpsOrigin]
+ expected: same-origin
+ - description: Same-Origin -> Same-Site
+ origins: [httpsOrigin, httpsSameSite]
+ expected: same-site
+ - description: Same-Origin -> Cross-Site
+ origins: [httpsOrigin, httpsCrossSite]
+ expected: cross-site
+ - description: Same-Site -> Same Origin
+ origins: [httpsSameSite, httpsOrigin]
+ expected: same-site
+ - description: Same-Site -> Same-Site
+ origins: [httpsSameSite, httpsSameSite]
+ expected: same-site
+ - description: Same-Site -> Cross-Site
+ origins: [httpsSameSite, httpsCrossSite]
+ expected: cross-site
+ template_axes:
+ # Service Workers' redirect mode is "error"
+ serviceworker.https.sub.html: []
+ # Interstitial locations in an HTTP redirect chain are not added to the
+ # session history, so these requests cannot be initiated using the
+ # History API.
+ window-history.sub.html: []
+ # Unused
+ # - the request mode of all "classic" worker scripts is set to
+ # "same-origin"
+ # https://html.spec.whatwg.org/#fetch-a-classic-worker-script
+ # - the request mode of all "top-level "module" worker scripts is set to
+ # "same-origin":
+ # https://html.spec.whatwg.org/#fetch-a-single-module-script
+ worker-dedicated-constructor.sub.html: []
+
+ appcache-manifest.sub.https.html: [{}]
+ audioworklet.https.sub.html: [{}]
+ css-images.sub.html:
+ - filename_flags: [tentative]
+ css-font-face.sub.html:
+ - filename_flags: [tentative]
+ element-a.sub.html: [{}]
+ element-area.sub.html: [{}]
+ element-audio.sub.html: [{}]
+ element-embed.sub.html: [{}]
+ element-frame.sub.html: [{}]
+ element-iframe.sub.html: [{}]
+ element-img.sub.html:
+ - sourceAttr: src
+ - sourceAttr: srcset
+ element-img-environment-change.sub.html: [{}]
+ element-input-image.sub.html: [{}]
+ element-link-icon.sub.html: [{}]
+ element-link-prefetch.optional.sub.html: [{}]
+ element-meta-refresh.optional.sub.html: [{}]
+ element-picture.sub.html: [{}]
+ element-script.sub.html:
+ - {}
+ - elementAttrs: { type: module }
+ element-video.sub.html: [{}]
+ element-video-poster.sub.html: [{}]
+ fetch.sub.html: [{ init: { mode: no-cors } }]
+ fetch-via-serviceworker.https.sub.html: [{ init: { mode: no-cors } }]
+ form-submission.sub.html:
+ - method: GET
+ - method: POST
+ header-link.sub.html:
+ - rel: icon
+ - rel: stylesheet
+ header-refresh.optional.sub.html: [{}]
+ window-location.sub.html: [{}]
+ script-module-import-dynamic.sub.html: [{}]
+ script-module-import-static.sub.html: [{}]
+ svg-image.sub.html: [{}]
+ worker-dedicated-importscripts.sub.html: [{}]
+
+ # Sec-Fetch-Site - redirection with mixed content
+ # These tests verify the effect that redirection has on the request's "site".
+ # The initial request must be made to a resource that is "same-site" with its
+ # origin. This avoids false positives because if the request were made to a
+ # cross-site resource, the value of "cross-site" would be assigned regardless
+ # of the subseqent redirection.
+ #
+ # Because these conditions necessarily warrant mixed content, only templates
+ # which can be configured to allow mixed content [1] can be used.
+ #
+ # [1] https://w3c.github.io/webappsec-mixed-content/#should-block-fetch
+
+ - common_axis:
+ - description: HTTPS downgrade-upgrade
+ headerName: sec-fetch-site
+ origins: [httpsOrigin, httpOrigin, httpsOrigin]
+ expected: cross-site
+ filename_flags: [https]
+ template_axes:
+ # Mixed Content considers only a small subset of requests as
+ # "optionally-blockable." These are the only requests that can be tested
+ # for the "downgrade-upgrade" scenario, so all other templates must be
+ # explicitly ignored.
+ audioworklet.https.sub.html: []
+ css-font-face.sub.html: []
+ element-embed.sub.html: []
+ element-frame.sub.html: []
+ element-iframe.sub.html: []
+ element-img-environment-change.sub.html: []
+ element-link-icon.sub.html: []
+ element-link-prefetch.optional.sub.html: []
+ element-picture.sub.html: []
+ element-script.sub.html: []
+ fetch.sub.html: []
+ fetch-via-serviceworker.https.sub.html: []
+ header-link.sub.html: []
+ script-module-import-static.sub.html: []
+ script-module-import-dynamic.sub.html: []
+ # Service Workers' redirect mode is "error"
+ serviceworker.https.sub.html: []
+ # Interstitial locations in an HTTP redirect chain are not added to the
+ # session history, so these requests cannot be initiated using the
+ # History API.
+ window-history.sub.html: []
+ worker-dedicated-constructor.sub.html: []
+ worker-dedicated-importscripts.sub.html: []
+ # Avoid duplicate subtest for 'sec-fetch-site - HTTPS downgrade-upgrade'
+ appcache-manifest.sub.https.html: []
+ css-images.sub.html:
+ - filename_flags: [tentative]
+ element-a.sub.html: [{}]
+ element-area.sub.html: [{}]
+ element-audio.sub.html: [{}]
+ element-img.sub.html:
+ # srcset omitted because it is not "optionally-blockable"
+ # https://w3c.github.io/webappsec-mixed-content/#category-optionally-blockable
+ - sourceAttr: src
+ element-input-image.sub.html: [{}]
+ element-meta-refresh.optional.sub.html: [{}]
+ element-video.sub.html: [{}]
+ element-video-poster.sub.html: [{}]
+ form-submission.sub.html:
+ - method: GET
+ - method: POST
+ header-refresh.optional.sub.html: [{}]
+ svg-image.sub.html: [{}]
+ window-location.sub.html: [{}]
+
+ # Sec-Fetch-Mode
+ # These tests are served over HTTPS so the induced requests will be both
+ # same-origin with the document [1] and a potentially-trustworthy URL [2].
+ #
+ # [1] https://html.spec.whatwg.org/multipage/origin.html#same-origin
+ # [2] https://w3c.github.io/webappsec-secure-contexts/#potentially-trustworthy-url
+ - common_axis:
+ - headerName: sec-fetch-mode
+ filename_flags: [https]
+ origins: []
+ template_axes:
+ appcache-manifest.sub.https.html:
+ - expected: no-cors
+ audioworklet.https.sub.html:
+ # https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-single-module-script
+ - expected: cors
+ css-images.sub.html:
+ - expected: no-cors
+ filename_flags: [tentative]
+ css-font-face.sub.html:
+ - expected: cors
+ filename_flags: [tentative]
+ element-a.sub.html:
+ - expected: navigate
+ # https://html.spec.whatwg.org/multipage/links.html#downloading-hyperlinks
+ - elementAttrs: {download: ''}
+ expected: no-cors
+ element-area.sub.html:
+ - expected: navigate
+ # https://html.spec.whatwg.org/multipage/links.html#downloading-hyperlinks
+ - elementAttrs: {download: ''}
+ expected: no-cors
+ element-audio.sub.html:
+ - expected: no-cors
+ - expected: cors
+ elementAttrs: { crossorigin: '' }
+ - expected: cors
+ elementAttrs: { crossorigin: anonymous }
+ - expected: cors
+ elementAttrs: { crossorigin: use-credentials }
+ element-embed.sub.html:
+ - expected: no-cors
+ element-frame.sub.html:
+ - expected: navigate
+ element-iframe.sub.html:
+ - expected: navigate
+ element-img.sub.html:
+ - sourceAttr: src
+ expected: no-cors
+ - sourceAttr: src
+ expected: cors
+ elementAttrs: { crossorigin: '' }
+ - sourceAttr: src
+ expected: cors
+ elementAttrs: { crossorigin: anonymous }
+ - sourceAttr: src
+ expected: cors
+ elementAttrs: { crossorigin: use-credentials }
+ - sourceAttr: srcset
+ expected: no-cors
+ - sourceAttr: srcset
+ expected: cors
+ elementAttrs: { crossorigin: '' }
+ - sourceAttr: srcset
+ expected: cors
+ elementAttrs: { crossorigin: anonymous }
+ - sourceAttr: srcset
+ expected: cors
+ elementAttrs: { crossorigin: use-credentials }
+ element-img-environment-change.sub.html:
+ - expected: no-cors
+ - expected: cors
+ elementAttrs: { crossorigin: '' }
+ - expected: cors
+ elementAttrs: { crossorigin: anonymous }
+ - expected: cors
+ elementAttrs: { crossorigin: use-credentials }
+ element-input-image.sub.html:
+ - expected: no-cors
+ element-link-icon.sub.html:
+ - expected: no-cors
+ - expected: cors
+ elementAttrs: { crossorigin: '' }
+ - expected: cors
+ elementAttrs: { crossorigin: anonymous }
+ - expected: cors
+ elementAttrs: { crossorigin: use-credentials }
+ element-link-prefetch.optional.sub.html:
+ - expected: no-cors
+ - expected: cors
+ elementAttrs: { crossorigin: '' }
+ - expected: cors
+ elementAttrs: { crossorigin: anonymous }
+ - expected: cors
+ elementAttrs: { crossorigin: use-credentials }
+ element-meta-refresh.optional.sub.html:
+ - expected: navigate
+ element-picture.sub.html:
+ - expected: no-cors
+ - expected: cors
+ elementAttrs: { crossorigin: '' }
+ - expected: cors
+ elementAttrs: { crossorigin: anonymous }
+ - expected: cors
+ elementAttrs: { crossorigin: use-credentials }
+ element-script.sub.html:
+ - expected: no-cors
+ - expected: cors
+ elementAttrs: { type: module }
+ - expected: cors
+ elementAttrs: { crossorigin: '' }
+ - expected: cors
+ elementAttrs: { crossorigin: anonymous }
+ - expected: cors
+ elementAttrs: { crossorigin: use-credentials }
+ element-video.sub.html:
+ - expected: no-cors
+ - expected: cors
+ elementAttrs: { crossorigin: '' }
+ - expected: cors
+ elementAttrs: { crossorigin: anonymous }
+ - expected: cors
+ elementAttrs: { crossorigin: use-credentials }
+ element-video-poster.sub.html:
+ - expected: no-cors
+ fetch.sub.html:
+ - expected: cors
+ - expected: cors
+ init: { mode: cors }
+ - expected: no-cors
+ init: { mode: no-cors }
+ - expected: same-origin
+ init: { mode: same-origin }
+ fetch-via-serviceworker.https.sub.html:
+ - expected: cors
+ - expected: cors
+ init: { mode: cors }
+ - expected: no-cors
+ init: { mode: no-cors }
+ - expected: same-origin
+ init: { mode: same-origin }
+ form-submission.sub.html:
+ - method: GET
+ expected: navigate
+ - method: POST
+ expected: navigate
+ header-link.sub.html:
+ - rel: icon
+ expected: no-cors
+ - rel: stylesheet
+ expected: no-cors
+ header-refresh.optional.sub.html:
+ - expected: navigate
+ window-history.sub.html:
+ - expected: navigate
+ window-location.sub.html:
+ - expected: navigate
+ script-module-import-dynamic.sub.html:
+ - expected: cors
+ script-module-import-static.sub.html:
+ - expected: cors
+ # https://svgwg.org/svg2-draft/linking.html#processingURL-fetch
+ svg-image.sub.html:
+ - expected: no-cors
+ - expected: cors
+ elementAttrs: { crossorigin: '' }
+ - expected: cors
+ elementAttrs: { crossorigin: anonymous }
+ - expected: cors
+ elementAttrs: { crossorigin: use-credentials }
+ serviceworker.https.sub.html:
+ - expected: same-origin
+ options: { type: 'classic' }
+ # https://github.com/whatwg/html/pull/5875
+ - expected: same-origin
+ worker-dedicated-constructor.sub.html:
+ - expected: same-origin
+ - options: { type: module }
+ expected: same-origin
+ worker-dedicated-importscripts.sub.html:
+ - expected: no-cors
+
+ # Sec-Fetch-Dest
+ - common_axis:
+ - headerName: sec-fetch-dest
+ filename_flags: [https]
+ origins: []
+ template_axes:
+ appcache-manifest.sub.https.html:
+ - expected: empty
+ audioworklet.https.sub.html:
+ # https://github.com/WebAudio/web-audio-api/issues/2203
+ - expected: audioworklet
+ css-images.sub.html:
+ - expected: image
+ filename_flags: [tentative]
+ css-font-face.sub.html:
+ - expected: font
+ filename_flags: [tentative]
+ element-a.sub.html:
+ - expected: document
+ # https://html.spec.whatwg.org/multipage/links.html#downloading-hyperlinks
+ - elementAttrs: {download: ''}
+ expected: empty
+ element-area.sub.html:
+ - expected: document
+ # https://html.spec.whatwg.org/multipage/links.html#downloading-hyperlinks
+ - elementAttrs: {download: ''}
+ expected: empty
+ element-audio.sub.html:
+ - expected: audio
+ element-embed.sub.html:
+ - expected: embed
+ element-frame.sub.html:
+ # https://github.com/whatwg/html/pull/4976
+ - expected: frame
+ element-iframe.sub.html:
+ # https://github.com/whatwg/html/pull/4976
+ - expected: iframe
+ element-img.sub.html:
+ - sourceAttr: src
+ expected: image
+ - sourceAttr: srcset
+ expected: image
+ element-img-environment-change.sub.html:
+ - expected: image
+ element-input-image.sub.html:
+ - expected: image
+ element-link-icon.sub.html:
+ - expected: empty
+ element-link-prefetch.optional.sub.html:
+ - expected: empty
+ - elementAttrs: { as: audio }
+ expected: audio
+ - elementAttrs: { as: document }
+ expected: document
+ - elementAttrs: { as: embed }
+ expected: embed
+ - elementAttrs: { as: fetch }
+ expected: fetch
+ - elementAttrs: { as: font }
+ expected: font
+ - elementAttrs: { as: image }
+ expected: image
+ - elementAttrs: { as: object }
+ expected: object
+ - elementAttrs: { as: script }
+ expected: script
+ - elementAttrs: { as: style }
+ expected: style
+ - elementAttrs: { as: track }
+ expected: track
+ - elementAttrs: { as: video }
+ expected: video
+ - elementAttrs: { as: worker }
+ expected: worker
+ element-meta-refresh.optional.sub.html:
+ - expected: document
+ element-picture.sub.html:
+ - expected: image
+ element-script.sub.html:
+ - expected: script
+ element-video.sub.html:
+ - expected: video
+ element-video-poster.sub.html:
+ - expected: image
+ fetch.sub.html:
+ - expected: empty
+ fetch-via-serviceworker.https.sub.html:
+ - expected: empty
+ form-submission.sub.html:
+ - method: GET
+ expected: document
+ - method: POST
+ expected: document
+ header-link.sub.html:
+ - rel: icon
+ expected: empty
+ - rel: stylesheet
+ filename_flags: [tentative]
+ expected: style
+ header-refresh.optional.sub.html:
+ - expected: document
+ window-history.sub.html:
+ - expected: document
+ window-location.sub.html:
+ - expected: document
+ script-module-import-dynamic.sub.html:
+ - expected: script
+ script-module-import-static.sub.html:
+ - expected: script
+ serviceworker.https.sub.html:
+ - expected: serviceworker
+ # Implemented as "image" in Chromium and Firefox, but specified as
+ # "empty"
+ # https://github.com/w3c/svgwg/issues/782
+ svg-image.sub.html:
+ - expected: empty
+ worker-dedicated-constructor.sub.html:
+ - expected: worker
+ - options: { type: module }
+ expected: worker
+ worker-dedicated-importscripts.sub.html:
+ - expected: script
+
+ # Sec-Fetch-User
+ - common_axis:
+ - headerName: sec-fetch-user
+ filename_flags: [https]
+ origins: []
+ template_axes:
+ appcache-manifest.sub.https.html:
+ - expected: NULL
+ audioworklet.https.sub.html:
+ - expected: NULL
+ css-images.sub.html:
+ - expected: NULL
+ filename_flags: [tentative]
+ css-font-face.sub.html:
+ - expected: NULL
+ filename_flags: [tentative]
+ element-a.sub.html:
+ - expected: NULL
+ - userActivated: TRUE
+ expected: ?1
+ element-area.sub.html:
+ - expected: NULL
+ - userActivated: TRUE
+ expected: ?1
+ element-audio.sub.html:
+ - expected: NULL
+ element-embed.sub.html:
+ - expected: NULL
+ element-frame.sub.html:
+ - expected: NULL
+ - userActivated: TRUE
+ expected: ?1
+ element-iframe.sub.html:
+ - expected: NULL
+ - userActivated: TRUE
+ expected: ?1
+ element-img.sub.html:
+ - sourceAttr: src
+ expected: NULL
+ - sourceAttr: srcset
+ expected: NULL
+ element-img-environment-change.sub.html:
+ - expected: NULL
+ element-input-image.sub.html:
+ - expected: NULL
+ element-link-icon.sub.html:
+ - expected: NULL
+ element-link-prefetch.optional.sub.html:
+ - expected: NULL
+ element-meta-refresh.optional.sub.html:
+ - expected: NULL
+ element-picture.sub.html:
+ - expected: NULL
+ element-script.sub.html:
+ - expected: NULL
+ element-video.sub.html:
+ - expected: NULL
+ element-video-poster.sub.html:
+ - expected: NULL
+ fetch.sub.html:
+ - expected: NULL
+ fetch-via-serviceworker.https.sub.html:
+ - expected: NULL
+ form-submission.sub.html:
+ - method: GET
+ expected: NULL
+ - method: GET
+ userActivated: TRUE
+ expected: ?1
+ - method: POST
+ expected: NULL
+ - method: POST
+ userActivated: TRUE
+ expected: ?1
+ header-link.sub.html:
+ - rel: icon
+ expected: NULL
+ - rel: stylesheet
+ expected: NULL
+ header-refresh.optional.sub.html:
+ - expected: NULL
+ window-history.sub.html:
+ - expected: NULL
+ window-location.sub.html:
+ - expected: NULL
+ - userActivated: TRUE
+ expected: ?1
+ script-module-import-dynamic.sub.html:
+ - expected: NULL
+ script-module-import-static.sub.html:
+ - expected: NULL
+ serviceworker.https.sub.html:
+ - expected: NULL
+ svg-image.sub.html:
+ - expected: NULL
+ worker-dedicated-constructor.sub.html:
+ - expected: NULL
+ - options: { type: module }
+ expected: NULL
+ worker-dedicated-importscripts.sub.html:
+ - expected: NULL
diff --git a/fetch/metadata/tools/generate.py b/fetch/metadata/tools/generate.py
new file mode 100755
index 00000000000000..9475ab3e2b3fb9
--- /dev/null
+++ b/fetch/metadata/tools/generate.py
@@ -0,0 +1,195 @@
+#!/usr/bin/env python3
+
+import itertools
+import os
+
+import jinja2
+import yaml
+
+HERE = os.path.abspath(os.path.dirname(__file__))
+PROJECT_ROOT = os.path.join(HERE, '..', '..', '..')
+
+def find_templates(starting_directory):
+ for directory, subdirectories, file_names in os.walk(starting_directory):
+ for file_name in file_names:
+ if file_name.startswith('.'):
+ continue
+ yield file_name, os.path.join(directory, file_name)
+
+def test_name(directory, template_name, subtest_flags):
+ '''
+ Create a test name based on a template and the WPT file name flags [1]
+ required for a given subtest. This name is used to determine how subtests
+ may be grouped together. In order to promote grouping, the combination uses
+ a few aspects of how file name flags are interpreted:
+
+ - repeated flags have no effect, so duplicates are removed
+ - flag sequence does not matter, so flags are consistently sorted
+
+ directory | template_name | subtest_flags | result
+ ----------|------------------|-----------------|-------
+ cors | image.html | [] | cors/image.html
+ cors | image.https.html | [] | cors/image.https.html
+ cors | image.html | [https] | cors/image.https.html
+ cors | image.https.html | [https] | cors/image.https.html
+ cors | image.https.html | [https] | cors/image.https.html
+ cors | image.sub.html | [https] | cors/image.https.sub.html
+ cors | image.https.html | [sub] | cors/image.https.sub.html
+
+ [1] docs/writing-tests/file-names.md
+ '''
+ template_name_parts = template_name.split('.')
+ flags = set(subtest_flags) | set(template_name_parts[1:-1])
+ test_name_parts = (
+ [template_name_parts[0]] +
+ sorted(flags) +
+ [template_name_parts[-1]]
+ )
+ return os.path.join(directory, '.'.join(test_name_parts))
+
+def merge(a, b):
+ if type(a) != type(b):
+ raise Exception('Cannot merge disparate types')
+ if type(a) == list:
+ return a + b
+ if type(a) == dict:
+ merged = {}
+
+ for key in a:
+ if key in b:
+ merged[key] = merge(a[key], b[key])
+ else:
+ merged[key] = a[key]
+
+ for key in b:
+ if not key in a:
+ merged[key] = b[key]
+
+ return merged
+
+ raise Exception('Cannot merge {} type'.format(type(a).__name__))
+
+def product(a, b):
+ '''
+ Given two lists of objects, compute their Cartesian product by merging the
+ elements together. For example,
+
+ product(
+ [{'a': 1}, {'b': 2}],
+ [{'c': 3}, {'d': 4}, {'e': 5}]
+ )
+
+ returns the following list:
+
+ [
+ {'a': 1, 'c': 3},
+ {'a': 1, 'd': 4},
+ {'a': 1, 'e': 5},
+ {'b': 2, 'c': 3},
+ {'b': 2, 'd': 4},
+ {'b': 2, 'e': 5}
+ ]
+ '''
+ result = []
+
+ for a_object in a:
+ for b_object in b:
+ result.append(merge(a_object, b_object))
+
+ return result
+
+def make_provenance(project_root, cases, template):
+ return '\n'.join([
+ 'This test was procedurally generated. Please do not modify it directly.',
+ 'Sources:',
+ '- {}'.format(os.path.relpath(cases, project_root)),
+ '- {}'.format(os.path.relpath(template, project_root))
+ ])
+
+def collection_filter(obj, title):
+ if not obj:
+ return 'no {}'.format(title)
+
+ members = []
+ for name, value in obj.items():
+ if value is '':
+ members.append(name)
+ else:
+ members.append('{}={}'.format(name, value))
+
+ return '{}: {}'.format(title, ', '.join(members))
+
+def pad_filter(value, side, padding):
+ if not value:
+ return ''
+ if side == 'start':
+ return padding + value
+
+ return value + padding
+
+def main(config_file):
+ with open(config_file, 'r') as handle:
+ config = yaml.safe_load(handle.read())
+
+ templates_directory = os.path.normpath(
+ os.path.join(os.path.dirname(config_file), config['templates'])
+ )
+
+ environment = jinja2.Environment(
+ variable_start_string='[%',
+ variable_end_string='%]'
+ )
+ environment.filters['collection'] = collection_filter
+ environment.filters['pad'] = pad_filter
+ templates = {}
+ subtests = {}
+
+ for template_name, path in find_templates(templates_directory):
+ subtests[template_name] = []
+ with open(path, 'r') as handle:
+ templates[template_name] = environment.from_string(handle.read())
+
+ for case in config['cases']:
+ unused_templates = set(templates) - set(case['template_axes'])
+
+ # This warning is intended to help authors avoid mistakenly omitting
+ # templates. It can be silenced by extending the`template_axes`
+ # dictionary with an empty list for templates which are intentionally
+ # unused.
+ if unused_templates:
+ print(
+ 'Warning: case does not reference the following templates:'
+ )
+ print('\n'.join('- {}'.format(name) for name in unused_templates))
+
+ common_axis = product(
+ case['common_axis'], [case.get('all_subtests', {})]
+ )
+
+ for template_name, template_axis in case['template_axes'].items():
+ subtests[template_name].extend(product(common_axis, template_axis))
+
+ for template_name, template in templates.items():
+ provenance = make_provenance(
+ PROJECT_ROOT,
+ config_file,
+ os.path.join(templates_directory, template_name)
+ )
+ get_filename = lambda subtest: test_name(
+ config['output_directory'],
+ template_name,
+ subtest['filename_flags']
+ )
+ subtests_by_filename = itertools.groupby(
+ sorted(subtests[template_name], key=get_filename),
+ key=get_filename
+ )
+ for filename, some_subtests in subtests_by_filename:
+ with open(filename, 'w') as handle:
+ handle.write(templates[template_name].render(
+ subtests=list(some_subtests),
+ provenance=provenance
+ ) + '\n')
+
+if __name__ == '__main__':
+ main('fetch-metadata.conf.yml')
diff --git a/fetch/metadata/tools/requirements.txt b/fetch/metadata/tools/requirements.txt
new file mode 100644
index 00000000000000..c28e0a8b96f501
--- /dev/null
+++ b/fetch/metadata/tools/requirements.txt
@@ -0,0 +1,3 @@
+Jinja2==2.10.3
+MarkupSafe==1.1.1
+PyYAML==5.2
diff --git a/fetch/metadata/tools/templates/appcache-manifest.sub.https.html b/fetch/metadata/tools/templates/appcache-manifest.sub.https.html
new file mode 100644
index 00000000000000..0dfc084f2e3442
--- /dev/null
+++ b/fetch/metadata/tools/templates/appcache-manifest.sub.https.html
@@ -0,0 +1,63 @@
+
+
+
+
+ HTTP headers on request for Appcache manifest
+
+
+
+
+
+
+
diff --git a/fetch/metadata/tools/templates/audioworklet.https.sub.html b/fetch/metadata/tools/templates/audioworklet.https.sub.html
new file mode 100644
index 00000000000000..7be309c50685ec
--- /dev/null
+++ b/fetch/metadata/tools/templates/audioworklet.https.sub.html
@@ -0,0 +1,53 @@
+
+
+
+
+ HTTP headers on request for AudioWorklet module
+
+
+
+
+
+
+
+
+
diff --git a/fetch/metadata/tools/templates/css-font-face.sub.html b/fetch/metadata/tools/templates/css-font-face.sub.html
new file mode 100644
index 00000000000000..94b33f4e6b004a
--- /dev/null
+++ b/fetch/metadata/tools/templates/css-font-face.sub.html
@@ -0,0 +1,60 @@
+
+
+
+
+ HTTP headers on request for CSS font-face
+
+
+
+
+
+
+
diff --git a/fetch/metadata/tools/templates/css-images.sub.html b/fetch/metadata/tools/templates/css-images.sub.html
new file mode 100644
index 00000000000000..e394f9f5b06256
--- /dev/null
+++ b/fetch/metadata/tools/templates/css-images.sub.html
@@ -0,0 +1,137 @@
+
+
+
+
+ {%- if subtests|length > 10 %}
+
+ {%- endif %}
+ HTTP headers on request for CSS image-accepting properties
+
+
+
+
+
+
+
diff --git a/fetch/metadata/tools/templates/element-a.sub.html b/fetch/metadata/tools/templates/element-a.sub.html
new file mode 100644
index 00000000000000..2bd8e8a40e08e4
--- /dev/null
+++ b/fetch/metadata/tools/templates/element-a.sub.html
@@ -0,0 +1,72 @@
+
+
+
+
+ {%- if subtests|length > 10 %}
+
+ {%- endif %}
+ HTTP headers on request for HTML "a" element navigation
+
+
+ {%- if subtests|selectattr('userActivated')|list %}
+
+
+ {%- endif %}
+
+
+
+
+
diff --git a/fetch/metadata/tools/templates/element-area.sub.html b/fetch/metadata/tools/templates/element-area.sub.html
new file mode 100644
index 00000000000000..0cef5b2294118a
--- /dev/null
+++ b/fetch/metadata/tools/templates/element-area.sub.html
@@ -0,0 +1,72 @@
+
+
+
+
+ {%- if subtests|length > 10 %}
+
+ {%- endif %}
+ HTTP headers on request for HTML "area" element navigation
+
+
+ {%- if subtests|selectattr('userActivated')|list %}
+
+
+ {%- endif %}
+
+
+
+
+
diff --git a/fetch/metadata/tools/templates/element-audio.sub.html b/fetch/metadata/tools/templates/element-audio.sub.html
new file mode 100644
index 00000000000000..92bc22198e72a6
--- /dev/null
+++ b/fetch/metadata/tools/templates/element-audio.sub.html
@@ -0,0 +1,51 @@
+
+
+
+
+ HTTP headers on request for HTML "audio" element source
+
+
+
+
+
+
+
diff --git a/fetch/metadata/tools/templates/element-embed.sub.html b/fetch/metadata/tools/templates/element-embed.sub.html
new file mode 100644
index 00000000000000..18ce09e5fdc6e1
--- /dev/null
+++ b/fetch/metadata/tools/templates/element-embed.sub.html
@@ -0,0 +1,54 @@
+
+
+
+
+ HTTP headers on request for HTML "embed" element source
+
+
+
+
+
+
+
diff --git a/fetch/metadata/tools/templates/element-frame.sub.html b/fetch/metadata/tools/templates/element-frame.sub.html
new file mode 100644
index 00000000000000..ce90171779ab07
--- /dev/null
+++ b/fetch/metadata/tools/templates/element-frame.sub.html
@@ -0,0 +1,62 @@
+
+
+
+
+ HTTP headers on request for HTML "frame" element source
+
+
+ {%- if subtests|selectattr('userActivated')|list %}
+
+
+ {%- endif %}
+
+
+
+
+
diff --git a/fetch/metadata/tools/templates/element-iframe.sub.html b/fetch/metadata/tools/templates/element-iframe.sub.html
new file mode 100644
index 00000000000000..43a632a15cd3cb
--- /dev/null
+++ b/fetch/metadata/tools/templates/element-iframe.sub.html
@@ -0,0 +1,62 @@
+
+
+
+
+ HTTP headers on request for HTML "frame" element source
+
+
+ {%- if subtests|selectattr('userActivated')|list %}
+
+
+ {%- endif %}
+
+
+
+
+
diff --git a/fetch/metadata/tools/templates/element-img-environment-change.sub.html b/fetch/metadata/tools/templates/element-img-environment-change.sub.html
new file mode 100644
index 00000000000000..5a65114f184a55
--- /dev/null
+++ b/fetch/metadata/tools/templates/element-img-environment-change.sub.html
@@ -0,0 +1,78 @@
+
+
+
+
+ HTTP headers on image request triggered by change to environment
+
+
+
+
+
+
+
diff --git a/fetch/metadata/tools/templates/element-img.sub.html b/fetch/metadata/tools/templates/element-img.sub.html
new file mode 100644
index 00000000000000..1dac5843ecda0c
--- /dev/null
+++ b/fetch/metadata/tools/templates/element-img.sub.html
@@ -0,0 +1,52 @@
+
+
+
+
+ HTTP headers on request for HTML "img" element source
+
+
+
+
+
+
+
diff --git a/fetch/metadata/tools/templates/element-input-image.sub.html b/fetch/metadata/tools/templates/element-input-image.sub.html
new file mode 100644
index 00000000000000..3c50008433c385
--- /dev/null
+++ b/fetch/metadata/tools/templates/element-input-image.sub.html
@@ -0,0 +1,48 @@
+
+
+
+
+ HTTP headers on request for HTML "input" element with type="button"
+
+
+
+
+
+
+
diff --git a/fetch/metadata/tools/templates/element-link-icon.sub.html b/fetch/metadata/tools/templates/element-link-icon.sub.html
new file mode 100644
index 00000000000000..18ce12a6898711
--- /dev/null
+++ b/fetch/metadata/tools/templates/element-link-icon.sub.html
@@ -0,0 +1,75 @@
+
+
+
+
+ {%- if subtests|length > 10 %}
+
+ {%- endif %}
+ HTTP headers on request for HTML "link" element with rel="icon"
+
+
+
+
+
+
+
diff --git a/fetch/metadata/tools/templates/element-link-prefetch.optional.sub.html b/fetch/metadata/tools/templates/element-link-prefetch.optional.sub.html
new file mode 100644
index 00000000000000..59d677d8d67f57
--- /dev/null
+++ b/fetch/metadata/tools/templates/element-link-prefetch.optional.sub.html
@@ -0,0 +1,71 @@
+
+
+
+
+ {%- if subtests|length > 10 %}
+
+ {%- endif %}
+ HTTP headers on request for HTML "link" element with rel="prefetch"
+
+
+
+
+
+
+
+
diff --git a/fetch/metadata/tools/templates/element-meta-refresh.optional.sub.html b/fetch/metadata/tools/templates/element-meta-refresh.optional.sub.html
new file mode 100644
index 00000000000000..5a8d8f8ecd270f
--- /dev/null
+++ b/fetch/metadata/tools/templates/element-meta-refresh.optional.sub.html
@@ -0,0 +1,60 @@
+
+
+
+
+ HTTP headers on request for HTML "meta" element with http-equiv="refresh"
+
+
+
+
+
+
+
diff --git a/fetch/metadata/tools/templates/element-picture.sub.html b/fetch/metadata/tools/templates/element-picture.sub.html
new file mode 100644
index 00000000000000..903aeed1f376f3
--- /dev/null
+++ b/fetch/metadata/tools/templates/element-picture.sub.html
@@ -0,0 +1,101 @@
+
+
+
+
+ HTTP headers on request for HTML "picture" element source
+
+
+
+
+
+
+
+
diff --git a/fetch/metadata/tools/templates/element-script.sub.html b/fetch/metadata/tools/templates/element-script.sub.html
new file mode 100644
index 00000000000000..4a281ae5192832
--- /dev/null
+++ b/fetch/metadata/tools/templates/element-script.sub.html
@@ -0,0 +1,54 @@
+
+
+
+
+ HTTP headers on request for HTML "script" element source
+
+
+
+
+
+
diff --git a/fetch/metadata/tools/templates/element-video-poster.sub.html b/fetch/metadata/tools/templates/element-video-poster.sub.html
new file mode 100644
index 00000000000000..9cdaf063ace4ed
--- /dev/null
+++ b/fetch/metadata/tools/templates/element-video-poster.sub.html
@@ -0,0 +1,62 @@
+
+
+
+
+ HTTP headers on request for HTML "video" element "poster"
+
+
+
+
+
+
+
diff --git a/fetch/metadata/tools/templates/element-video.sub.html b/fetch/metadata/tools/templates/element-video.sub.html
new file mode 100644
index 00000000000000..1b7b976d7c4d86
--- /dev/null
+++ b/fetch/metadata/tools/templates/element-video.sub.html
@@ -0,0 +1,51 @@
+
+
+
+
+ HTTP headers on request for HTML "video" element source
+
+
+
+
+
+
+
diff --git a/fetch/metadata/tools/templates/fetch-via-serviceworker.https.sub.html b/fetch/metadata/tools/templates/fetch-via-serviceworker.https.sub.html
new file mode 100644
index 00000000000000..fdd9f41940e1f0
--- /dev/null
+++ b/fetch/metadata/tools/templates/fetch-via-serviceworker.https.sub.html
@@ -0,0 +1,86 @@
+
+
+
+
+ HTTP headers on request using the "fetch" API and passing through a Serive Worker
+
+
+
+
+
+
+
+
+
diff --git a/fetch/metadata/tools/templates/fetch.sub.html b/fetch/metadata/tools/templates/fetch.sub.html
new file mode 100644
index 00000000000000..a8dc5368f8b545
--- /dev/null
+++ b/fetch/metadata/tools/templates/fetch.sub.html
@@ -0,0 +1,42 @@
+
+
+
+
+ HTTP headers on request using the "fetch" API
+
+
+
+
+
+
+
diff --git a/fetch/metadata/tools/templates/form-submission.sub.html b/fetch/metadata/tools/templates/form-submission.sub.html
new file mode 100644
index 00000000000000..cbf7b5ca7f3eab
--- /dev/null
+++ b/fetch/metadata/tools/templates/form-submission.sub.html
@@ -0,0 +1,86 @@
+
+
+
+
+ HTTP headers on request for HTML form navigation
+
+
+ {%- if subtests|selectattr('userActivated')|list %}
+
+
+ {%- endif %}
+
+
+
+
+
diff --git a/fetch/metadata/tools/templates/header-link.sub.html b/fetch/metadata/tools/templates/header-link.sub.html
new file mode 100644
index 00000000000000..2831f221d5c8ac
--- /dev/null
+++ b/fetch/metadata/tools/templates/header-link.sub.html
@@ -0,0 +1,56 @@
+
+
+
+
+ HTTP headers on request for HTTP "Link" header
+
+
+
+
+
+
+
diff --git a/fetch/metadata/tools/templates/header-refresh.optional.sub.html b/fetch/metadata/tools/templates/header-refresh.optional.sub.html
new file mode 100644
index 00000000000000..ff497b5915b969
--- /dev/null
+++ b/fetch/metadata/tools/templates/header-refresh.optional.sub.html
@@ -0,0 +1,56 @@
+
+
+
+
+ HTTP headers on request for HTTP "Refresh" header
+
+
+
+
+
+
+
diff --git a/fetch/metadata/tools/templates/script-module-import-dynamic.sub.html b/fetch/metadata/tools/templates/script-module-import-dynamic.sub.html
new file mode 100644
index 00000000000000..653d3cdec44ae4
--- /dev/null
+++ b/fetch/metadata/tools/templates/script-module-import-dynamic.sub.html
@@ -0,0 +1,35 @@
+
+
+
+
+ HTTP headers on request for dynamic ECMAScript module import
+
+
+
+
+
diff --git a/fetch/metadata/tools/templates/script-module-import-static.sub.html b/fetch/metadata/tools/templates/script-module-import-static.sub.html
new file mode 100644
index 00000000000000..c8d5f9532a94e6
--- /dev/null
+++ b/fetch/metadata/tools/templates/script-module-import-static.sub.html
@@ -0,0 +1,53 @@
+
+
+
+
+ HTTP headers on request for static ECMAScript module import
+
+
+
+
+
+
diff --git a/fetch/metadata/tools/templates/serviceworker.https.sub.html b/fetch/metadata/tools/templates/serviceworker.https.sub.html
new file mode 100644
index 00000000000000..82843255469e9c
--- /dev/null
+++ b/fetch/metadata/tools/templates/serviceworker.https.sub.html
@@ -0,0 +1,72 @@
+
+
+
+
+
+ HTTP headers on request for Service Workers
+
+
+
+
+
+
+
diff --git a/fetch/metadata/tools/templates/svg-image.sub.html b/fetch/metadata/tools/templates/svg-image.sub.html
new file mode 100644
index 00000000000000..52f7806b33c300
--- /dev/null
+++ b/fetch/metadata/tools/templates/svg-image.sub.html
@@ -0,0 +1,75 @@
+
+
+
+
+ {%- if subtests|length > 10 %}
+
+ {%- endif %}
+ HTTP headers on request for SVG "image" element source
+
+
+
+
+
+
+
diff --git a/fetch/metadata/tools/templates/window-history.sub.html b/fetch/metadata/tools/templates/window-history.sub.html
new file mode 100644
index 00000000000000..286d019887d466
--- /dev/null
+++ b/fetch/metadata/tools/templates/window-history.sub.html
@@ -0,0 +1,134 @@
+
+
+
+
+ {%- if subtests|length > 10 %}
+
+ {%- endif %}
+ HTTP headers on request for navigation via the HTML History API
+
+
+ {%- if subtests|selectattr('userActivated')|list %}
+
+
+ {%- endif %}
+
+
+
+
+
diff --git a/fetch/metadata/tools/templates/window-location.sub.html b/fetch/metadata/tools/templates/window-location.sub.html
new file mode 100644
index 00000000000000..96f3912361633f
--- /dev/null
+++ b/fetch/metadata/tools/templates/window-location.sub.html
@@ -0,0 +1,128 @@
+
+
+
+
+ {%- if subtests|length > 10 %}
+
+ {%- endif %}
+ HTTP headers on request for navigation via the HTML Location API
+
+
+ {%- if subtests|selectattr('userActivated')|list %}
+
+
+ {%- endif %}
+
+
+
+
+
diff --git a/fetch/metadata/tools/templates/worker-dedicated-constructor.sub.html b/fetch/metadata/tools/templates/worker-dedicated-constructor.sub.html
new file mode 100644
index 00000000000000..fede5965d3aaa7
--- /dev/null
+++ b/fetch/metadata/tools/templates/worker-dedicated-constructor.sub.html
@@ -0,0 +1,49 @@
+
+
+
+
+ HTTP headers on request for dedicated worker via the "Worker" constructor
+
+
+
+
+
diff --git a/fetch/metadata/tools/templates/worker-dedicated-importscripts.sub.html b/fetch/metadata/tools/templates/worker-dedicated-importscripts.sub.html
new file mode 100644
index 00000000000000..93e6374d54baa6
--- /dev/null
+++ b/fetch/metadata/tools/templates/worker-dedicated-importscripts.sub.html
@@ -0,0 +1,54 @@
+
+
+
+
+ HTTP headers on request for dedicated worker via the "importScripts" API
+
+
+
+
+
diff --git a/lint.ignore b/lint.ignore
index ef36a259a6b1ed..fd01861553513e 100644
--- a/lint.ignore
+++ b/lint.ignore
@@ -153,6 +153,8 @@ SET TIMEOUT: css/selectors/selector-required-type-change-002.html
SET TIMEOUT: encrypted-media/polyfill/chrome-polyfill.js
SET TIMEOUT: encrypted-media/polyfill/clearkey-polyfill.js
SET TIMEOUT: encrypted-media/scripts/playback-temporary-events.js
+SET TIMEOUT: fetch/metadata/resources/helper.sub.js
+SET TIMEOUT: fetch/metadata/resources/message-opener.html
SET TIMEOUT: focus/support/iframe-focus-with-different-site-intermediate-frame-outer.sub.html
SET TIMEOUT: focus/support/iframe-focus-with-different-site-intermediate-frame-middle.sub.html
SET TIMEOUT: focus/support/iframe-contentwindow-focus-with-different-site-intermediate-frame-outer.sub.html
diff --git a/tools/ci/requirements_build.txt b/tools/ci/requirements_build.txt
index 645592da36f3c8..8e10a0f997c4ee 100644
--- a/tools/ci/requirements_build.txt
+++ b/tools/ci/requirements_build.txt
@@ -1,4 +1,5 @@
cairocffi==1.3.0
fonttools==4.33.2
genshi==0.7.7
+jinja2==3.1.1
pyyaml==6.0
diff --git a/tools/ci/update_built.py b/tools/ci/update_built.py
index aadaba64f5db52..ffd6a280736bbc 100644
--- a/tools/ci/update_built.py
+++ b/tools/ci/update_built.py
@@ -20,6 +20,7 @@
# "css/css-text-decor/tools/generate-text-emphasis-position-property-tests.py",
# "css/css-text-decor/tools/generate-text-emphasis-ruby-tests.py",
# "css/css-text-decor/tools/generate-text-emphasis-style-property-tests.py"],
+ "fetch": ["fetch/metadata/tools/generate.py"],
"html5lib": ["html/tools/update_html5lib_tests.py"],
"infrastructure": ["infrastructure/assumptions/tools/ahem-generate-table.py"],
"mimesniff": ["mimesniff/mime-types/resources/generated-mime-types.py"],