diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/fetch/metadata/tools | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/fetch/metadata/tools')
36 files changed, 3397 insertions, 0 deletions
diff --git a/testing/web-platform/tests/fetch/metadata/tools/README.md b/testing/web-platform/tests/fetch/metadata/tools/README.md new file mode 100644 index 0000000000..1c3bac2be5 --- /dev/null +++ b/testing/web-platform/tests/fetch/metadata/tools/README.md @@ -0,0 +1,126 @@ +# 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, run `./wpt update-built --include fetch` +in the root of the repository. + +## 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/testing/web-platform/tests/fetch/metadata/tools/fetch-metadata.conf.yml b/testing/web-platform/tests/fetch/metadata/tools/fetch-metadata.conf.yml new file mode 100644 index 0000000000..b277bcb7b5 --- /dev/null +++ b/testing/web-platform/tests/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/testing/web-platform/tests/fetch/metadata/tools/generate.py b/testing/web-platform/tests/fetch/metadata/tools/generate.py new file mode 100755 index 0000000000..fa850c8c8a --- /dev/null +++ b/testing/web-platform/tests/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 == '': + 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/testing/web-platform/tests/fetch/metadata/tools/templates/appcache-manifest.sub.https.html b/testing/web-platform/tests/fetch/metadata/tools/templates/appcache-manifest.sub.https.html new file mode 100644 index 0000000000..0dfc084f2e --- /dev/null +++ b/testing/web-platform/tests/fetch/metadata/tools/templates/appcache-manifest.sub.https.html @@ -0,0 +1,63 @@ +<!DOCTYPE html> +<!-- +[%provenance%] +--> +<html lang="en"> + <meta charset="utf-8"> + <title>HTTP headers on request for Appcache manifest</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/fetch/metadata/resources/helper.sub.js"></script> + <body> + <script> + 'use strict'; + + function induceRequest(url) { + const iframe = document.createElement('iframe'); + iframe.src = + '/fetch/metadata/resources/appcache-iframe.sub.html?manifest=' + encodeURIComponent(url); + + return new Promise((resolve) => { + addEventListener('message', function onMessage(event) { + if (event.source !== iframe.contentWindow) { + return; + } + removeEventListener('message', onMessage); + resolve(event.data); + }); + + document.body.appendChild(iframe); + }) + .then((message) => { + if (message !== 'okay') { + throw message; + } + }) + .then(() => iframe.remove()); + } + + {%- for subtest in subtests %} + + async_test((t) => { + const key = '{{uuid()}}'; + assert_implements_optional( + !!window.applicationCache, 'Application Cache supported.' + ); + + induceRequest(makeRequestURL(key, [% subtest.origins %])) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }) + .then(() => t.done(), t.step_func((error) => { throw error; })); + }, '[%subtest.headerName%][%subtest.description | pad("start", " - ")%]'); + + {%- endfor %} + </script> + </body> +</html> diff --git a/testing/web-platform/tests/fetch/metadata/tools/templates/audioworklet.https.sub.html b/testing/web-platform/tests/fetch/metadata/tools/templates/audioworklet.https.sub.html new file mode 100644 index 0000000000..7be309c506 --- /dev/null +++ b/testing/web-platform/tests/fetch/metadata/tools/templates/audioworklet.https.sub.html @@ -0,0 +1,53 @@ +<!DOCTYPE html> +<!-- +[%provenance%] +--> +<html lang="en"> + <meta charset="utf-8"> + <title>HTTP headers on request for AudioWorklet module</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + <script src="/fetch/metadata/resources/helper.sub.js"></script> + <body> + <script> + 'use strict'; + + function induceRequest(url, test) { + return test_driver.bless( + 'Enable WebAudio playback', + () => { + const audioContext = new AudioContext(); + + test.add_cleanup(() => audioContext.close()); + + return audioContext.audioWorklet.addModule(url); + } + ); + } + + {%- for subtest in subtests %} + + promise_test((t) => { + const key = '{{uuid()}}'; + + return induceRequest( + makeRequestURL(key, [% subtest.origins %], {mime: 'text/javascript'}), + t + ) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%][%subtest.description | pad("start", " - ")%]'); + + {%- endfor %} + </script> + </body> +</html> diff --git a/testing/web-platform/tests/fetch/metadata/tools/templates/css-font-face.sub.html b/testing/web-platform/tests/fetch/metadata/tools/templates/css-font-face.sub.html new file mode 100644 index 0000000000..94b33f4e6b --- /dev/null +++ b/testing/web-platform/tests/fetch/metadata/tools/templates/css-font-face.sub.html @@ -0,0 +1,60 @@ +<!DOCTYPE html> +<!-- +[%provenance%] +--> +<html lang="en"> + <meta charset="utf-8"> + <title>HTTP headers on request for CSS font-face</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/fetch/metadata/resources/helper.sub.js"></script> + <body> + <script> + 'use strict'; + + let count = 0; + + function induceRequest(t, url) { + const id = `el-${count += 1}`; + const style = document.createElement('style'); + style.appendChild(document.createTextNode(` + @font-face { + font-family: wpt-font-family${id}; + src: url(${url}); + } + #el-${id} { + font-family: wpt-font-family${id}; + } + `)); + const div = document.createElement('div'); + div.setAttribute('id', 'el-' + id); + div.appendChild(style); + div.appendChild(document.createTextNode('x')); + document.body.appendChild(div); + + t.add_cleanup(() => div.remove()); + + return document.fonts.ready; + } + + {%- for subtest in subtests %} + + promise_test((t) => { + const key = '{{uuid()}}'; + + return induceRequest(t, makeRequestURL(key, [% subtest.origins %])) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%][%subtest.description | pad("start", " - ")%]'); + + {%- endfor %} + </script> + </body> +</html> diff --git a/testing/web-platform/tests/fetch/metadata/tools/templates/css-images.sub.html b/testing/web-platform/tests/fetch/metadata/tools/templates/css-images.sub.html new file mode 100644 index 0000000000..e394f9f5b0 --- /dev/null +++ b/testing/web-platform/tests/fetch/metadata/tools/templates/css-images.sub.html @@ -0,0 +1,137 @@ +<!DOCTYPE html> +<!-- +[%provenance%] +--> +<html lang="en"> + <meta charset="utf-8"> + {%- if subtests|length > 10 %} + <meta name="timeout" content="long"> + {%- endif %} + <title>HTTP headers on request for CSS image-accepting properties</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/fetch/metadata/resources/helper.sub.js"></script> + <body> + <script> + 'use strict'; + + /** + * The subtests in this file use an iframe to induce requests for CSS + * resources because an iframe's `onload` event is the most direct and + * generic mechanism to detect loading of CSS resources. As an optimization, + * the subtests share the same iframe and document. + */ + const declarations = []; + const iframe = document.createElement('iframe'); + const whenIframeReady = new Promise((resolve, reject) => { + iframe.onload = resolve; + iframe.onerror = reject; + }); + + {%- for subtest in subtests %} + + async_test((t) => { + const key = '{{uuid()}}'; + const url = makeRequestURL(key, [% subtest.origins %]); + + declarations.push(`background-image: url("${url}");`); + + whenIframeReady + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_equals(headers['[%subtest.headerName%]'], '[%subtest.expected%]'); + {%- endif %} + }) + .then(t.step_func_done(), (error) => t.unreached_func()); + }, 'background-image [%subtest.headerName%][%subtest.description | pad("start", " - ")%]'); + + async_test((t) => { + const key = '{{uuid()}}'; + const url = makeRequestURL(key, [% subtest.origins %]); + + declarations.push(`border-image: url("${url}");`); + + whenIframeReady + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }) + .then(t.step_func_done(), t.unreached_func()); + }, 'border-image [%subtest.headerName%][%subtest.description | pad("start", " - ")%]'); + + async_test((t) => { + const key = '{{uuid()}}'; + const url = makeRequestURL(key, [% subtest.origins %]); + + declarations.push(`content: url("${url}");`); + + whenIframeReady + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }) + .then(t.step_func_done(), t.unreached_func()); + }, 'content [%subtest.headerName%][%subtest.description | pad("start", " - ")%]'); + + async_test((t) => { + const key = '{{uuid()}}'; + const url = makeRequestURL(key, [% subtest.origins %]); + + declarations.push(`cursor: url("${url}"), auto;`); + + whenIframeReady + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }) + .then(t.step_func_done(), t.unreached_func()); + }, 'cursor [%subtest.headerName%][%subtest.description | pad("start", " - ")%]'); + + async_test((t) => { + const key = '{{uuid()}}'; + const url = makeRequestURL(key, [% subtest.origins %]); + + declarations.push(`list-style-image: url("${url}");`); + + whenIframeReady + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }) + .then(t.step_func_done(), t.unreached_func()); + }, 'list-style-image [%subtest.headerName%][%subtest.description | pad("start", " - ")%]'); + + {%- endfor %} + + iframe.srcdoc = declarations.map((declaration, index) => ` + <style>.el${index} { ${declaration} }</style><div class="el${index}"></div>` + ).join(''); + document.body.appendChild(iframe); + + </script> + </body> +</html> diff --git a/testing/web-platform/tests/fetch/metadata/tools/templates/element-a.sub.html b/testing/web-platform/tests/fetch/metadata/tools/templates/element-a.sub.html new file mode 100644 index 0000000000..2bd8e8a40e --- /dev/null +++ b/testing/web-platform/tests/fetch/metadata/tools/templates/element-a.sub.html @@ -0,0 +1,72 @@ +<!DOCTYPE html> +<!-- +[%provenance%] +--> +<html lang="en"> + <meta charset="utf-8"> + {%- if subtests|length > 10 %} + <meta name="timeout" content="long"> + {%- endif %} + <title>HTTP headers on request for HTML "a" element navigation</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + {%- if subtests|selectattr('userActivated')|list %} + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + {%- endif %} + <script src="/fetch/metadata/resources/helper.sub.js"></script> + <body> + <script> + 'use strict'; + + function induceRequest(url, {test, userActivated, attributes}) { + const win = window.open(); + const anchor = win.document.createElement('a'); + anchor.setAttribute('href', url); + + for (const [ name, value ] of Object.entries(attributes)) { + anchor.setAttribute(name, value); + } + + win.document.body.appendChild(anchor); + + test.add_cleanup(() => win.close()); + + if (userActivated) { + test_driver.bless('enable user activation', () => anchor.click()); + } else { + anchor.click(); + } + } + + {%- for subtest in subtests %} + + promise_test((t) => { + const key = '{{uuid()}}'; + + induceRequest( + makeRequestURL(key, [% subtest.origins %], {mime: 'text/html'}), + { + test: t, + userActivated: [%subtest.userActivated | default(false) | tojson%], + attributes: [%subtest.elementAttrs | default({}) | tojson%] + } + ); + + // `induceRequest` does not necessarily trigger a navigation, so the Python + // handler must be polled until it has received the initial request. + return retrieve(key, {poll: true}) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%][%subtest.description | pad("start", " - ")%] - [%subtest.elementAttrs | collection("attributes")%][% " with user activation" if subtest.userActivated%]'); + + {%- endfor %} + </script> + </body> +</html> diff --git a/testing/web-platform/tests/fetch/metadata/tools/templates/element-area.sub.html b/testing/web-platform/tests/fetch/metadata/tools/templates/element-area.sub.html new file mode 100644 index 0000000000..0cef5b2294 --- /dev/null +++ b/testing/web-platform/tests/fetch/metadata/tools/templates/element-area.sub.html @@ -0,0 +1,72 @@ +<!DOCTYPE html> +<!-- +[%provenance%] +--> +<html lang="en"> + <meta charset="utf-8"> + {%- if subtests|length > 10 %} + <meta name="timeout" content="long"> + {%- endif %} + <title>HTTP headers on request for HTML "area" element navigation</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + {%- if subtests|selectattr('userActivated')|list %} + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + {%- endif %} + <script src="/fetch/metadata/resources/helper.sub.js"></script> + <body> + <script> + 'use strict'; + + function induceRequest(url, {test, userActivated, attributes}) { + const win = window.open(); + const area = win.document.createElement('area'); + area.setAttribute('href', url); + + for (const [ name, value ] of Object.entries(attributes)) { + area.setAttribute(name, value); + } + + win.document.body.appendChild(area); + + test.add_cleanup(() => win.close()); + + if (userActivated) { + test_driver.bless('enable user activation', () => area.click()); + } else { + area.click(); + } + } + + {%- for subtest in subtests %} + + promise_test((t) => { + const key = '{{uuid()}}'; + + induceRequest( + makeRequestURL(key, [% subtest.origins %], {mime: 'text/html'}), + { + test: t, + userActivated: [%subtest.userActivated | default(false) | tojson%], + attributes: [%subtest.elementAttrs | default({}) | tojson%] + } + ); + + // `induceRequest` does not necessarily trigger a navigation, so the Python + // handler must be polled until it has received the initial request. + return retrieve(key, {poll: true}) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%][%subtest.description | pad("start", " - ")%] - [%subtest.elementAttrs | collection("attributes")%][% " with user activation" if subtest.userActivated%]'); + + {%- endfor %} + </script> + </body> +</html> diff --git a/testing/web-platform/tests/fetch/metadata/tools/templates/element-audio.sub.html b/testing/web-platform/tests/fetch/metadata/tools/templates/element-audio.sub.html new file mode 100644 index 0000000000..92bc22198e --- /dev/null +++ b/testing/web-platform/tests/fetch/metadata/tools/templates/element-audio.sub.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<!-- +[%provenance%] +--> +<html lang="en"> + <meta charset="utf-8"> + <title>HTTP headers on request for HTML "audio" element source</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/fetch/metadata/resources/helper.sub.js"></script> + <body> + <script> + 'use strict'; + + function induceRequest(url, attributes) { + const audio = document.createElement('audio'); + + for (const [ name, value ] of Object.entries(attributes)) { + audio.setAttribute(name, value); + } + + return new Promise((resolve) => { + audio.setAttribute('src', url); + audio.onload = audio.onerror = resolve; + }); + } + + {%- for subtest in subtests %} + + promise_test(() => { + const key = '{{uuid()}}'; + + return induceRequest( + makeRequestURL(key, [% subtest.origins %]), + [%subtest.elementAttrs | default({}) | tojson%] + ) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%] - [%subtest.description | pad("end", ", ")%][%subtest.elementAttrs | collection("attributes")%]'); + + {%- endfor %} + </script> + </body> +</html> diff --git a/testing/web-platform/tests/fetch/metadata/tools/templates/element-embed.sub.html b/testing/web-platform/tests/fetch/metadata/tools/templates/element-embed.sub.html new file mode 100644 index 0000000000..18ce09e5fd --- /dev/null +++ b/testing/web-platform/tests/fetch/metadata/tools/templates/element-embed.sub.html @@ -0,0 +1,54 @@ +<!DOCTYPE html> +<!-- +[%provenance%] +--> +<html lang="en"> + <meta charset="utf-8"> + <title>HTTP headers on request for HTML "embed" element source</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/fetch/metadata/resources/helper.sub.js"></script> + <body> + <script> + 'use strict'; + + const params = { + body: ` + <svg xmlns="http://www.w3.org/2000/svg" width="123" height="123"> + <rect fill="lime" width="123" height="123"/> + </svg> + `, + mime: 'image/svg+xml' + }; + + function induceRequest(t, url) { + const embed = document.createElement('embed'); + embed.setAttribute('src', url); + document.body.appendChild(embed); + + t.add_cleanup(() => embed.remove()); + + return new Promise((resolve) => embed.addEventListener('load', resolve)); + } + + {%- for subtest in subtests %} + + promise_test((t) => { + const key = '{{uuid()}}'; + + return induceRequest(t, makeRequestURL(key, [% subtest.origins %], params)) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%][%subtest.description | pad("start", " - ")%]'); + + {%- endfor %} + </script> + </body> +</html> diff --git a/testing/web-platform/tests/fetch/metadata/tools/templates/element-frame.sub.html b/testing/web-platform/tests/fetch/metadata/tools/templates/element-frame.sub.html new file mode 100644 index 0000000000..ce90171779 --- /dev/null +++ b/testing/web-platform/tests/fetch/metadata/tools/templates/element-frame.sub.html @@ -0,0 +1,62 @@ +<!DOCTYPE html> +<!-- +[%provenance%] +--> +<html lang="en"> + <meta charset="utf-8"> + <title>HTTP headers on request for HTML "frame" element source</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + {%- if subtests|selectattr('userActivated')|list %} + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + {%- endif %} + <script src="/fetch/metadata/resources/helper.sub.js"></script> + <body> + <script> + 'use strict'; + + function induceRequest(url, test, userActivated) { + const frame = document.createElement('frame'); + + const setSrc = () => frame.setAttribute('src', url); + + document.body.appendChild(frame); + test.add_cleanup(() => frame.remove()); + + return new Promise((resolve) => { + if (userActivated) { + test_driver.bless('enable user activation', setSrc); + } else { + setSrc(); + } + + frame.onload = frame.onerror = resolve; + }); + } + + {%- for subtest in subtests %} + + promise_test((t) => { + const key = '{{uuid()}}'; + + return induceRequest( + makeRequestURL(key, [% subtest.origins %], {mime: 'text/html'}), + t, + [%subtest.userActivated | default(false) | tojson%] + ) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%][%subtest.description | pad("start", " - ")%][% " with user activation" if subtest.userActivated%]'); + + {%- endfor %} + </script> + </body> +</html> diff --git a/testing/web-platform/tests/fetch/metadata/tools/templates/element-iframe.sub.html b/testing/web-platform/tests/fetch/metadata/tools/templates/element-iframe.sub.html new file mode 100644 index 0000000000..43a632a15c --- /dev/null +++ b/testing/web-platform/tests/fetch/metadata/tools/templates/element-iframe.sub.html @@ -0,0 +1,62 @@ +<!DOCTYPE html> +<!-- +[%provenance%] +--> +<html lang="en"> + <meta charset="utf-8"> + <title>HTTP headers on request for HTML "frame" element source</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + {%- if subtests|selectattr('userActivated')|list %} + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + {%- endif %} + <script src="/fetch/metadata/resources/helper.sub.js"></script> + <body> + <script> + 'use strict'; + + function induceRequest(url, test, userActivated) { + const iframe = document.createElement('iframe'); + + const setSrc = () => iframe.setAttribute('src', url); + + document.body.appendChild(iframe); + test.add_cleanup(() => iframe.remove()); + + return new Promise((resolve) => { + if (userActivated) { + test_driver.bless('enable user activation', setSrc); + } else { + setSrc(); + } + + iframe.onload = iframe.onerror = resolve; + }); + } + + {%- for subtest in subtests %} + + promise_test((t) => { + const key = '{{uuid()}}'; + + return induceRequest( + makeRequestURL(key, [% subtest.origins %], {mime: 'text/html'}), + t, + [%subtest.userActivated | default(false) | tojson%] + ) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%][%subtest.description | pad("start", " - ")%][% " with user activation" if subtest.userActivated%]'); + + {%- endfor %} + </script> + </body> +</html> diff --git a/testing/web-platform/tests/fetch/metadata/tools/templates/element-img-environment-change.sub.html b/testing/web-platform/tests/fetch/metadata/tools/templates/element-img-environment-change.sub.html new file mode 100644 index 0000000000..5a65114f18 --- /dev/null +++ b/testing/web-platform/tests/fetch/metadata/tools/templates/element-img-environment-change.sub.html @@ -0,0 +1,78 @@ +<!DOCTYPE html> +<!-- +[%provenance%] +--> +<html lang="en"> + <meta charset="utf-8"> + <title>HTTP headers on image request triggered by change to environment</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/fetch/metadata/resources/helper.sub.js"></script> + <body> + <script> + 'use strict'; + + // The response to the request under test must describe a valid image + // resource in order for the `load` event to be fired. + const params = { + body: ` + <svg xmlns="http://www.w3.org/2000/svg" width="123" height="123"> + <rect fill="lime" width="123" height="123"/> + </svg> + `, + mime: 'image/svg+xml' + }; + + function induceRequest(t, url, attributes) { + const iframe = document.createElement('iframe'); + iframe.style.width = '50px'; + document.body.appendChild(iframe); + t.add_cleanup(() => iframe.remove()); + iframe.contentDocument.open(); + iframe.contentDocument.close(); + + const image = iframe.contentDocument.createElement('img'); + for (const [ name, value ] of Object.entries(attributes)) { + image.setAttribute(name, value); + } + iframe.contentDocument.body.appendChild(image); + + image.setAttribute('srcset', `${url} 100w, /media/1x1-green.png 1w`); + image.setAttribute('sizes', '(max-width: 100px) 1px, (min-width: 150px) 123px'); + + return new Promise((resolve) => { + image.onload = image.onerror = resolve; + }) + .then(() => { + + iframe.style.width = '200px'; + + return new Promise((resolve) => image.onload = resolve); + }); + } + + {%- for subtest in subtests %} + + promise_test((t) => { + const key = '{{uuid()}}'; + + return induceRequest( + t, + makeRequestURL(key, [% subtest.origins %], params), + [%subtest.elementAttrs | default({}) | tojson%] + ) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%] - [%subtest.description | pad("end", ", ")%][%subtest.elementAttrs | collection("attributes")%]'); + + {%- endfor %} + </script> + </body> +</html> diff --git a/testing/web-platform/tests/fetch/metadata/tools/templates/element-img.sub.html b/testing/web-platform/tests/fetch/metadata/tools/templates/element-img.sub.html new file mode 100644 index 0000000000..1dac5843ec --- /dev/null +++ b/testing/web-platform/tests/fetch/metadata/tools/templates/element-img.sub.html @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<!-- +[%provenance%] +--> +<html lang="en"> + <meta charset="utf-8"> + <title>HTTP headers on request for HTML "img" element source</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/fetch/metadata/resources/helper.sub.js"></script> + <body> + <script> + 'use strict'; + + function induceRequest(url, sourceAttr, attributes) { + const image = document.createElement('img'); + + for (const [ name, value ] of Object.entries(attributes)) { + image.setAttribute(name, value); + } + + return new Promise((resolve) => { + image.setAttribute(sourceAttr, url); + image.onload = image.onerror = resolve; + }); + } + + {%- for subtest in subtests %} + + promise_test(() => { + const key = '{{uuid()}}'; + + return induceRequest( + makeRequestURL(key, [% subtest.origins %]), + '[%subtest.sourceAttr%]', + [%subtest.elementAttrs | default({}) | tojson%] + ) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%] - [%subtest.sourceAttr%] - [%subtest.description | pad("end", ", ")%][%subtest.elementAttrs | collection("attributes")%]'); + + {%- endfor %} + </script> + </body> +</html> diff --git a/testing/web-platform/tests/fetch/metadata/tools/templates/element-input-image.sub.html b/testing/web-platform/tests/fetch/metadata/tools/templates/element-input-image.sub.html new file mode 100644 index 0000000000..3c50008433 --- /dev/null +++ b/testing/web-platform/tests/fetch/metadata/tools/templates/element-input-image.sub.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<!-- +[%provenance%] +--> +<html lang="en"> + <meta charset="utf-8"> + <title>HTTP headers on request for HTML "input" element with type="button"</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/fetch/metadata/resources/helper.sub.js"></script> + <body> + <script> + 'use strict'; + + function induceRequest(url, test) { + const input = document.createElement('input'); + input.setAttribute('type', 'image'); + + document.body.appendChild(input); + test.add_cleanup(() => input.remove()); + + return new Promise((resolve) => { + input.onload = input.onerror = resolve; + input.setAttribute('src', url); + }); + } + + {%- for subtest in subtests %} + + promise_test((t) => { + const key = '{{uuid()}}'; + + return induceRequest(makeRequestURL(key, [% subtest.origins %]), t) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%] - [%subtest.description | pad("end", ", ")%][%subtest.elementAttrs | collection("attributes")%]'); + + {%- endfor %} + </script> + </body> +</html> diff --git a/testing/web-platform/tests/fetch/metadata/tools/templates/element-link-icon.sub.html b/testing/web-platform/tests/fetch/metadata/tools/templates/element-link-icon.sub.html new file mode 100644 index 0000000000..18ce12a689 --- /dev/null +++ b/testing/web-platform/tests/fetch/metadata/tools/templates/element-link-icon.sub.html @@ -0,0 +1,75 @@ +<!DOCTYPE html> +<!-- +[%provenance%] +--> +<html lang="en"> + <meta charset="utf-8"> + {%- if subtests|length > 10 %} + <meta name="timeout" content="long"> + {%- endif %} + <title>HTTP headers on request for HTML "link" element with rel="icon"</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/fetch/metadata/resources/helper.sub.js"></script> + <body> + <script> + 'use strict'; + + const params = { + body: ` + <svg xmlns="http://www.w3.org/2000/svg" width="123" height="123"> + <rect fill="lime" width="123" height="123"/> + </svg> + `, + mime: 'image/svg+xml' + }; + + /** + * The `link` element supports a `load` event. That event would reliably + * indicate that the browser had received the request. Multiple major + * browsers do not implement the event, however, so in order to promote the + * visibility of this test, a less efficient polling-based detection + * mechanism is used. + * + * https://bugzilla.mozilla.org/show_bug.cgi?id=1638188 + * https://bugs.chromium.org/p/chromium/issues/detail?id=1083034 + */ + function induceRequest(t, url, attributes) { + const link = document.createElement('link'); + link.setAttribute('rel', 'icon'); + link.setAttribute('href', url); + + for (const [ name, value ] of Object.entries(attributes)) { + link.setAttribute(name, value); + } + + document.head.appendChild(link); + t.add_cleanup(() => link.remove()); + } + + {%- for subtest in subtests %} + + promise_test((t) => { + const key = '{{uuid()}}'; + + induceRequest( + t, + makeRequestURL(key, [% subtest.origins %], params), + [%subtest.elementAttrs | default({}) | tojson%] + ); + + return retrieve(key, {poll:true}) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%][%subtest.description | pad("start", " - ")%] [%subtest.elementAttrs | collection("attributes")%]'); + + {%- endfor %} + </script> + </body> +</html> diff --git a/testing/web-platform/tests/fetch/metadata/tools/templates/element-link-prefetch.optional.sub.html b/testing/web-platform/tests/fetch/metadata/tools/templates/element-link-prefetch.optional.sub.html new file mode 100644 index 0000000000..59d677d8d6 --- /dev/null +++ b/testing/web-platform/tests/fetch/metadata/tools/templates/element-link-prefetch.optional.sub.html @@ -0,0 +1,71 @@ +<!DOCTYPE html> +<!-- +[%provenance%] +--> +<html lang="en"> + <meta charset="utf-8"> + {%- if subtests|length > 10 %} + <meta name="timeout" content="long"> + {%- endif %} + <title>HTTP headers on request for HTML "link" element with rel="prefetch"</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/fetch/metadata/resources/helper.sub.js"></script> + <body> + <script> + 'use strict'; + + /** + * The `link` element supports a `load` event. That event would reliably + * indicate that the browser had received the request. Multiple major + * browsers do not implement the event, however, so in order to promote the + * visibility of this test, a less efficient polling-based detection + * mechanism is used. + * + * https://bugzilla.mozilla.org/show_bug.cgi?id=1638188 + * https://bugs.chromium.org/p/chromium/issues/detail?id=1083034 + */ + function induceRequest(t, url, attributes) { + const link = document.createElement('link'); + link.setAttribute('rel', 'prefetch'); + link.setAttribute('href', url); + + for (const [ name, value ] of Object.entries(attributes)) { + link.setAttribute(name, value); + } + + document.head.appendChild(link); + t.add_cleanup(() => link.remove()); + } + + setup(() => { + assert_implements_optional(document.createElement('link').relList.supports('prefetch')); + }); + + {%- for subtest in subtests %} + + promise_test((t) => { + const key = '{{uuid()}}'; + + induceRequest( + t, + makeRequestURL(key, [% subtest.origins %]), + [%subtest.elementAttrs | default({}) | tojson%] + ); + + return retrieve(key, {poll:true}) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%][%subtest.description | pad("start", " - ")%] [%subtest.elementAttrs | collection("attributes")%]'); + + {%- endfor %} + </script> + </body> +</html> + diff --git a/testing/web-platform/tests/fetch/metadata/tools/templates/element-meta-refresh.optional.sub.html b/testing/web-platform/tests/fetch/metadata/tools/templates/element-meta-refresh.optional.sub.html new file mode 100644 index 0000000000..5a8d8f8ecd --- /dev/null +++ b/testing/web-platform/tests/fetch/metadata/tools/templates/element-meta-refresh.optional.sub.html @@ -0,0 +1,60 @@ +<!DOCTYPE html> +<!-- +[%provenance%] +--> +<html lang="en"> + <meta charset="utf-8"> + <title>HTTP headers on request for HTML "meta" element with http-equiv="refresh"</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/fetch/metadata/resources/helper.sub.js"></script> + <body> + <script> + 'use strict'; + + function induceRequest(url, test) { + const win = window.open(); + test.add_cleanup(() => win.close()); + + win.document.open(); + win.document.write( + `<meta http-equiv="Refresh" content="0; URL=${url}">` + ); + win.document.close(); + + return new Promise((resolve) => { + addEventListener('message', (event) => { + if (event.source === win) { + resolve(); + } + }); + }); + } + + const responseParams = { + mime: 'text/html', + body: `<script>opener.postMessage(0, '*')</${''}script>` + }; + {%- for subtest in subtests %} + + promise_test((t) => { + const key = '{{uuid()}}'; + + return induceRequest( + makeRequestURL(key, [% subtest.origins %], responseParams), t + ) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%][%subtest.description | pad("start", " - ")%]'); + + {%- endfor %} + </script> + </body> +</html> diff --git a/testing/web-platform/tests/fetch/metadata/tools/templates/element-picture.sub.html b/testing/web-platform/tests/fetch/metadata/tools/templates/element-picture.sub.html new file mode 100644 index 0000000000..903aeed1f3 --- /dev/null +++ b/testing/web-platform/tests/fetch/metadata/tools/templates/element-picture.sub.html @@ -0,0 +1,101 @@ +<!DOCTYPE html> +<!-- +[%provenance%] +--> +<html lang="en"> + <meta charset="utf-8"> + <title>HTTP headers on request for HTML "picture" element source</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/fetch/metadata/resources/helper.sub.js"></script> + <body> + <script> + 'use strict'; + + function induceRequest(url, sourceEl, sourceAttr, attributes) { + const picture = document.createElement('picture'); + const els = { + img: document.createElement('img'), + source: document.createElement('source') + }; + picture.appendChild(els.source); + picture.appendChild(els.img); + document.body.appendChild(picture); + + for (const [ name, value ] of Object.entries(attributes)) { + els.img.setAttribute(name, value); + } + + return new Promise((resolve) => { + els[sourceEl].setAttribute(sourceAttr, url); + els.img.onload = els.img.onerror = resolve; + }); + } + + {%- for subtest in subtests %} + + promise_test(() => { + const key = '{{uuid()}}'; + + return induceRequest( + makeRequestURL(key, [% subtest.origins %]), + 'img', + 'src', + [%subtest.elementAttrs | default({}) | tojson%] + ) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%] - img[src] - [%subtest.description | pad("end", ", ")%][%subtest.elementAttrs | collection("attributes")%]'); + + promise_test(() => { + const key = '{{uuid()}}'; + + return induceRequest( + makeRequestURL(key, [% subtest.origins %]), + 'img', + 'srcset', + [%subtest.elementAttrs | default({}) | tojson%] + ) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%] - img[srcset] - [%subtest.description | pad("end", ", ")%][%subtest.elementAttrs | collection("attributes")%]'); + + promise_test(() => { + const key = '{{uuid()}}'; + + return induceRequest( + makeRequestURL(key, [% subtest.origins %]), + 'source', + 'srcset', + [%subtest.elementAttrs | default({}) | tojson%] + ) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%] - source[srcset] - [%subtest.description | pad("end", ", ")%][%subtest.elementAttrs | collection("attributes")%]'); + + {%- endfor %} + </script> + </body> +</html> + diff --git a/testing/web-platform/tests/fetch/metadata/tools/templates/element-script.sub.html b/testing/web-platform/tests/fetch/metadata/tools/templates/element-script.sub.html new file mode 100644 index 0000000000..4a281ae519 --- /dev/null +++ b/testing/web-platform/tests/fetch/metadata/tools/templates/element-script.sub.html @@ -0,0 +1,54 @@ +<!DOCTYPE html> +<!-- +[%provenance%] +--> +<html lang="en"> + <meta charset="utf-8"> + <title>HTTP headers on request for HTML "script" element source</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/fetch/metadata/resources/helper.sub.js"></script> + <body> + <script> + 'use strict'; + + function induceRequest(url, attributes) { + const script = document.createElement('script'); + script.setAttribute('src', url); + + for (const [ name, value ] of Object.entries(attributes)) { + script.setAttribute(name, value); + } + + return new Promise((resolve, reject) => { + script.onload = resolve; + script.onerror = () => reject('Failed to load script'); + document.body.appendChild(script); + }) + .then(() => script.remove()); + } + {%- for subtest in subtests %} + + promise_test(() => { + const key = '{{uuid()}}'; + const url = makeRequestURL( + key, [% subtest.origins %], { mime: 'application/javascript' } + ); + + return induceRequest(url, + [%subtest.elementAttrs | default({}) | tojson%] + ) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%] - [%subtest.description | pad("end", ", ")%][%-subtest.elementAttrs | collection("attributes")%]'); + + {%- endfor %} + </script> +</html> diff --git a/testing/web-platform/tests/fetch/metadata/tools/templates/element-video-poster.sub.html b/testing/web-platform/tests/fetch/metadata/tools/templates/element-video-poster.sub.html new file mode 100644 index 0000000000..9cdaf063ac --- /dev/null +++ b/testing/web-platform/tests/fetch/metadata/tools/templates/element-video-poster.sub.html @@ -0,0 +1,62 @@ +<!DOCTYPE html> +<!-- +[%provenance%] +--> +<html lang="en"> + <meta charset="utf-8"> + <title>HTTP headers on request for HTML "video" element "poster"</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/fetch/metadata/resources/helper.sub.js"></script> + <body> + <script> + 'use strict'; + + const params = { + body: ` + <svg xmlns="http://www.w3.org/2000/svg" width="123" height="123"> + <rect fill="lime" width="123" height="123"/> + </svg> + `, + mime: 'image/svg+xml' + }; + + function induceRequest(t, url) { + var video = document.createElement('video'); + video.setAttribute('poster', url); + document.body.appendChild(video); + + const poll = () => { + if (video.clientWidth === 123) { + return; + } + + return new Promise((resolve) => t.step_timeout(resolve, 0)) + .then(poll); + }; + t.add_cleanup(() => video.remove()); + + return poll(); + } + + {%- for subtest in subtests %} + + promise_test((t) => { + const key = '{{uuid()}}'; + + return induceRequest(t, makeRequestURL(key, [% subtest.origins %], params)) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%][%subtest.description | pad("start", " - ")%]'); + + {%- endfor %} + </script> + </body> +</html> diff --git a/testing/web-platform/tests/fetch/metadata/tools/templates/element-video.sub.html b/testing/web-platform/tests/fetch/metadata/tools/templates/element-video.sub.html new file mode 100644 index 0000000000..1b7b976d7c --- /dev/null +++ b/testing/web-platform/tests/fetch/metadata/tools/templates/element-video.sub.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<!-- +[%provenance%] +--> +<html lang="en"> + <meta charset="utf-8"> + <title>HTTP headers on request for HTML "video" element source</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/fetch/metadata/resources/helper.sub.js"></script> + <body> + <script> + 'use strict'; + + function induceRequest(url, attributes) { + const video = document.createElement('video'); + + for (const [ name, value ] of Object.entries(attributes)) { + video.setAttribute(name, value); + } + + return new Promise((resolve) => { + video.setAttribute('src', url); + video.onload = video.onerror = resolve; + }); + } + + {%- for subtest in subtests %} + + promise_test(() => { + const key = '{{uuid()}}'; + + return induceRequest( + makeRequestURL(key, [% subtest.origins %]), + [%subtest.elementAttrs | default({}) | tojson%] + ) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%] - [%subtest.description | pad("end", ", ")%][%subtest.elementAttrs | collection("attributes")%]'); + + {%- endfor %} + </script> + </body> +</html> diff --git a/testing/web-platform/tests/fetch/metadata/tools/templates/fetch-via-serviceworker.https.sub.html b/testing/web-platform/tests/fetch/metadata/tools/templates/fetch-via-serviceworker.https.sub.html new file mode 100644 index 0000000000..eead710200 --- /dev/null +++ b/testing/web-platform/tests/fetch/metadata/tools/templates/fetch-via-serviceworker.https.sub.html @@ -0,0 +1,88 @@ +<!DOCTYPE html> +<!-- +[%provenance%] +--> +<html lang="en"> + <meta charset="utf-8"> + {%- if subtests|length > 10 %} + <meta name="timeout" content="long"> + {%- endif %} + <title>HTTP headers on request using the "fetch" API and passing through a Serive Worker</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script> + <script src="/fetch/metadata/resources/helper.sub.js"></script> + <body> + <script> + 'use strict'; + + const scripts = { + fallback: '/fetch/metadata/resources/fetch-via-serviceworker--fallback--sw.js', + respondWith: '/fetch/metadata/resources/fetch-via-serviceworker--respondWith--sw.js' + }; + + function induceRequest(t, url, init, script) { + const SCOPE = '/fetch/metadata/resources/fetch-via-serviceworker-frame.html'; + const SCRIPT = scripts[script]; + + return service_worker_unregister_and_register(t, SCRIPT, SCOPE) + .then((registration) => { + t.add_cleanup(() => registration.unregister()); + + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(() => with_iframe(SCOPE)) + .then((frame) => { + t.add_cleanup(() => frame.remove()); + + return frame.contentWindow.fetch(url, init); + }); + } + + {%- for subtest in subtests %} + + promise_test((t) => { + const key = '{{uuid()}}'; + + return induceRequest( + t, + makeRequestURL(key, [% subtest.origins %]), + [%subtest.init | default({}) | tojson%], + 'respondWith' + ) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%] - [%subtest.description | pad("end", ", ")%][%subtest.init | collection("init")%] - respondWith'); + + promise_test((t) => { + const key = '{{uuid()}}'; + + return induceRequest( + t, + makeRequestURL(key, [% subtest.origins %]), + [%subtest.init | default({}) | tojson%], + 'fallback' + ) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%] - [%subtest.description | pad("end", ", ")%][%subtest.init | collection("init")%] - fallback'); + + {%- endfor %} + + </script> + </body> +</html> diff --git a/testing/web-platform/tests/fetch/metadata/tools/templates/fetch.sub.html b/testing/web-platform/tests/fetch/metadata/tools/templates/fetch.sub.html new file mode 100644 index 0000000000..a8dc5368f8 --- /dev/null +++ b/testing/web-platform/tests/fetch/metadata/tools/templates/fetch.sub.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<!-- +[%provenance%] +--> +<html lang="en"> + <meta charset="utf-8"> + <title>HTTP headers on request using the "fetch" API</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/fetch/metadata/resources/helper.sub.js"></script> + <body> + <script> + 'use strict'; + + function induceRequest(url, init) { + return fetch(url, init); + } + + {%- for subtest in subtests %} + + promise_test(() => { + const key = '{{uuid()}}'; + + return induceRequest( + makeRequestURL(key, [% subtest.origins %]), + [%subtest.init | default({}) | tojson%] + ) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%] - [%subtest.description | pad("end", ", ")%][%subtest.init | collection("init")%]'); + + {%- endfor %} + </script> + </body> +</html> diff --git a/testing/web-platform/tests/fetch/metadata/tools/templates/form-submission.sub.html b/testing/web-platform/tests/fetch/metadata/tools/templates/form-submission.sub.html new file mode 100644 index 0000000000..4c9c8c50f8 --- /dev/null +++ b/testing/web-platform/tests/fetch/metadata/tools/templates/form-submission.sub.html @@ -0,0 +1,87 @@ +<!DOCTYPE html> +<!-- +[%provenance%] +--> +<html lang="en"> + <meta charset="utf-8"> + <meta name="timeout" content="long"> + <title>HTTP headers on request for HTML form navigation</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + {%- if subtests|selectattr('userActivated')|list %} + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + {%- endif %} + <script src="/fetch/metadata/resources/helper.sub.js"></script> + <body> + <script> + 'use strict'; + + function induceRequest(method, url, userActivated) { + const windowName = String(Math.random()); + const form = document.createElement('form'); + const submit = document.createElement('input'); + submit.setAttribute('type', 'submit'); + form.appendChild(submit); + const win = open('about:blank', windowName); + form.setAttribute('method', method); + form.setAttribute('action', url); + form.setAttribute('target', windowName); + document.body.appendChild(form); + + // Query parameters must be expressed as form values so that they are sent + // with the submission of forms whose method is POST. + Array.from(new URL(url, location.origin).searchParams) + .forEach(([name, value]) => { + const input = document.createElement('input'); + input.setAttribute('type', 'hidden'); + input.setAttribute('name', name); + input.setAttribute('value', value); + form.appendChild(input); + }); + + return new Promise((resolve) => { + addEventListener('message', function(event) { + if (event.source === win) { + resolve(); + } + }); + + if (userActivated) { + test_driver.click(submit); + } else { + submit.click(); + } + }) + .then(() => { + form.remove(); + win.close(); + }); + } + const responseParams = { + mime: 'text/html', + body: `<script>opener.postMessage('done', '*')</${''}script>` + }; + + {%- for subtest in subtests %} + + promise_test(() => { + const key = '{{uuid()}}'; + const url = makeRequestURL(key, [% subtest.origins %], responseParams); + const userActivated = [% 'true' if subtest.userActivated else 'false' %]; + return induceRequest('[%subtest.method | default("POST")%]', url, userActivated) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%] - [%subtest.description | pad("end", " - ")%][%subtest.method | default("POST")%][%" with user activation" if subtest.userActivated%]'); + + {%- endfor %} + </script> + </body> +</html> diff --git a/testing/web-platform/tests/fetch/metadata/tools/templates/header-link.sub.html b/testing/web-platform/tests/fetch/metadata/tools/templates/header-link.sub.html new file mode 100644 index 0000000000..2831f221d5 --- /dev/null +++ b/testing/web-platform/tests/fetch/metadata/tools/templates/header-link.sub.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<!-- +[%provenance%] +--> +<html lang="en"> + <meta charset="utf-8"> + <title>HTTP headers on request for HTTP "Link" header</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/fetch/metadata/resources/helper.sub.js"></script> + <body> + <script> + 'use strict'; + + function induceRequest(url, rel, test) { + const iframe = document.createElement('iframe'); + + iframe.setAttribute( + 'src', + '/fetch/metadata/resources/header-link.py' + + `?location=${encodeURIComponent(url)}&rel=${rel}` + ); + + document.body.appendChild(iframe); + test.add_cleanup(() => iframe.remove()); + + return new Promise((resolve) => { + iframe.onload = iframe.onerror = resolve; + }); + } + + {%- for subtest in subtests %} + + promise_test((t) => { + const key = '{{uuid()}}'; + + return induceRequest( + makeRequestURL(key, [% subtest.origins %], {mime: 'text/html'}), + '[%subtest.rel%]', + t + ) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%] rel=[%subtest.rel%][%subtest.description | pad("start", " - ")%]'); + + {%- endfor %} + </script> + </body> +</html> diff --git a/testing/web-platform/tests/fetch/metadata/tools/templates/header-refresh.optional.sub.html b/testing/web-platform/tests/fetch/metadata/tools/templates/header-refresh.optional.sub.html new file mode 100644 index 0000000000..ec963d5cc0 --- /dev/null +++ b/testing/web-platform/tests/fetch/metadata/tools/templates/header-refresh.optional.sub.html @@ -0,0 +1,59 @@ +<!DOCTYPE html> +<!-- +[%provenance%] +--> +<html lang="en"> + <meta charset="utf-8"> + {%- if subtests|length > 10 %} + <meta name="timeout" content="long"> + {%- endif %} + <title>HTTP headers on request for HTTP "Refresh" header</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/fetch/metadata/resources/helper.sub.js"></script> + <body> + <script> + 'use strict'; + + function induceRequest(url, test) { + const win = window.open(); + test.add_cleanup(() => win.close()); + + win.location = `/common/refresh.py?location=${encodeURIComponent(url)}` + + return new Promise((resolve) => { + addEventListener('message', (event) => { + if (event.source === win) { + resolve(); + } + }); + }); + } + + const responseParams = { + mime: 'text/html', + body: `<script>opener.postMessage(0, '*')</${''}script>` + }; + {%- for subtest in subtests %} + + promise_test((t) => { + const key = '{{uuid()}}'; + + return induceRequest( + makeRequestURL(key, [% subtest.origins %], responseParams), t + ) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%][%subtest.description | pad("start", " - ")%]'); + + {%- endfor %} + </script> + </body> +</html> diff --git a/testing/web-platform/tests/fetch/metadata/tools/templates/script-module-import-dynamic.sub.html b/testing/web-platform/tests/fetch/metadata/tools/templates/script-module-import-dynamic.sub.html new file mode 100644 index 0000000000..653d3cdec4 --- /dev/null +++ b/testing/web-platform/tests/fetch/metadata/tools/templates/script-module-import-dynamic.sub.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<!-- +[%provenance%] +--> +<html lang="en"> + <meta charset="utf-8"> + <title>HTTP headers on request for dynamic ECMAScript module import</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/fetch/metadata/resources/helper.sub.js"></script> + <script type="module"> + 'use strict'; + {%- for subtest in subtests %} + + promise_test(() => { + const key = '{{uuid()}}'; + const url = makeRequestURL( + key, [% subtest.origins %], { mime: 'application/javascript' } + ); + + return import(url) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%][%subtest.description | pad("start", " - ")%]'); + + {%- endfor %} + </script> +</html> diff --git a/testing/web-platform/tests/fetch/metadata/tools/templates/script-module-import-static.sub.html b/testing/web-platform/tests/fetch/metadata/tools/templates/script-module-import-static.sub.html new file mode 100644 index 0000000000..c8d5f9532a --- /dev/null +++ b/testing/web-platform/tests/fetch/metadata/tools/templates/script-module-import-static.sub.html @@ -0,0 +1,53 @@ +<!DOCTYPE html> +<!-- +[%provenance%] +--> +<html lang="en"> + <meta charset="utf-8"> + <title>HTTP headers on request for static ECMAScript module import</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/fetch/metadata/resources/helper.sub.js"></script> + <body> + <script> + 'use strict'; + + function induceRequest(url) { + const script = document.createElement('script'); + script.setAttribute('type', 'module'); + script.setAttribute( + 'src', + '/fetch/metadata/resources/es-module.sub.js?moduleId=' + encodeURIComponent(url) + ); + + return new Promise((resolve, reject) => { + script.onload = resolve; + script.onerror = () => reject('Failed to load script'); + document.body.appendChild(script); + }) + .then(() => script.remove()); + } + {%- for subtest in subtests %} + + promise_test(() => { + const key = '{{uuid()}}'; + + return induceRequest( + makeRequestURL( + key, [% subtest.origins %], { mime: 'application/javascript' } + ) + ) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%][%subtest.description | pad("start", " - ")%]'); + + {%- endfor %} + </script> +</html> diff --git a/testing/web-platform/tests/fetch/metadata/tools/templates/serviceworker.https.sub.html b/testing/web-platform/tests/fetch/metadata/tools/templates/serviceworker.https.sub.html new file mode 100644 index 0000000000..8284325546 --- /dev/null +++ b/testing/web-platform/tests/fetch/metadata/tools/templates/serviceworker.https.sub.html @@ -0,0 +1,72 @@ +<!DOCTYPE html> +<!-- +[%provenance%] +--> +<!DOCTYPE html> +<html lang="en"> + <meta charset="utf-8"> + <title>HTTP headers on request for Service Workers</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/fetch/metadata/resources/helper.sub.js"></script> + <body> + <script> + 'use strict'; + + function induceRequest(t, url, options, event, clear) { + // Register a service worker and check the request header. + return navigator.serviceWorker.register(url, options) + .then((registration) => { + t.add_cleanup(() => registration.unregister()); + if (event === 'register') { + return; + } + return clear().then(() => registration.update()); + }); + } + + {%- for subtest in subtests %} + {%- set origin = subtest.origins[0]|default('httpsOrigin') %} + {%- if origin == 'httpsOrigin' or not origin %} + + promise_test((t) => { + const key = '{{uuid()}}'; + const url = makeRequestURL( + key, [% subtest.origins %], { mime: 'application/javascript' } + ); + + return induceRequest(t, url, [%subtest.options | default({}) | tojson%], 'register') + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%] - [%subtest.description | pad("end", ", ")%][%subtest.options | collection("options")%] - registration'); + + promise_test((t) => { + const key = '{{uuid()}}'; + const url = makeRequestURL( + key, [% subtest.origins %], { mime: 'application/javascript' } + ); + + return induceRequest(t, url, [%subtest.options | default({}) | tojson%], 'update', () => retrieve(key)) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%] - [%subtest.description | pad("end", ", ")%][%subtest.options | collection("options")%] - updating'); + + {%- endif %} + {%- endfor %} + </script> +</body> +</html> diff --git a/testing/web-platform/tests/fetch/metadata/tools/templates/svg-image.sub.html b/testing/web-platform/tests/fetch/metadata/tools/templates/svg-image.sub.html new file mode 100644 index 0000000000..52f7806b33 --- /dev/null +++ b/testing/web-platform/tests/fetch/metadata/tools/templates/svg-image.sub.html @@ -0,0 +1,75 @@ +<!DOCTYPE html> +<!-- +[%provenance%] +--> +<html lang="en"> + <meta charset="utf-8"> + {%- if subtests|length > 10 %} + <meta name="timeout" content="long"> + {%- endif %} + <title>HTTP headers on request for SVG "image" element source</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/fetch/metadata/resources/helper.sub.js"></script> + <body> + <script> + 'use strict'; + + const params = { + body: ` + <svg xmlns="http://www.w3.org/2000/svg" width="123" height="123"> + <rect fill="lime" width="123" height="123"/> + </svg> + `, + mime: 'image/svg+xml' + }; + + function induceRequest(t, url, attributes) { + const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + svg.setAttributeNS( + "http://www.w3.org/2000/xmlns/", + "xmlns:xlink", + "http://www.w3.org/1999/xlink" + ); + const image = document.createElementNS("http://www.w3.org/2000/svg", "image"); + image.setAttribute("href", url); + svg.appendChild(image); + + for (const [ name, value ] of Object.entries(attributes)) { + image.setAttribute(name, value); + } + + document.body.appendChild(svg); + t.add_cleanup(() => svg.remove()); + + return new Promise((resolve, reject) => { + image.onload = resolve; + image.onerror = reject; + }); + } + + {%- for subtest in subtests %} + + promise_test((t) => { + const key = '{{uuid()}}'; + + return induceRequest( + t, + makeRequestURL(key, [% subtest.origins %], params), + [%subtest.elementAttrs | default({}) | tojson%] + ) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%][%subtest.description | pad("start", " - ")%] [%subtest.elementAttrs | collection("attributes")%]'); + + {%- endfor %} + </script> + </body> +</html> diff --git a/testing/web-platform/tests/fetch/metadata/tools/templates/window-history.sub.html b/testing/web-platform/tests/fetch/metadata/tools/templates/window-history.sub.html new file mode 100644 index 0000000000..286d019887 --- /dev/null +++ b/testing/web-platform/tests/fetch/metadata/tools/templates/window-history.sub.html @@ -0,0 +1,134 @@ +<!DOCTYPE html> +<!-- +[%provenance%] +--> +<html lang="en"> + <meta charset="utf-8"> + {%- if subtests|length > 10 %} + <meta name="timeout" content="long"> + {%- endif %} + <title>HTTP headers on request for navigation via the HTML History API</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + {%- if subtests|selectattr('userActivated')|list %} + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + {%- endif %} + <script src="/fetch/metadata/resources/helper.sub.js"></script> + <body> + <script> + 'use strict'; + + const whenDone = (win) => { + return new Promise((resolve) => { + addEventListener('message', function handle(event) { + if (event.source === win) { + resolve(); + removeEventListener('message', handle); + } + }); + }) + }; + + /** + * Prime the UA's session history such that the location of the request is + * immediately behind the current entry. Because the location may not be + * same-origin with the current browsing context, this must be done via a + * true navigation and not, e.g. the `history.pushState` API. The initial + * navigation will alter the WPT server's internal state; in order to avoid + * false positives, clear that state prior to initiating the second + * navigation via `history.back`. + */ + function induceBackRequest(url, test, clear) { + const win = window.open(url); + + test.add_cleanup(() => win.close()); + + return whenDone(win) + .then(clear) + .then(() => win.history.back()) + .then(() => whenDone(win)); + } + + /** + * Prime the UA's session history such that the location of the request is + * immediately ahead of the current entry. Because the location may not be + * same-origin with the current browsing context, this must be done via a + * true navigation and not, e.g. the `history.pushState` API. The initial + * navigation will alter the WPT server's internal state; in order to avoid + * false positives, clear that state prior to initiating the second + * navigation via `history.forward`. + */ + function induceForwardRequest(url, test, clear) { + const win = window.open(messageOpenerUrl); + + test.add_cleanup(() => win.close()); + + return whenDone(win) + .then(() => win.location = url) + .then(() => whenDone(win)) + .then(clear) + .then(() => win.history.go(-2)) + .then(() => whenDone(win)) + .then(() => win.history.forward()) + .then(() => whenDone(win)); + } + + const messageOpenerUrl = new URL( + '/fetch/metadata/resources/message-opener.html', location + ); + // For these tests to function, replacement must *not* be enabled during + // navigation. Assignment must therefore take place after the document has + // completely loaded [1]. This event is not directly observable, but it is + // scheduled as a task immediately following the global object's `load` + // event [2]. By queuing a task during the dispatch of the `load` event, + // navigation can be consistently triggered without replacement. + // + // [1] https://html.spec.whatwg.org/multipage/history.html#location-object-setter-navigate + // [2] https://html.spec.whatwg.org/multipage/parsing.html#the-end + const responseParams = { + mime: 'text/html', + body: `<script> + window.addEventListener('load', () => { + set`+`Timeout(() => location.assign('${messageOpenerUrl}')); + }); + <`+`/script>` + }; + {%- for subtest in subtests %} + + promise_test((t) => { + const key = '{{uuid()}}'; + const url = makeRequestURL(key, [% subtest.origins %], responseParams); + + return induceBackRequest(url, t, () => retrieve(key)) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%] - [%subtest.description | pad("right", " - ")%]history.back[%subtest.api%][% " with user activation" if subtest.userActivated%]'); + + promise_test((t) => { + const key = '{{uuid()}}'; + const url = makeRequestURL(key, [% subtest.origins %], responseParams); + + return induceForwardRequest(url, t, () => retrieve(key)) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%] - [%subtest.description | pad("right", " - ")%]history.forward[%subtest.api%][% " with user activation" if subtest.userActivated%]'); + + {%- endfor %} + </script> + </body> +</html> diff --git a/testing/web-platform/tests/fetch/metadata/tools/templates/window-location.sub.html b/testing/web-platform/tests/fetch/metadata/tools/templates/window-location.sub.html new file mode 100644 index 0000000000..96f3912361 --- /dev/null +++ b/testing/web-platform/tests/fetch/metadata/tools/templates/window-location.sub.html @@ -0,0 +1,128 @@ +<!DOCTYPE html> +<!-- +[%provenance%] +--> +<html lang="en"> + <meta charset="utf-8"> + {%- if subtests|length > 10 %} + <meta name="timeout" content="long"> + {%- endif %} + <title>HTTP headers on request for navigation via the HTML Location API</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + {%- if subtests|selectattr('userActivated')|list %} + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + {%- endif %} + <script src="/fetch/metadata/resources/helper.sub.js"></script> + <body> + <script> + 'use strict'; + + function induceRequest(url, navigate, userActivated) { + const win = window.open(); + + return new Promise((resolve) => { + addEventListener('message', function(event) { + if (event.source === win) { + resolve(); + } + }); + + if (userActivated) { + test_driver.bless('enable user activation', () => { + navigate(win, url); + }); + } else { + navigate(win, url); + } + }) + .then(() => win.close()); + } + + const responseParams = { + mime: 'text/html', + body: `<script>opener.postMessage('done', '*')</${''}script>` + }; + {%- for subtest in subtests %} + + promise_test(() => { + const key = '{{uuid()}}'; + const url = makeRequestURL(key, [% subtest.origins %], responseParams); + + const navigate = (win, path) => { + win.location = path; + }; + return induceRequest(url, navigate, [% 'true' if subtest.userActivated else 'false' %]) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%] - [%subtest.description | pad("end", " - ")%]location[% " with user activation" if subtest.userActivated%]'); + + promise_test(() => { + const key = '{{uuid()}}'; + const url = makeRequestURL(key, [% subtest.origins %], responseParams); + + const navigate = (win, path) => { + win.location.href = path; + }; + return induceRequest(url, navigate, [% 'true' if subtest.userActivated else 'false' %]) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%] - [%subtest.description | pad("end", " - ")%]location.href[% " with user activation" if subtest.userActivated%]'); + + promise_test(() => { + const key = '{{uuid()}}'; + const url = makeRequestURL(key, [% subtest.origins %], responseParams); + + const navigate = (win, path) => { + win.location.assign(path); + }; + return induceRequest(url, navigate, [% 'true' if subtest.userActivated else 'false' %]) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%] - [%subtest.description | pad("end", " - ")%]location.assign[% " with user activation" if subtest.userActivated%]'); + + promise_test(() => { + const key = '{{uuid()}}'; + const url = makeRequestURL(key, [% subtest.origins %], responseParams); + + const navigate = (win, path) => { + win.location.replace(path); + }; + return induceRequest(url, navigate, [% 'true' if subtest.userActivated else 'false' %]) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%] - [%subtest.description | pad("end", " - ")%]location.replace[% " with user activation" if subtest.userActivated%]'); + + {%- endfor %} + </script> + </body> +</html> diff --git a/testing/web-platform/tests/fetch/metadata/tools/templates/worker-dedicated-constructor.sub.html b/testing/web-platform/tests/fetch/metadata/tools/templates/worker-dedicated-constructor.sub.html new file mode 100644 index 0000000000..fede5965d3 --- /dev/null +++ b/testing/web-platform/tests/fetch/metadata/tools/templates/worker-dedicated-constructor.sub.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> +<!-- +[%provenance%] +--> +<html lang="en"> + <meta charset="utf-8"> + <title>HTTP headers on request for dedicated worker via the "Worker" constructor</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/fetch/metadata/resources/helper.sub.js"></script> + <script type="module"> + 'use strict'; + function induceRequest(url, options) { + return new Promise((resolve, reject) => { + const worker = new Worker(url, options); + worker.onmessage = resolve; + worker.onerror = reject; + }); + } + + {%- for subtest in subtests %} + + promise_test(() => { + const key = '{{uuid()}}'; + const url = makeRequestURL( + key, + [% subtest.origins %], + { mime: 'application/javascript', body: 'postMessage("")' } + ); + + return induceRequest(url + {%- if subtest.options -%} + , [% subtest.options | tojson %] + {%- endif -%} + ) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%] - [%subtest.description | pad("end", ", ")%][%subtest.options|collection("options")%]'); + + {%- endfor %} + </script> +</html> diff --git a/testing/web-platform/tests/fetch/metadata/tools/templates/worker-dedicated-importscripts.sub.html b/testing/web-platform/tests/fetch/metadata/tools/templates/worker-dedicated-importscripts.sub.html new file mode 100644 index 0000000000..93e6374d54 --- /dev/null +++ b/testing/web-platform/tests/fetch/metadata/tools/templates/worker-dedicated-importscripts.sub.html @@ -0,0 +1,54 @@ +<!DOCTYPE html> +<!-- +[%provenance%] +--> +<html lang="en"> + <meta charset="utf-8"> + <title>HTTP headers on request for dedicated worker via the "importScripts" API</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/fetch/metadata/resources/helper.sub.js"></script> + <script type="module"> + 'use strict'; + function induceRequest(url, options) { + const src = ` + importScripts('${url}'); + postMessage('done'); + `; + const workerUrl = URL.createObjectURL( + new Blob([src], { type: 'application/javascript' }) + ); + return new Promise((resolve, reject) => { + const worker = new Worker(workerUrl, options); + worker.onmessage = resolve; + worker.onerror = reject; + }); + } + + {%- for subtest in subtests %} + + promise_test(() => { + const key = '{{uuid()}}'; + const url = makeRequestURL( + key, [% subtest.origins %], { mime: 'application/javascript' } + ); + + return induceRequest(url + {%- if subtest.options -%} + , [% subtest.options | tojson %] + {%- endif -%} + ) + .then(() => retrieve(key)) + .then((headers) => { + {%- if subtest.expected == none %} + assert_not_own_property(headers, '[%subtest.headerName%]'); + {%- else %} + assert_own_property(headers, '[%subtest.headerName%]'); + assert_array_equals(headers['[%subtest.headerName%]'], ['[%subtest.expected%]']); + {%- endif %} + }); + }, '[%subtest.headerName%][%subtest.description | pad("start", " - ")%]'); + + {%- endfor %} + </script> +</html> |