diff --git a/common/refresh.py b/common/refresh.py
new file mode 100644
index 00000000000000..28a065d92c2721
--- /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 = [
+ ('Content-Type', 'text/html'),
+ ('Refresh', '0; URL={}'.format(request.GET.first('location')))
+ ]
+ return (200, headers, '')
diff --git a/fetch/api/resources/redirect.py b/fetch/api/resources/redirect.py
index eb5c98dd52ff8b..1f392eeeff0f19 100644
--- a/fetch/api/resources/redirect.py
+++ b/fetch/api/resources/redirect.py
@@ -33,6 +33,8 @@ def main(request, response):
if "redirect_status" in request.GET:
status = int(request.GET['redirect_status'])
+ elif "redirect_status" in request.POST:
+ status = int(request.POST['redirect_status'])
stashed_data['count'] += 1
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..1250220cdd4515
--- /dev/null
+++ b/fetch/metadata/generated/appcache-manifest.https.sub.html
@@ -0,0 +1,296 @@
+
+
+
+
+
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..a821fe073ec775
--- /dev/null
+++ b/fetch/metadata/generated/audioworklet.https.sub.html
@@ -0,0 +1,214 @@
+
+
+
+
+ HTTP headers on request for AudioWorklet module
+
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/cors-preflight.https.sub.html b/fetch/metadata/generated/cors-preflight.https.sub.html
new file mode 100644
index 00000000000000..545516a05703f4
--- /dev/null
+++ b/fetch/metadata/generated/cors-preflight.https.sub.html
@@ -0,0 +1,71 @@
+
+
+
+
+ HTTP headers on CORS preflight request
+
+
+
+
+
+
+
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..7bfafad095021c
--- /dev/null
+++ b/fetch/metadata/generated/css-font-face.https.sub.tentative.html
@@ -0,0 +1,186 @@
+
+
+
+
+ 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..f009e1ba9e6a06
--- /dev/null
+++ b/fetch/metadata/generated/css-font-face.sub.tentative.html
@@ -0,0 +1,76 @@
+
+
+
+
+ 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..a4d5aab19987a2
--- /dev/null
+++ b/fetch/metadata/generated/css-images.https.sub.tentative.html
@@ -0,0 +1,1084 @@
+
+
+
+
+
+ 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..f9677aae1cb27f
--- /dev/null
+++ b/fetch/metadata/generated/css-images.sub.tentative.html
@@ -0,0 +1,258 @@
+
+
+
+
+ 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..6e2c6d7a7a429f
--- /dev/null
+++ b/fetch/metadata/generated/element-a.https.sub.html
@@ -0,0 +1,397 @@
+
+
+
+
+
+ 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..227e3eff1c9f3a
--- /dev/null
+++ b/fetch/metadata/generated/element-a.sub.html
@@ -0,0 +1,101 @@
+
+
+
+
+ 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..e1a1574e632ac5
--- /dev/null
+++ b/fetch/metadata/generated/element-area.https.sub.html
@@ -0,0 +1,397 @@
+
+
+
+
+
+ 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..1efb75bf70d733
--- /dev/null
+++ b/fetch/metadata/generated/element-area.sub.html
@@ -0,0 +1,101 @@
+
+
+
+
+ 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..273430671b937e
--- /dev/null
+++ b/fetch/metadata/generated/element-audio.https.sub.html
@@ -0,0 +1,269 @@
+
+
+
+
+ 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..9938f033e0637b
--- /dev/null
+++ b/fetch/metadata/generated/element-audio.sub.html
@@ -0,0 +1,73 @@
+
+
+
+
+ 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..d234bf2e40195e
--- /dev/null
+++ b/fetch/metadata/generated/element-embed.https.sub.html
@@ -0,0 +1,180 @@
+
+
+
+
+ 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..18313a823cdb97
--- /dev/null
+++ b/fetch/metadata/generated/element-embed.sub.html
@@ -0,0 +1,70 @@
+
+
+
+
+ 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..2ab90f9f22fdda
--- /dev/null
+++ b/fetch/metadata/generated/element-frame.https.sub.html
@@ -0,0 +1,248 @@
+
+
+
+
+ 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..49ae054005eec1
--- /dev/null
+++ b/fetch/metadata/generated/element-frame.sub.html
@@ -0,0 +1,82 @@
+
+
+
+
+ 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..b73efbb3ad81c1
--- /dev/null
+++ b/fetch/metadata/generated/element-iframe.https.sub.html
@@ -0,0 +1,248 @@
+
+
+
+
+ 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..5d114c62a3d09f
--- /dev/null
+++ b/fetch/metadata/generated/element-iframe.sub.html
@@ -0,0 +1,82 @@
+
+
+
+
+ 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..cf4a41e4a5c8b2
--- /dev/null
+++ b/fetch/metadata/generated/element-img-environment-change.https.sub.html
@@ -0,0 +1,297 @@
+
+
+
+
+ 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..51fb818e0784b3
--- /dev/null
+++ b/fetch/metadata/generated/element-img-environment-change.sub.html
@@ -0,0 +1,102 @@
+
+
+
+
+ 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..d4f0977a094792
--- /dev/null
+++ b/fetch/metadata/generated/element-img.https.sub.html
@@ -0,0 +1,525 @@
+
+
+
+
+ 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..230167fc2e6375
--- /dev/null
+++ b/fetch/metadata/generated/element-img.sub.html
@@ -0,0 +1,120 @@
+
+
+
+
+ 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..cf55e4aee52218
--- /dev/null
+++ b/fetch/metadata/generated/element-input-image.https.sub.html
@@ -0,0 +1,185 @@
+
+
+
+
+ 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..d21f9cb9ba2168
--- /dev/null
+++ b/fetch/metadata/generated/element-input-image.sub.html
@@ -0,0 +1,64 @@
+
+
+
+
+ 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..7e4c148b53f80f
--- /dev/null
+++ b/fetch/metadata/generated/element-link-icon.https.sub.html
@@ -0,0 +1,307 @@
+
+
+
+
+
+ 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..e7d2a30c3df283
--- /dev/null
+++ b/fetch/metadata/generated/element-link-icon.sub.html
@@ -0,0 +1,98 @@
+
+
+
+
+ HTTP headers on request for HTML "link" element with rel="icon"
+
+
+
+
+
+
+
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..fe3999adec6375
--- /dev/null
+++ b/fetch/metadata/generated/element-meta-refresh.https.optional.sub.html
@@ -0,0 +1,224 @@
+
+
+
+
+ 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..72a7070d3c2b40
--- /dev/null
+++ b/fetch/metadata/generated/element-meta-refresh.optional.sub.html
@@ -0,0 +1,81 @@
+
+
+
+
+ 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..392cab61e75ffa
--- /dev/null
+++ b/fetch/metadata/generated/element-picture.https.sub.html
@@ -0,0 +1,805 @@
+
+
+
+
+ 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..db9796374e3343
--- /dev/null
+++ b/fetch/metadata/generated/element-picture.sub.html
@@ -0,0 +1,181 @@
+
+
+
+
+ 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..6f89e014d676f6
--- /dev/null
+++ b/fetch/metadata/generated/element-script.https.sub.html
@@ -0,0 +1,465 @@
+
+
+
+
+ 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..6b4d86ff99c9be
--- /dev/null
+++ b/fetch/metadata/generated/element-script.sub.html
@@ -0,0 +1,128 @@
+
+
+
+
+ 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..d70dfe468cf46b
--- /dev/null
+++ b/fetch/metadata/generated/element-video-poster.https.sub.html
@@ -0,0 +1,199 @@
+
+
+
+
+ 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..e4304b0560a05d
--- /dev/null
+++ b/fetch/metadata/generated/element-video-poster.sub.html
@@ -0,0 +1,78 @@
+
+
+
+
+ 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..169d7c5b57c741
--- /dev/null
+++ b/fetch/metadata/generated/element-video.https.sub.html
@@ -0,0 +1,269 @@
+
+
+
+
+ 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..9db9189bb3b896
--- /dev/null
+++ b/fetch/metadata/generated/element-video.sub.html
@@ -0,0 +1,73 @@
+
+
+
+
+ HTTP headers on request for HTML "video" element source
+
+
+
+
+
+
+
diff --git a/fetch/metadata/generated/fetch.https.sub.html b/fetch/metadata/generated/fetch.https.sub.html
new file mode 100644
index 00000000000000..41488a047aefca
--- /dev/null
+++ b/fetch/metadata/generated/fetch.https.sub.html
@@ -0,0 +1,246 @@
+
+
+
+
+ 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..affb8e65b8d63b
--- /dev/null
+++ b/fetch/metadata/generated/fetch.sub.html
@@ -0,0 +1,64 @@
+
+
+
+
+ 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..8da1407a5641da
--- /dev/null
+++ b/fetch/metadata/generated/form-submission.https.sub.html
@@ -0,0 +1,424 @@
+
+
+
+
+ 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..466f04ad3a4580
--- /dev/null
+++ b/fetch/metadata/generated/form-submission.sub.html
@@ -0,0 +1,135 @@
+
+
+
+
+ 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..ab15ba999036e7
--- /dev/null
+++ b/fetch/metadata/generated/header-link.https.sub.html
@@ -0,0 +1,409 @@
+
+
+
+
+ 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..b389c0aaee430f
--- /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..a3a8086c4a0c81
--- /dev/null
+++ b/fetch/metadata/generated/header-link.sub.html
@@ -0,0 +1,124 @@
+
+
+
+
+ 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..2ab80e75e99c3c
--- /dev/null
+++ b/fetch/metadata/generated/header-refresh.https.optional.sub.html
@@ -0,0 +1,220 @@
+
+
+
+
+ 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..5cfd613865454e
--- /dev/null
+++ b/fetch/metadata/generated/header-refresh.optional.sub.html
@@ -0,0 +1,77 @@
+
+
+
+
+ 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..85ee071c644ad5
--- /dev/null
+++ b/fetch/metadata/generated/script-module-import-dynamic.https.sub.html
@@ -0,0 +1,198 @@
+
+
+
+
+ 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..b1fd9fd6805482
--- /dev/null
+++ b/fetch/metadata/generated/script-module-import-dynamic.sub.html
@@ -0,0 +1,58 @@
+
+
+
+
+ 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..1880badf38596c
--- /dev/null
+++ b/fetch/metadata/generated/script-module-import-static.https.sub.html
@@ -0,0 +1,228 @@
+
+
+
+
+ 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..b1b7910c16ec31
--- /dev/null
+++ b/fetch/metadata/generated/script-module-import-static.sub.html
@@ -0,0 +1,78 @@
+
+
+
+
+ HTTP headers on request for static ECMAScript module import
+
+
+
+
+
+
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..af8cd6e149b686
--- /dev/null
+++ b/fetch/metadata/generated/svg-image.https.sub.html
@@ -0,0 +1,307 @@
+
+
+
+
+
+ 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..9446cdea5ad7a9
--- /dev/null
+++ b/fetch/metadata/generated/svg-image.sub.html
@@ -0,0 +1,96 @@
+
+
+
+
+ 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..85327d73b9db23
--- /dev/null
+++ b/fetch/metadata/generated/window-history.https.sub.html
@@ -0,0 +1,165 @@
+
+
+
+
+ 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..aef2060039e66e
--- /dev/null
+++ b/fetch/metadata/generated/window-location.https.sub.html
@@ -0,0 +1,943 @@
+
+
+
+
+
+ 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..605634460556eb
--- /dev/null
+++ b/fetch/metadata/generated/window-location.sub.html
@@ -0,0 +1,221 @@
+
+
+
+
+ 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..8d53b916f82983
--- /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-importscripts.https.sub.html b/fetch/metadata/generated/worker-dedicated-importscripts.https.sub.html
new file mode 100644
index 00000000000000..991943daa1f76e
--- /dev/null
+++ b/fetch/metadata/generated/worker-dedicated-importscripts.https.sub.html
@@ -0,0 +1,212 @@
+
+
+
+
+ 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..58503a5475ddc2
--- /dev/null
+++ b/fetch/metadata/generated/worker-dedicated-importscripts.sub.html
@@ -0,0 +1,72 @@
+
+
+
+
+ HTTP headers on request for dedicated worker via the "importScripts" API
+
+
+
+
+
diff --git a/fetch/metadata/redirect/cross-site-redirect.tentative.https.sub.html b/fetch/metadata/redirect/cross-site-redirect.tentative.https.sub.html
deleted file mode 100644
index 827eb982e47f3f..00000000000000
--- a/fetch/metadata/redirect/cross-site-redirect.tentative.https.sub.html
+++ /dev/null
@@ -1,77 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/fetch/metadata/redirect/multiple-redirect-cross-site.tentative.https.sub.html b/fetch/metadata/redirect/multiple-redirect-cross-site.tentative.https.sub.html
deleted file mode 100644
index fc986aaf4fdcff..00000000000000
--- a/fetch/metadata/redirect/multiple-redirect-cross-site.tentative.https.sub.html
+++ /dev/null
@@ -1,36 +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/header-link.py b/fetch/metadata/resources/header-link.py
new file mode 100644
index 00000000000000..990dbdaa92c6d0
--- /dev/null
+++ b/fetch/metadata/resources/header-link.py
@@ -0,0 +1,12 @@
+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 = [
+ ('Content-Type', 'text/html'),
+ ('Link', '<{}>; rel={}'.format(request.GET.first('location'), request.GET.first('rel')))
+ ]
+ return (200, headers, '')
+
diff --git a/fetch/metadata/resources/helper.sub.js b/fetch/metadata/resources/helper.sub.js
new file mode 100644
index 00000000000000..89de32df1428f6
--- /dev/null
+++ b/fetch/metadata/resources/helper.sub.js
@@ -0,0 +1,65 @@
+'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 = {
+ sameOrigin: 'https://{{host}}:{{ports[https][0]}}',
+ httpOrigin: 'http://{{host}}:{{ports[http][0]}}',
+ crossSite: 'https://{{hosts[alt][]}}:{{ports[https][0]}}',
+ sameSite: 'https://{{hosts[][www]}}:{{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.sameOrigin + 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/record-header.py b/fetch/metadata/resources/record-header.py
index 99115475683d50..3f57f6ea01d4dc 100644
--- a/fetch/metadata/resources/record-header.py
+++ b/fetch/metadata/resources/record-header.py
@@ -129,3 +129,6 @@ def main(request, response):
"""
+ if key.startswith("script"):
+ response.headers.set("Content-Type", "application/javascript")
+ return "void 0;"
diff --git a/fetch/metadata/resources/record-headers.py b/fetch/metadata/resources/record-headers.py
new file mode 100644
index 00000000000000..9918d034f8f63c
--- /dev/null
+++ b/fetch/metadata/resources/record-headers.py
@@ -0,0 +1,47 @@
+import os
+import uuid
+import hashlib
+import time
+import json
+
+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 'requireOPTIONS' in request.GET and request.method != 'OPTIONS':
+ return
+
+ if 'key' in request.GET:
+ key = request.GET['key']
+ elif 'key' in request.POST:
+ key = request.POST['key']
+
+ ## Convert the key from String to UUID valid String ##
+ testId = hashlib.md5(key).hexdigest()
+
+ ## Handle the header retrieval request ##
+ if 'retrieve' in request.GET:
+ recorded_headers = request.server.stash.take(testId)
+
+ if recorded_headers is None:
+ return (204, [], '')
+
+ return (200, [], recorded_headers)
+
+ ## Record incoming fetch metadata header value
+ else:
+ try:
+ request.server.stash.put(testId, json.dumps(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("Access-Control-Allow-Origin", "*")
+ response.headers.set("Cache-Control", "no-cache, no-store, must-revalidate")
+ response.headers.set("Pragma", "no-cache")
+ response.headers.set("Expires", "0")
+ if "mime" in request.GET:
+ response.headers.set("Content-Type", request.GET.first("mime"))
+
+ return request.GET.first("body", request.POST.first("body", ""))
diff --git a/fetch/metadata/resources/redirectTestHelper.sub.js b/fetch/metadata/resources/redirectTestHelper.sub.js
index e5547e7ac7931f..3b34000155897a 100644
--- a/fetch/metadata/resources/redirectTestHelper.sub.js
+++ b/fetch/metadata/resources/redirectTestHelper.sub.js
@@ -23,7 +23,7 @@ let insecureTestURL = 'http://{{host}}:{{ports[http][0]}}/fetch/metadata/';
// simulate us downgrading then upgrading again during the same redirect chain.
function MultipleRedirectTo(partialPath) {
let finalURL = insecureRedirectURL + encodeURIComponent(secureTestURL + partialPath);
- return insecureRedirectURL + encodeURIComponent(finalURL);
+ return secureRedirectURL + encodeURIComponent(finalURL);
}
// Helper to craft an URL that will go from HTTP => HTTPS to simulate upgrading a
diff --git a/fetch/metadata/tools/README.md b/fetch/metadata/tools/README.md
new file mode 100644
index 00000000000000..3486eff7426bc7
--- /dev/null
+++ b/fetch/metadata/tools/README.md
@@ -0,0 +1,137 @@
+# 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.
+
+## 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/fetch-metadata.conf.yml b/fetch/metadata/tools/fetch-metadata.conf.yml
new file mode 100644
index 00000000000000..87cf83ed3582c4
--- /dev/null
+++ b/fetch/metadata/tools/fetch-metadata.conf.yml
@@ -0,0 +1,591 @@
+---
+templates: templates
+output_directory: generated
+cases:
+ # Sec-Fetch-Site - redirection from HTTP
+ - all_subtests:
+ headerName: sec-fetch-site
+ filename_flags: []
+ common_axis:
+ - description: HTTPS downgrade (header not sent)
+ origins: [sameOrigin, httpOrigin]
+ expected: NULL
+ - description: HTTPS upgrade
+ origins: [httpOrigin, sameOrigin]
+ expected: cross-site
+ - description: HTTPS downgrade-upgrade
+ origins: [sameOrigin, httpOrigin, sameOrigin]
+ 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: []
+ # These requests do not necessarily induce preflight requests because
+ # preflight requests are only issued on the basis of the origin of the
+ # initial request.
+ cors-preflight.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-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: [sameOrigin, crossSite, sameOrigin]
+ expected: cross-site
+ - description: Cross-Site -> Same Origin
+ origins: [crossSite, sameOrigin]
+ expected: cross-site
+ - description: Cross-Site -> Same-Site
+ origins: [crossSite, sameSite]
+ expected: cross-site
+ - description: Cross-Site -> Cross-Site
+ origins: [crossSite, crossSite]
+ expected: cross-site
+ - description: Same-Origin -> Same Origin
+ origins: [sameOrigin, sameOrigin]
+ expected: same-origin
+ - description: Same-Origin -> Same-Site
+ origins: [sameOrigin, sameSite]
+ expected: same-site
+ - description: Same-Origin -> Cross-Site
+ origins: [sameOrigin, crossSite]
+ expected: cross-site
+ - description: Same-Site -> Same Origin
+ origins: [sameSite, sameOrigin]
+ expected: same-site
+ - description: Same-Site -> Same-Site
+ origins: [sameSite, sameSite]
+ expected: same-site
+ - description: Same-Site -> Cross-Site
+ origins: [sameSite, crossSite]
+ expected: cross-site
+ template_axes:
+ # Unused
+ # These requests do not necessarily induce preflight requests because
+ # preflight requests are only issued on the basis of the origin of the
+ # initial request.
+ cors-preflight.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-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 } }]
+ 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: [sameOrigin, httpOrigin, sameOrigin]
+ 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-picture.sub.html: []
+ element-script.sub.html: []
+ fetch.sub.html: []
+ header-link.sub.html: []
+ script-module-import-static.sub.html: []
+ script-module-import-dynamic.sub.html: []
+ cors-preflight.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: []
+
+ 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]
+ cors-preflight.sub.html:
+ - expected: cors
+ 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-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 }
+ 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 }
+ 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]
+ cors-preflight.sub.html:
+ - expected: empty
+ 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-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
+ 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
+ # 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]
+ cors-preflight.sub.html:
+ - expected: NULL
+ 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-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
+ 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
+ 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..c483eb61ee1c05
--- /dev/null
+++ b/fetch/metadata/tools/generate.py
@@ -0,0 +1,202 @@
+#!/usr/bin/env python3
+
+import argparse
+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) 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__':
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ 'config_file',
+ type=str,
+ help='Path to a YAML-formatted configuration file'
+ )
+ main(**vars(parser.parse_args()))
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..0aed6e8729774d
--- /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..1fdc7b4859059e
--- /dev/null
+++ b/fetch/metadata/tools/templates/audioworklet.https.sub.html
@@ -0,0 +1,52 @@
+
+
+
+
+ HTTP headers on request for AudioWorklet module
+
+
+
+
+
+
+
+
diff --git a/fetch/metadata/tools/templates/cors-preflight.sub.html b/fetch/metadata/tools/templates/cors-preflight.sub.html
new file mode 100644
index 00000000000000..b5b4edfa13758b
--- /dev/null
+++ b/fetch/metadata/tools/templates/cors-preflight.sub.html
@@ -0,0 +1,43 @@
+
+
+
+
+ HTTP headers on CORS preflight request
+
+
+
+
+
+
+
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..6d9d289b53b167
--- /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..c0e6e0e42dbf01
--- /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..2075988e6b11f1
--- /dev/null
+++ b/fetch/metadata/tools/templates/element-a.sub.html
@@ -0,0 +1,71 @@
+
+
+
+
+ {%- 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..fb3ccdd0178dd1
--- /dev/null
+++ b/fetch/metadata/tools/templates/element-area.sub.html
@@ -0,0 +1,71 @@
+
+
+
+
+ {%- 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..2ce840fde52e5b
--- /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..37bc8ba50fa871
--- /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..1fcfa9b69deabc
--- /dev/null
+++ b/fetch/metadata/tools/templates/element-frame.sub.html
@@ -0,0 +1,61 @@
+
+
+
+
+ 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..a6175878f7dd2b
--- /dev/null
+++ b/fetch/metadata/tools/templates/element-iframe.sub.html
@@ -0,0 +1,61 @@
+
+
+
+
+ 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..5c840f54a45928
--- /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..cb3d070ed7a26c
--- /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..d0155be772942a
--- /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..f6c2dec754aa84
--- /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-meta-refresh.optional.sub.html b/fetch/metadata/tools/templates/element-meta-refresh.optional.sub.html
new file mode 100644
index 00000000000000..aaf91b54446bb7
--- /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..4fc3f29862d958
--- /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..97b5a0dd69d062
--- /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..e3bd9ff0b68ced
--- /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..8d254bca2ef0a8
--- /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.sub.html b/fetch/metadata/tools/templates/fetch.sub.html
new file mode 100644
index 00000000000000..26defa29387d20
--- /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..dab9d06f834bb7
--- /dev/null
+++ b/fetch/metadata/tools/templates/form-submission.sub.html
@@ -0,0 +1,85 @@
+
+
+
+
+ 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..b5cf1f9696ed19
--- /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..610302fc84bf05
--- /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..2600c3e22c294f
--- /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..1a89485b31fc83
--- /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/svg-image.sub.html b/fetch/metadata/tools/templates/svg-image.sub.html
new file mode 100644
index 00000000000000..0e26db5f6e5b9c
--- /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..b20efcab6cc6bc
--- /dev/null
+++ b/fetch/metadata/tools/templates/window-history.sub.html
@@ -0,0 +1,133 @@
+
+
+
+
+ {%- 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..6fd84498d5d7d3
--- /dev/null
+++ b/fetch/metadata/tools/templates/window-location.sub.html
@@ -0,0 +1,127 @@
+
+
+
+
+ {%- 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..7df5519a80176e
--- /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..ee843ef0aa0379
--- /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/update-built-tests.sh b/update-built-tests.sh
index a45058c18f89aa..f4138e1d13d3b0 100755
--- a/update-built-tests.sh
+++ b/update-built-tests.sh
@@ -8,3 +8,4 @@ offscreen-canvas/tools/build.sh
python mimesniff/mime-types/resources/generated-mime-types.py
python3 css/css-ui/tools/appearance-build-webkit-reftests.py
python3 WebIDL/tools/generate-setlike.py
+python fetch/metadata/tools/generate.py