summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/resource-timing
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/resource-timing')
-rw-r--r--testing/web-platform/tests/resource-timing/304-response-recorded.html53
-rw-r--r--testing/web-platform/tests/resource-timing/CodingConventions.md77
-rw-r--r--testing/web-platform/tests/resource-timing/META.yml6
-rw-r--r--testing/web-platform/tests/resource-timing/SO-XO-SO-redirect-chain-tao.https.html64
-rw-r--r--testing/web-platform/tests/resource-timing/SyntheticResponse.py50
-rw-r--r--testing/web-platform/tests/resource-timing/TAO-match.html82
-rw-r--r--testing/web-platform/tests/resource-timing/TAO-port-mismatch-means-crossorigin.html46
-rw-r--r--testing/web-platform/tests/resource-timing/__init__.py0
-rw-r--r--testing/web-platform/tests/resource-timing/body-size-cross-origin.https.html63
-rw-r--r--testing/web-platform/tests/resource-timing/buffer-full-add-after-full-event.html27
-rw-r--r--testing/web-platform/tests/resource-timing/buffer-full-add-entries-during-callback-that-drop.html29
-rw-r--r--testing/web-platform/tests/resource-timing/buffer-full-add-entries-during-callback.html28
-rw-r--r--testing/web-platform/tests/resource-timing/buffer-full-add-then-clear.html31
-rw-r--r--testing/web-platform/tests/resource-timing/buffer-full-decrease-buffer-during-callback.html26
-rw-r--r--testing/web-platform/tests/resource-timing/buffer-full-eventually.html31
-rw-r--r--testing/web-platform/tests/resource-timing/buffer-full-increase-buffer-during-callback.html26
-rw-r--r--testing/web-platform/tests/resource-timing/buffer-full-inspect-buffer-during-callback.html30
-rw-r--r--testing/web-platform/tests/resource-timing/buffer-full-set-to-current-buffer.html34
-rw-r--r--testing/web-platform/tests/resource-timing/buffer-full-store-and-clear-during-callback.html36
-rw-r--r--testing/web-platform/tests/resource-timing/buffer-full-then-decreased.html29
-rw-r--r--testing/web-platform/tests/resource-timing/buffer-full-then-increased.html28
-rw-r--r--testing/web-platform/tests/resource-timing/buffer-full-when-populate-entries.html30
-rw-r--r--testing/web-platform/tests/resource-timing/buffered-flag.any.js18
-rw-r--r--testing/web-platform/tests/resource-timing/cached-image-gets-single-entry.html67
-rw-r--r--testing/web-platform/tests/resource-timing/clear-resource-timings.html22
-rw-r--r--testing/web-platform/tests/resource-timing/connection-reuse.html56
-rw-r--r--testing/web-platform/tests/resource-timing/connection-reuse.https.html25
-rw-r--r--testing/web-platform/tests/resource-timing/content-type-parsing.html76
-rw-r--r--testing/web-platform/tests/resource-timing/content-type.html117
-rw-r--r--testing/web-platform/tests/resource-timing/cors-preflight.any.js49
-rw-r--r--testing/web-platform/tests/resource-timing/cross-origin-iframe.html33
-rw-r--r--testing/web-platform/tests/resource-timing/cross-origin-redirects.html102
-rw-r--r--testing/web-platform/tests/resource-timing/cross-origin-start-end-time-with-redirects.html38
-rw-r--r--testing/web-platform/tests/resource-timing/cross-origin-status-codes.html70
-rw-r--r--testing/web-platform/tests/resource-timing/delivery-type.tentative.any.js90
-rw-r--r--testing/web-platform/tests/resource-timing/document-domain-no-impact-opener.html16
-rw-r--r--testing/web-platform/tests/resource-timing/entries-for-network-errors.sub.https.html35
-rw-r--r--testing/web-platform/tests/resource-timing/entry-attributes.html39
-rw-r--r--testing/web-platform/tests/resource-timing/event-source-timing.html32
-rw-r--r--testing/web-platform/tests/resource-timing/fetch-cross-origin-redirect.https.html33
-rw-r--r--testing/web-platform/tests/resource-timing/font-timestamps.html62
-rw-r--r--testing/web-platform/tests/resource-timing/frameset-timing.html6
-rw-r--r--testing/web-platform/tests/resource-timing/idlharness.any.js24
-rw-r--r--testing/web-platform/tests/resource-timing/iframe-failed-commit.html131
-rw-r--r--testing/web-platform/tests/resource-timing/iframe-non-html.html23
-rw-r--r--testing/web-platform/tests/resource-timing/iframe-redirect-without-location.html17
-rw-r--r--testing/web-platform/tests/resource-timing/iframe-sequence-of-events.html24
-rw-r--r--testing/web-platform/tests/resource-timing/iframe-with-download.html24
-rw-r--r--testing/web-platform/tests/resource-timing/image-sequence-of-events.html29
-rw-r--r--testing/web-platform/tests/resource-timing/initiator-type-for-script.html67
-rw-r--r--testing/web-platform/tests/resource-timing/initiator-type/audio.html34
-rw-r--r--testing/web-platform/tests/resource-timing/initiator-type/dynamic-insertion.html41
-rw-r--r--testing/web-platform/tests/resource-timing/initiator-type/embed.html20
-rw-r--r--testing/web-platform/tests/resource-timing/initiator-type/frameset.html22
-rw-r--r--testing/web-platform/tests/resource-timing/initiator-type/iframe.html19
-rw-r--r--testing/web-platform/tests/resource-timing/initiator-type/img-srcset.html21
-rw-r--r--testing/web-platform/tests/resource-timing/initiator-type/img.html19
-rw-r--r--testing/web-platform/tests/resource-timing/initiator-type/input.html19
-rw-r--r--testing/web-platform/tests/resource-timing/initiator-type/link.html35
-rw-r--r--testing/web-platform/tests/resource-timing/initiator-type/misc.html31
-rw-r--r--testing/web-platform/tests/resource-timing/initiator-type/picture.html39
-rw-r--r--testing/web-platform/tests/resource-timing/initiator-type/resources/initiator-type-test.js15
-rw-r--r--testing/web-platform/tests/resource-timing/initiator-type/script.html26
-rw-r--r--testing/web-platform/tests/resource-timing/initiator-type/style.html45
-rw-r--r--testing/web-platform/tests/resource-timing/initiator-type/svg.html23
-rw-r--r--testing/web-platform/tests/resource-timing/initiator-type/video.html32
-rw-r--r--testing/web-platform/tests/resource-timing/initiator-type/workers.html23
-rw-r--r--testing/web-platform/tests/resource-timing/input-sequence-of-events.html21
-rw-r--r--testing/web-platform/tests/resource-timing/interim-response-times.h2.html74
-rw-r--r--testing/web-platform/tests/resource-timing/interim-response-times.html72
-rw-r--r--testing/web-platform/tests/resource-timing/link-sequence-of-events.html30
-rw-r--r--testing/web-platform/tests/resource-timing/load-from-mem-cache-transfer-size.html65
-rw-r--r--testing/web-platform/tests/resource-timing/nested-context-navigations-embed.html36
-rw-r--r--testing/web-platform/tests/resource-timing/nested-context-navigations-iframe.html32
-rw-r--r--testing/web-platform/tests/resource-timing/nested-context-navigations-object.html37
-rw-r--r--testing/web-platform/tests/resource-timing/nested-nav-fallback-timing.html33
-rw-r--r--testing/web-platform/tests/resource-timing/nextHopProtocol-is-tao-protected.https.html49
-rw-r--r--testing/web-platform/tests/resource-timing/no-entries-for-cross-origin-css-fetched-memory-cache.sub.html53
-rw-r--r--testing/web-platform/tests/resource-timing/no-entries-for-cross-origin-css-fetched.sub.html39
-rw-r--r--testing/web-platform/tests/resource-timing/object-not-found-adds-entry.html37
-rw-r--r--testing/web-platform/tests/resource-timing/object-not-found-after-TAO-cross-origin-redirect.html47
-rw-r--r--testing/web-platform/tests/resource-timing/object-not-found-after-cross-origin-redirect.html42
-rw-r--r--testing/web-platform/tests/resource-timing/opaque-origin.html46
-rw-r--r--testing/web-platform/tests/resource-timing/ping-rt-entries.html29
-rw-r--r--testing/web-platform/tests/resource-timing/queue-entry-regardless-buffer-size.html38
-rw-r--r--testing/web-platform/tests/resource-timing/redirects.html61
-rw-r--r--testing/web-platform/tests/resource-timing/render-blocking-status-link.html222
-rw-r--r--testing/web-platform/tests/resource-timing/render-blocking-status-script.html196
-rw-r--r--testing/web-platform/tests/resource-timing/resource-ignore-data-url.html39
-rw-r--r--testing/web-platform/tests/resource-timing/resource-reload-TAO.html18
-rw-r--r--testing/web-platform/tests/resource-timing/resource-timing-level1.js476
-rw-r--r--testing/web-platform/tests/resource-timing/resource-timing-level1.sub.html19
-rw-r--r--testing/web-platform/tests/resource-timing/resource_connection_reuse_mixed_content.html55
-rw-r--r--testing/web-platform/tests/resource-timing/resource_connection_reuse_mixed_content_redirect.html55
-rw-r--r--testing/web-platform/tests/resource-timing/resource_dedicated_worker.html28
-rw-r--r--testing/web-platform/tests/resource-timing/resource_nested_dedicated_worker.worker.js17
-rw-r--r--testing/web-platform/tests/resource-timing/resource_reparenting.html53
-rw-r--r--testing/web-platform/tests/resource-timing/resource_subframe_self_navigation.html53
-rw-r--r--testing/web-platform/tests/resource-timing/resource_timing.worker.js64
-rw-r--r--testing/web-platform/tests/resource-timing/resource_timing_content_length.html35
-rw-r--r--testing/web-platform/tests/resource-timing/resources/200.https.asis5
-rw-r--r--testing/web-platform/tests/resource-timing/resources/200_empty.asis3
-rw-r--r--testing/web-platform/tests/resource-timing/resources/204_empty.asis3
-rw-r--r--testing/web-platform/tests/resource-timing/resources/205_empty.asis3
-rw-r--r--testing/web-platform/tests/resource-timing/resources/TAOResponse.py64
-rw-r--r--testing/web-platform/tests/resource-timing/resources/blank-with-tao.html10
-rw-r--r--testing/web-platform/tests/resource-timing/resources/blank-with-tao.html.headers1
-rw-r--r--testing/web-platform/tests/resource-timing/resources/blue-with-tao.pngbin0 -> 1010 bytes
-rw-r--r--testing/web-platform/tests/resource-timing/resources/blue-with-tao.png.headers1
-rw-r--r--testing/web-platform/tests/resource-timing/resources/blue.pngbin0 -> 1010 bytes
-rw-r--r--testing/web-platform/tests/resource-timing/resources/buffer-full-utilities.js75
-rw-r--r--testing/web-platform/tests/resource-timing/resources/cacheable-and-validated.py30
-rw-r--r--testing/web-platform/tests/resource-timing/resources/close.html1
-rw-r--r--testing/web-platform/tests/resource-timing/resources/connection-reuse-test.js63
-rw-r--r--testing/web-platform/tests/resource-timing/resources/content-type.py5
-rw-r--r--testing/web-platform/tests/resource-timing/resources/cors-ahem.py19
-rw-r--r--testing/web-platform/tests/resource-timing/resources/csp-default-none.html3
-rw-r--r--testing/web-platform/tests/resource-timing/resources/csp-default-none.html.headers2
-rw-r--r--testing/web-platform/tests/resource-timing/resources/delay-css.py6
-rw-r--r--testing/web-platform/tests/resource-timing/resources/delay-load.html4
-rw-r--r--testing/web-platform/tests/resource-timing/resources/document-domain-no-impact.html28
-rw-r--r--testing/web-platform/tests/resource-timing/resources/document-navigated.html12
-rw-r--r--testing/web-platform/tests/resource-timing/resources/document-refreshed.html12
-rw-r--r--testing/web-platform/tests/resource-timing/resources/document-that-navigates.html11
-rw-r--r--testing/web-platform/tests/resource-timing/resources/document-that-refreshes.html9
-rw-r--r--testing/web-platform/tests/resource-timing/resources/download.asis6
-rw-r--r--testing/web-platform/tests/resource-timing/resources/embed-navigate-back.html18
-rw-r--r--testing/web-platform/tests/resource-timing/resources/embed-navigate.html18
-rw-r--r--testing/web-platform/tests/resource-timing/resources/embed-refresh.html18
-rw-r--r--testing/web-platform/tests/resource-timing/resources/empty.js1
-rw-r--r--testing/web-platform/tests/resource-timing/resources/empty.py3
-rw-r--r--testing/web-platform/tests/resource-timing/resources/empty_script.js0
-rw-r--r--testing/web-platform/tests/resource-timing/resources/empty_style.css1
-rw-r--r--testing/web-platform/tests/resource-timing/resources/entry-invariants.js510
-rw-r--r--testing/web-platform/tests/resource-timing/resources/eventsource.py3
-rw-r--r--testing/web-platform/tests/resource-timing/resources/fake_responses.html19
-rw-r--r--testing/web-platform/tests/resource-timing/resources/fake_responses.py42
-rw-r--r--testing/web-platform/tests/resource-timing/resources/fake_responses_https.sub.html18
-rw-r--r--testing/web-platform/tests/resource-timing/resources/fake_responses_https_redirect.sub.html20
-rw-r--r--testing/web-platform/tests/resource-timing/resources/frame-timing.js63
-rw-r--r--testing/web-platform/tests/resource-timing/resources/frameset-timing-frame.html8
-rw-r--r--testing/web-platform/tests/resource-timing/resources/green-frame.html7
-rw-r--r--testing/web-platform/tests/resource-timing/resources/green.html10
-rw-r--r--testing/web-platform/tests/resource-timing/resources/green.html.headers1
-rw-r--r--testing/web-platform/tests/resource-timing/resources/gzip_xml.py23
-rw-r--r--testing/web-platform/tests/resource-timing/resources/header-delay.h2.py25
-rw-r--r--testing/web-platform/tests/resource-timing/resources/header-delay.py29
-rw-r--r--testing/web-platform/tests/resource-timing/resources/iframe-TAO-crossorigin-port.sub.html31
-rw-r--r--testing/web-platform/tests/resource-timing/resources/iframe-load-from-mem-cache-transfer-size.html24
-rw-r--r--testing/web-platform/tests/resource-timing/resources/iframe-navigate-back.html18
-rw-r--r--testing/web-platform/tests/resource-timing/resources/iframe-navigate.html18
-rw-r--r--testing/web-platform/tests/resource-timing/resources/iframe-refresh.html18
-rw-r--r--testing/web-platform/tests/resource-timing/resources/iframe-reload-TAO.html25
-rw-r--r--testing/web-platform/tests/resource-timing/resources/iframe-setdomain.sub.html14
-rw-r--r--testing/web-platform/tests/resource-timing/resources/iframe-with-delay.sub.html3
-rw-r--r--testing/web-platform/tests/resource-timing/resources/iframe_TAO_match_origin.html21
-rw-r--r--testing/web-platform/tests/resource-timing/resources/import.sub.css1
-rw-r--r--testing/web-platform/tests/resource-timing/resources/importer.css1
-rw-r--r--testing/web-platform/tests/resource-timing/resources/importer.js2
-rw-r--r--testing/web-platform/tests/resource-timing/resources/importer_async.js2
-rw-r--r--testing/web-platform/tests/resource-timing/resources/importer_dynamic.css1
-rw-r--r--testing/web-platform/tests/resource-timing/resources/importer_print.css1
-rw-r--r--testing/web-platform/tests/resource-timing/resources/inject_resource_test.html7
-rw-r--r--testing/web-platform/tests/resource-timing/resources/invalid.jpg1
-rw-r--r--testing/web-platform/tests/resource-timing/resources/manifest.json4
-rw-r--r--testing/web-platform/tests/resource-timing/resources/multi_redirect.py59
-rw-r--r--testing/web-platform/tests/resource-timing/resources/navigate_back.html7
-rw-r--r--testing/web-platform/tests/resource-timing/resources/nested-contexts.js87
-rw-r--r--testing/web-platform/tests/resource-timing/resources/nested.css10
-rw-r--r--testing/web-platform/tests/resource-timing/resources/no-entries-for-cross-origin-css-fetched-memory-cache-iframe.sub.html8
-rw-r--r--testing/web-platform/tests/resource-timing/resources/notify_parent.html4
-rw-r--r--testing/web-platform/tests/resource-timing/resources/object-navigate-back.html18
-rw-r--r--testing/web-platform/tests/resource-timing/resources/object-navigate.html18
-rw-r--r--testing/web-platform/tests/resource-timing/resources/object-refresh.html18
-rw-r--r--testing/web-platform/tests/resource-timing/resources/observe-entry.js25
-rw-r--r--testing/web-platform/tests/resource-timing/resources/preflight.py9
-rw-r--r--testing/web-platform/tests/resource-timing/resources/redirect-cors.py22
-rw-r--r--testing/web-platform/tests/resource-timing/resources/redirect-without-location.py2
-rw-r--r--testing/web-platform/tests/resource-timing/resources/resource-loaders.js174
-rw-r--r--testing/web-platform/tests/resource-timing/resources/resource-timing-content-length.py20
-rw-r--r--testing/web-platform/tests/resource-timing/resources/resource_timing_test0.css4
-rw-r--r--testing/web-platform/tests/resource-timing/resources/resource_timing_test0.html15
-rw-r--r--testing/web-platform/tests/resource-timing/resources/resource_timing_test0.js3
-rw-r--r--testing/web-platform/tests/resource-timing/resources/resource_timing_test0.js.headers2
-rw-r--r--testing/web-platform/tests/resource-timing/resources/resource_timing_test0.pngbin0 -> 249 bytes
-rw-r--r--testing/web-platform/tests/resource-timing/resources/resource_timing_test0.xml6
-rw-r--r--testing/web-platform/tests/resource-timing/resources/self_navigation.html1
-rw-r--r--testing/web-platform/tests/resource-timing/resources/shared-worker.js3
-rw-r--r--testing/web-platform/tests/resource-timing/resources/sizes-helper.js16
-rw-r--r--testing/web-platform/tests/resource-timing/resources/status-code.py8
-rw-r--r--testing/web-platform/tests/resource-timing/resources/sw-install.html58
-rw-r--r--testing/web-platform/tests/resource-timing/resources/sw.js3
-rw-r--r--testing/web-platform/tests/resource-timing/resources/tao-response.js13
-rw-r--r--testing/web-platform/tests/resource-timing/resources/webperftestharnessextension.js188
-rw-r--r--testing/web-platform/tests/resource-timing/resources/worker_with_images.js22
-rw-r--r--testing/web-platform/tests/resource-timing/response-status-code.html165
-rw-r--r--testing/web-platform/tests/resource-timing/same-origin-from-cross-origin-redirect.html31
-rw-r--r--testing/web-platform/tests/resource-timing/script-rt-entries.html37
-rw-r--r--testing/web-platform/tests/resource-timing/secure-iframe-in-insecure-context.html32
-rw-r--r--testing/web-platform/tests/resource-timing/shared-worker-rt-entry.html26
-rw-r--r--testing/web-platform/tests/resource-timing/sizes-cache.any.js55
-rw-r--r--testing/web-platform/tests/resource-timing/sizes-redirect-img.html57
-rw-r--r--testing/web-platform/tests/resource-timing/sizes-redirect.any.js62
-rw-r--r--testing/web-platform/tests/resource-timing/sleep.py13
-rw-r--r--testing/web-platform/tests/resource-timing/status-codes-create-entry.html40
-rw-r--r--testing/web-platform/tests/resource-timing/supported_resource_type.any.js24
-rw-r--r--testing/web-platform/tests/resource-timing/test_resource_timing.html24
-rw-r--r--testing/web-platform/tests/resource-timing/test_resource_timing.https.html24
-rw-r--r--testing/web-platform/tests/resource-timing/test_resource_timing.js228
-rw-r--r--testing/web-platform/tests/resource-timing/tojson.html71
-rw-r--r--testing/web-platform/tests/resource-timing/workerStart-tao-protected.https.html76
-rw-r--r--testing/web-platform/tests/resource-timing/worklet-rt-entries.https.html19
-rw-r--r--testing/web-platform/tests/resource-timing/xhr-resource-timing.html29
213 files changed, 8027 insertions, 0 deletions
diff --git a/testing/web-platform/tests/resource-timing/304-response-recorded.html b/testing/web-platform/tests/resource-timing/304-response-recorded.html
new file mode 100644
index 0000000000..9e1bb3045c
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/304-response-recorded.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing - cached resources generate performance entries</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help"
+ href="https://www.w3.org/TR/resource-timing-2/#resources-included-in-the-performanceresourcetiming-interface"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/entry-invariants.js"></script>
+<script src="resources/resource-loaders.js"></script>
+</head>
+<body>
+<h1>Description</h1>
+<p>This test validates that a 304 Not Modified resource appears in the
+Performance Timeline.</p>
+<script>
+// Need to fetch the same resource twice; the first will get a 200 response but
+// the second request should be cached and get a 304.
+promise_test(async () => {
+ performance.clearResourceTimings();
+
+ const unique = Math.random();
+ const path = `resources/fake_responses.py?tag=${unique}`;
+
+ await load.xhr_sync(path);
+ await load.xhr_sync(path, {"If-None-Match": `${unique}`});
+ const entries = await new Promise(resolve => {
+ const accumulator = [];
+ new PerformanceObserver(entry_list => {
+ entry_list.getEntries().forEach(entry => {
+ accumulator.push(entry);
+ });
+ if (accumulator.length >= 2) {
+ resolve(accumulator);
+ }
+ }).observe({'type': 'resource', 'buffered': true});
+ });
+
+
+ if (entries.length != 2) {
+ throw new Error(`Expecting 2 but got ${entries.length} entries`);
+ }
+
+ assert_equals(entries[0].name, entries[1].name,
+ "Both entries should have the same name");
+ invariants.assert_tao_pass_no_redirect_http(entries[0]);
+ invariants.assert_tao_pass_304_not_modified_http(entries[1]);
+}, "304 responses should still show up in the PerformanceTimeline");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/CodingConventions.md b/testing/web-platform/tests/resource-timing/CodingConventions.md
new file mode 100644
index 0000000000..39b8d13435
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/CodingConventions.md
@@ -0,0 +1,77 @@
+For [Resource Timing][1] tests, we want to have a consistent and clear coding
+style. The goals of this style are to:
+* Make it easier for new contributors to find their way around
+* Help improve readability and maintainability
+* Help us understand which parts of the spec are tested or not
+Lots of the following rules are arbitrary but the value is realized in
+consistency instead of adhering to the 'perfect' style.
+
+We want the test suite to be navigable. Developers should be able to easily
+find the file or test that is relevant to their work.
+* Tests should be arranged in files according to which piece of the spec they
+ test
+* Files should be named using a consistent pattern
+* HTML files should include useful meta tags
+ * `<title>` for controlling labels in results pages
+ * `<link rel="help">` to point at the relevant piece of the spec
+
+We want the test suite to run consistently. Flaky tests are counterproductive.
+* Prefer `promise_test` to `async_test`
+ * Note that there’s [still potential for some concurrency][2]; use
+ `add_cleanup()` if needed
+
+We want the tests to be readable. Tests should be written in a modern style
+with recurring patterns.
+* 80 character line limits where we can
+* Consistent use of anonymous functions
+ * prefer
+ ```
+ const func1 = param1 => {
+ body();
+ }
+ const func2 = (param1, param2) => {
+ body();
+ }
+ fn(param => {
+ body();
+ });
+ ```
+
+ over
+
+ ```
+ function func1(param1) {
+ body();
+ }
+ function func2(param1, param2) {
+ body();
+ }
+ fn(function(param) {
+ body();
+ });
+ ```
+
+* Prefer `const` (or, if needed, `let`) to `var`
+* Contain use of ‘.sub’ in filenames to known helper utilities where possible
+ * E.g. prefer use of get-host-info.sub.js to `{{host}}` or `{{ports[0]}}`
+ expressions
+* Avoid use of webperftestharness[extension].js as it’s a layer of cognitive
+ overhead between test content and test intent
+ * Helper .js files are still encouraged where it makes sense but we want
+ to avoid a testing framework that is specific to Resource Timing (or
+ web performance APIs, in general).
+* Prefer [`fetch_tests_from_window`][3] to collect test results from embedded
+ iframes instead of hand-rolled `postMessage` approaches
+* Use the [`assert_*`][4] family of functions to check conformance to the spec
+ but throw exceptions explicitly when the test itself is broken.
+ * A failed assert indicates "the implementation doesn't conform to the
+ spec"
+ * Other uncaught exceptions indicate "the test case itself has a bug"
+* Where possible, we want tests to be scalable - adding another test case
+ should be as simple as calling the tests with new parameters, rather than
+ copying an existing test and modifying it.
+
+[1]: https://www.w3.org/TR/resource-timing-2/
+[2]: https://web-platform-tests.org/writing-tests/testharness-api.html#promise-tests
+[3]: https://web-platform-tests.org/writing-tests/testharness-api.html#consolidating-tests-from-other-documents
+[4]: https://web-platform-tests.org/writing-tests/testharness-api.html#list-of-assertions
diff --git a/testing/web-platform/tests/resource-timing/META.yml b/testing/web-platform/tests/resource-timing/META.yml
new file mode 100644
index 0000000000..662c42cb66
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/META.yml
@@ -0,0 +1,6 @@
+spec: https://w3c.github.io/resource-timing/
+suggested_reviewers:
+ - plehegar
+ - zqzhang
+ - igrigorik
+ - yoavweiss
diff --git a/testing/web-platform/tests/resource-timing/SO-XO-SO-redirect-chain-tao.https.html b/testing/web-platform/tests/resource-timing/SO-XO-SO-redirect-chain-tao.https.html
new file mode 100644
index 0000000000..e6568910e4
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/SO-XO-SO-redirect-chain-tao.https.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>This test validates resource timing information for a same-origin=>cross-origin=>same-origin redirect chain without Timing-Allow-Origin.</title>
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-cross-origin-resources"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/resource-loaders.js"></script>
+<script src="resources/entry-invariants.js"></script>
+</head>
+<body>
+<script>
+const {HTTPS_REMOTE_ORIGIN} = get_host_info();
+const SAME_ORIGIN = location.origin;
+// Same-Origin => Cross-Origin => Same-Origin => Same-Origin redirect chain
+let destUrl = `${SAME_ORIGIN}/resource-timing/resources/multi_redirect.py?`;
+destUrl += `page_origin=${SAME_ORIGIN}`;
+destUrl += `&cross_origin=${HTTPS_REMOTE_ORIGIN}`;
+destUrl += `&final_resource=/resource-timing/resources/blank_page_green.htm`;
+
+// No TAO in the redirect chain
+attribute_test(
+ load.iframe, destUrl,
+ invariants.assert_cross_origin_redirected_resource,
+ "Verify that cross origin resources' timings are not exposed when " +
+ "same-origin=>cross-origin=>same-origin redirects have no " +
+ "`Timing-Allow-Origin:` headers.");
+
+// Partial TAO in the redirect chain
+destUrl += '&tao_steps=2';
+attribute_test(
+ load.iframe, destUrl,
+ invariants.assert_cross_origin_redirected_resource,
+ "Verify that cross origin resources' timings are not exposed when " +
+ "same-origin=>cross-origin=>same-origin redirects have " +
+ "`Timing-Allow-Origin:` headers only on some of the responses.");
+
+// Cross-origin => Cross-Origin => Same-Origin => Same-Origin redirect chain.
+destUrl = `${HTTPS_REMOTE_ORIGIN}/resource-timing/resources/multi_redirect.py?`;
+destUrl += `page_origin=${SAME_ORIGIN}`;
+destUrl += `&cross_origin=${HTTPS_REMOTE_ORIGIN}`;
+destUrl += `&final_resource=/resource-timing/resources/blue-with-tao.png`;
+destUrl += `&tao_steps=3`;
+
+// Full redirect chain with `TAO: *`.
+attribute_test(
+ load.image, destUrl,
+ invariants.assert_tao_enabled_cross_origin_redirected_resource,
+ "Verify that cross origin resources' timings are exposed when cross-origin " +
+ "redirects have `Timing-Allow-Origin: *` headers");
+
+// TAO with a specific origin
+destUrl += `&tao_value=${SAME_ORIGIN}`;
+attribute_test(
+ load.image, destUrl,
+ invariants.assert_cross_origin_redirected_resource,
+ "Verify that cross origin resources' timings are not exposed when " +
+ "same-origin=>cross-origin=>same-origin redirects have " +
+ "`Timing-Allow-Origin:` headers with a specific origin.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/SyntheticResponse.py b/testing/web-platform/tests/resource-timing/SyntheticResponse.py
new file mode 100644
index 0000000000..6f888f3789
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/SyntheticResponse.py
@@ -0,0 +1,50 @@
+from urllib.parse import unquote
+
+from wptserve.utils import isomorphic_decode, isomorphic_encode
+
+import importlib
+sleep = importlib.import_module("resource-timing.sleep")
+
+def main(request, response):
+ index = isomorphic_encode(request.request_path).index(b"?")
+ args = isomorphic_encode(request.request_path[index+1:]).split(b"&")
+ headers = []
+ statusSent = False
+ headersSent = False
+ for arg in args:
+ if arg.startswith(b"ignored"):
+ continue
+ elif arg.endswith(b"ms"):
+ sleep.sleep_at_least(float(arg[0:-2]))
+ elif arg.startswith(b"redirect:"):
+ return (302, u"WEBPERF MARKETING"), [(b"Location", unquote(isomorphic_decode(arg[9:])))], u"TEST"
+
+ elif arg.startswith(b"mime:"):
+ headers.append((b"Content-Type", unquote(isomorphic_decode(arg[5:]))))
+
+ elif arg.startswith(b"send:"):
+ text = unquote(isomorphic_decode(arg[5:]))
+
+ if not statusSent:
+ # Default to a 200 status code.
+ response.writer.write_status(200)
+ statusSent = True
+ if not headersSent:
+ for key, value in headers:
+ response.writer.write_header(key, value)
+ response.writer.end_headers()
+ headersSent = True
+
+ response.writer.write_content(text)
+ elif arg.startswith(b"status:"):
+ code = int(unquote(isomorphic_decode(arg[7:])))
+ response.writer.write_status(code)
+ if code // 100 == 1:
+ # Terminate informational 1XX responses with an empty line.
+ response.writer.end_headers()
+ else:
+ statusSent = True
+
+# else:
+# error " INVALID ARGUMENT %s" % arg
+
diff --git a/testing/web-platform/tests/resource-timing/TAO-match.html b/testing/web-platform/tests/resource-timing/TAO-match.html
new file mode 100644
index 0000000000..dc0e2f7443
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/TAO-match.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing TAO tests</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help"
+ href="https://www.w3.org/TR/resource-timing-2/#timing-allow-origin"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/common/custom-cors-response.js"></script>
+<script src="resources/entry-invariants.js"></script>
+<script src="resources/resource-loaders.js"></script>
+<script src="resources/tao-response.js"></script>
+<body>
+<script>
+const {ORIGIN, REMOTE_ORIGIN} = get_host_info();
+
+const run_test = (loader, resource_type) => {
+ attribute_test(loader, remote_tao_response(ORIGIN),
+ invariants.assert_tao_pass_no_redirect_http,
+ `The timing allow check algorithm will pass when the Timing-Allow-Origin ` +
+ `header value contains only the origin. (${resource_type})`);
+
+ attribute_test(loader, remote_tao_response('*'),
+ invariants.assert_tao_pass_no_redirect_http,
+ `The timing allow check algorithm will pass when the Timing-Allow-Origin ` +
+ `header value contains only a wildcard. (${resource_type})`);
+
+ attribute_test(loader, remote_tao_response(`${ORIGIN},fake`),
+ invariants.assert_tao_pass_no_redirect_http,
+ `The timing allow check algorithm will pass when the Timing-Allow-Origin ` +
+ `header value list contains a case-sensitive match. (${resource_type})`);
+
+ attribute_test(loader, remote_tao_response(`${ORIGIN},*`),
+ invariants.assert_tao_pass_no_redirect_http,
+ `The timing allow check algorithm will pass when the Timing-Allow-Origin ` +
+ `header value list contains the origin and a wildcard. (${resource_type})`);
+
+ attribute_test(loader, remote_tao_response('fake,*'),
+ invariants.assert_tao_pass_no_redirect_http,
+ `The timing allow check algorithm will pass when the Timing-Allow-Origin ` +
+ `header value list contains a wildcard. (${resource_type})`);
+
+ attribute_test(loader, remote_tao_response('null'),
+ invariants.assert_tao_failure_resource,
+ `The timing allow check algorithm will fail when the Timing-Allow-Origin ` +
+ `header value list contains a null origin. (${resource_type})`);
+
+ attribute_test(loader, remote_tao_response('*,*'),
+ invariants.assert_tao_pass_no_redirect_http,
+ `The timing allow check algorithm will pass when the Timing-Allow-Origin ` +
+ `header value list contains multiple wildcards. (${resource_type})`);
+
+ attribute_test(loader, remote_tao_response(ORIGIN.toUpperCase()),
+ invariants.assert_tao_failure_resource,
+ `The timing allow check algorithm will fail when the Timing-Allow-Origin ` +
+ `header value contains only the uppercased origin. (${resource_type})`);
+
+ attribute_test(loader, remote_tao_response(`${ORIGIN} *`),
+ invariants.assert_tao_failure_resource,
+ `The timing allow check algorithm will fail when the Timing-Allow-Origin ` +
+ `header value contains the origin, a space, then a wildcard. ` +
+ `(${resource_type})`);
+
+ attribute_test(loader, custom_cors_response({}, REMOTE_ORIGIN),
+ invariants.assert_tao_failure_resource,
+ `The timing allow check algorithm will fail when the Timing-Allow-Origin ` +
+ `header is not present. (${resource_type})`);
+};
+
+run_test(load.font, "font");
+run_test(load.iframe, "iframe");
+run_test(load.image, "image");
+run_test(load.script, "script");
+run_test(load.stylesheet, "stylesheet");
+run_test(load.xhr_sync, "XMLHttpRequest");
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/TAO-port-mismatch-means-crossorigin.html b/testing/web-platform/tests/resource-timing/TAO-port-mismatch-means-crossorigin.html
new file mode 100644
index 0000000000..f1218d17a0
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/TAO-port-mismatch-means-crossorigin.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>TAO - port mismatch must fail the check</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-timing-allow-origin"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/entry-invariants.js"></script>
+<script>
+
+const {ORIGINAL_HOST, PORT, PORT2} = get_host_info();
+
+// The main page is being requested on the default port (PORT), while the
+// subresource will be requested on a separate port (PORT2). The response will
+// have a Timing-Allow-Origin header value with the second port so this page's
+// origin should not be a match.
+const port_mismatch_url = `${location.protocol}//${ORIGINAL_HOST}:${PORT2}` +
+ `/resource-timing/resources/TAOResponse.py?` +
+ `tao=origin_port_${PORT2}`;
+attribute_test(
+ fetch, port_mismatch_url, invariants.assert_tao_failure_resource,
+ "A port mismatch must fail the TAO check");
+
+// The same URL as above except the Timing-Allow-Origin header will have the
+// same port as this page's origin. Therefore, this page's origin will match
+// the Timing-Allow-Origin header's value. Therefore, the subresource's timings
+// must be exposed.
+const port_match_url = `${location.protocol}//${ORIGINAL_HOST}:${PORT2}` +
+ `/resource-timing/resources/TAOResponse.py?` +
+ `tao=origin_port_${PORT}`;
+attribute_test(
+ fetch, port_match_url, invariants.assert_tao_pass_no_redirect_http,
+ "An identical port must pass the TAO check");
+
+</script>
+</head>
+<body>
+<h1>Description</h1>
+<p>This test validates that for a cross origin resource with different ports,
+the timing allow check algorithm will fail when the value of
+Timing-Allow-Origin value has the right host but the wrong port in it.</p>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/__init__.py b/testing/web-platform/tests/resource-timing/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/__init__.py
diff --git a/testing/web-platform/tests/resource-timing/body-size-cross-origin.https.html b/testing/web-platform/tests/resource-timing/body-size-cross-origin.https.html
new file mode 100644
index 0000000000..b0340139bf
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/body-size-cross-origin.https.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Verify that encodedBodySize/decodedBodySize are CORS-protected rather than TAO-protected</title>
+<link rel="author" title="Noam Rosenthal" href="nrosenthal@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/common/utils.js"></script>
+</head>
+<body>
+<script>
+const {ORIGIN, REMOTE_ORIGIN} = get_host_info();
+
+async function test_body_size({mode, tao, expected_body_sizes}) {
+ promise_test(async t => {
+ const origin = mode === "same-origin" ? ORIGIN : REMOTE_ORIGIN;
+ const url = new URL(`${origin}/images/red.png?uid=${token()}`,
+ location.href);
+ const pipes = [];
+ if (mode === "cors")
+ pipes.push("header(Access-Control-Allow-Origin,*)");
+ if (tao)
+ pipes.push("header(Timing-Allow-Origin,*)");
+ const img = document.createElement("img");
+ if (mode === "cors")
+ img.crossOrigin = "anonymous";
+
+ if (pipes.length)
+ url.searchParams.set("pipe", pipes.join("|"));
+ img.src = url.toString();
+ await img.decode();
+ const [entry] = performance.getEntriesByName(url.toString());
+ if (expected_body_sizes) {
+ assert_greater_than(entry.encodedBodySize, 0);
+ assert_greater_than(entry.decodedBodySize, 0);
+ } else {
+ assert_equals(entry.encodedBodySize, 0);
+ assert_equals(entry.decodedBodySize, 0);
+ }
+
+ if (tao || mode === "same-origin")
+ assert_equals(entry.transferSize, entry.encodedBodySize + 300);
+ else
+ assert_equals(entry.transferSize, 0);
+
+ }, `Retrieving a ${mode} resource ${
+ tao ? "with" : "without"} Timing-Allow-Origin should ${
+ expected_body_sizes ? "expose" : "not expose"
+ } body size`);
+}
+
+test_body_size({mode: "same-origin", tao: false, expected_body_sizes: true});
+test_body_size({mode: "same-origin", tao: true, expected_body_sizes: true});
+test_body_size({mode: "no-cors", tao: false, expected_body_sizes: false});
+test_body_size({mode: "no-cors", tao: true, expected_body_sizes: false});
+test_body_size({mode: "cors", tao: false, expected_body_sizes: true});
+test_body_size({mode: "cors", tao: true, expected_body_sizes: true});
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/buffer-full-add-after-full-event.html b/testing/web-platform/tests/resource-timing/buffer-full-add-after-full-event.html
new file mode 100644
index 0000000000..43dc3d84fd
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/buffer-full-add-after-full-event.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performance-setresourcetimingbuffersize">
+<title>This test validates that setResourceTimingBufferFull behaves appropriately when set to the current buffer level.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/resource-loaders.js"></script>
+<script src="resources/buffer-full-utilities.js"></script>
+</head>
+<body>
+<script>
+promise_test(async t => {
+ await forceBufferFullEvent();
+ performance.clearResourceTimings();
+ return new Promise(resolve => {
+ new PerformanceObserver(t.step_func(() => {
+ assert_equals(performance.getEntriesByType('resource').length, 1,
+ 'The entry should be available in the performance timeline!');
+ resolve();
+ })).observe({type: 'resource'});
+ load.script(scriptResources[2]);
+ });
+}, "Test that entry was added to the buffer after a buffer full event");
+</script>
diff --git a/testing/web-platform/tests/resource-timing/buffer-full-add-entries-during-callback-that-drop.html b/testing/web-platform/tests/resource-timing/buffer-full-add-entries-during-callback-that-drop.html
new file mode 100644
index 0000000000..b00185c5b6
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/buffer-full-add-entries-during-callback-that-drop.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<head onload>
+<meta charset="utf-8" />
+<title>This test validates that synchronously adding entries in onresourcetimingbufferfull callback results in these entries being properly handled.</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performance-onresourcetimingbufferfull"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/resource-loaders.js"></script>
+<script src="resources/buffer-full-utilities.js"></script>
+</head>
+<body>
+<script>
+promise_test(async () => {
+ await fillUpTheBufferWithSingleResource();
+ performance.addEventListener('resourcetimingbufferfull', () => {
+ performance.setResourceTimingBufferSize(2);
+ // The sync entry is added to the secondary buffer, so will be the last one there and eventually dropped.
+ load.xhr_sync(scriptResources[2]);
+ });
+ // This resource overflows the entry buffer, and goes into the secondary buffer.
+ load.script(scriptResources[1]);
+ await bufferFullFirePromise;
+ checkEntries(2);
+}, "Test that entries synchronously added to the buffer during the callback are dropped");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/buffer-full-add-entries-during-callback.html b/testing/web-platform/tests/resource-timing/buffer-full-add-entries-during-callback.html
new file mode 100644
index 0000000000..d5883d33d5
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/buffer-full-add-entries-during-callback.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<head onload>
+<meta charset="utf-8" />
+<title>This test validates that synchronously adding entries in onresourcetimingbufferfull callback results in these entries being properly handled.</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performance-onresourcetimingbufferfull"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/resource-loaders.js"></script>
+<script src="resources/buffer-full-utilities.js"></script>
+</head>
+<body>
+<script>
+promise_test(async () => {
+ await fillUpTheBufferWithSingleResource();
+ performance.addEventListener('resourcetimingbufferfull', () => {
+ performance.setResourceTimingBufferSize(3);
+ load.xhr_sync(scriptResources[2]);
+ });
+ // This resource overflows the entry buffer, and goes into the secondary buffer.
+ load.script(scriptResources[1]);
+ await bufferFullFirePromise;
+ checkEntries(3);
+}, "Test that entries synchronously added to the buffer during the callback don't get dropped if the buffer is increased");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/buffer-full-add-then-clear.html b/testing/web-platform/tests/resource-timing/buffer-full-add-then-clear.html
new file mode 100644
index 0000000000..5617c30b88
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/buffer-full-add-then-clear.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<head onload>
+<meta charset="utf-8" />
+<title>This test validates that synchronously adding entries in onresourcetimingbufferfull callback results in these entries being properly handled.</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performance-onresourcetimingbufferfull"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/resource-loaders.js"></script>
+<script src="resources/buffer-full-utilities.js"></script>
+</head>
+<body>
+<script>
+promise_test(async t => {
+ addAssertUnreachedBufferFull(t);
+ await fillUpTheBufferWithSingleResource('resources/empty.js?willbelost');
+ // These resources overflow the entry buffer, and go into the secondary buffer.
+ load.xhr_sync(scriptResources[0]);
+ load.xhr_sync(scriptResources[1]);
+ performance.clearResourceTimings();
+ performance.setResourceTimingBufferSize(3);
+ load.xhr_sync(scriptResources[2]);
+ const entriesAfterAddition = performance.getEntriesByType('resource');
+ await waitForNextTask();
+ checkEntries(3);
+ assert_equals(entriesAfterAddition.length, 0, "No entries should have been added to the primary buffer before the task to 'fire a buffer full event'.");
+}, "Test that if the buffer is cleared after entries were added to the secondary buffer, those entries make it into the primary one");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/buffer-full-decrease-buffer-during-callback.html b/testing/web-platform/tests/resource-timing/buffer-full-decrease-buffer-during-callback.html
new file mode 100644
index 0000000000..3091fcf426
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/buffer-full-decrease-buffer-during-callback.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head onload>
+<meta charset="utf-8" />
+<title>This test validates that decreasing the buffer size in onresourcetimingbufferfull callback does not result in extra entries being dropped.</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performance-onresourcetimingbufferfull"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/resource-loaders.js"></script>
+<script src="resources/buffer-full-utilities.js"></script>
+</head>
+<body>
+<script>
+promise_test(async () => {
+ performance.addEventListener('resourcetimingbufferfull', () => {
+ performance.setResourceTimingBufferSize(1);
+ });
+ await fillUpTheBufferWithTwoResources();
+ load.script(scriptResources[2]);
+ await bufferFullFirePromise;
+ checkEntries(2);
+}, "Test that decreasing the buffer limit during the callback does not drop entries");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/buffer-full-eventually.html b/testing/web-platform/tests/resource-timing/buffer-full-eventually.html
new file mode 100644
index 0000000000..6e9d5db483
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/buffer-full-eventually.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8" />
+<link rel="help"
+ href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
+<title>This test validates that resource timing implementations have a finite
+ number of entries in their buffer.</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+promise_test(t => {
+ return new Promise(resolve => {
+ let counter = 0;
+ performance.onresourcetimingbufferfull = resolve;
+ const loadImagesRecursively = () => {
+ // Load an image.
+ (new Image()).src = "resources/blue.png?" + counter;
+ ++counter;
+ // Yield to enable queueing an entry, then recursively load another image.
+ t.step_timeout(loadImagesRecursively, 0);
+ };
+ loadImagesRecursively();
+ });
+}, "Finite resource timing entries buffer size");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/buffer-full-increase-buffer-during-callback.html b/testing/web-platform/tests/resource-timing/buffer-full-increase-buffer-during-callback.html
new file mode 100644
index 0000000000..dd12dd7afa
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/buffer-full-increase-buffer-during-callback.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head onload>
+<meta charset="utf-8" />
+<title>This test validates increasing the buffer size in onresourcetimingbufferfull callback of resource timing.</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performance-onresourcetimingbufferfull"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/resource-loaders.js"></script>
+<script src="resources/buffer-full-utilities.js"></script>
+</head>
+<body>
+<script>
+promise_test(async () => {
+ await fillUpTheBufferWithSingleResource();
+ performance.addEventListener('resourcetimingbufferfull', () => {
+ performance.setResourceTimingBufferSize(2);
+ });
+ await load.script(scriptResources[1]);
+ await bufferFullFirePromise;
+ checkEntries(2);
+}, "Test that increasing the buffer during the callback is enough for entries not to be dropped");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/buffer-full-inspect-buffer-during-callback.html b/testing/web-platform/tests/resource-timing/buffer-full-inspect-buffer-during-callback.html
new file mode 100644
index 0000000000..d5cc8e6ecd
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/buffer-full-inspect-buffer-during-callback.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<head onload>
+<meta charset="utf-8" />
+<title>This test validates the buffer doesn't contain more entries than it should inside onresourcetimingbufferfull callback.</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performance-setresourcetimingbuffersize"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/resource-loaders.js"></script>
+<script src="resources/buffer-full-utilities.js"></script>
+</head>
+<body>
+<script>
+promise_test(async t => {
+ performance.addEventListener('resourcetimingbufferfull', t.step_func(() => {
+ assert_equals(performance.getEntriesByType("resource").length, 1,
+ "resource timing buffer in resourcetimingbufferfull is the size of the limit");
+ load.xhr_sync(scriptResources[2]);
+ performance.setResourceTimingBufferSize(3);
+ assert_equals(performance.getEntriesByType("resource").length, 1,
+ "A sync request must not be added to the primary buffer just yet, because it is full");
+ }));
+ await forceBufferFullEvent();
+ await waitForNextTask();
+ checkEntries(3);
+}, "Test that entries in the secondary buffer are not exposed during the callback and before they are copied to the primary buffer");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/buffer-full-set-to-current-buffer.html b/testing/web-platform/tests/resource-timing/buffer-full-set-to-current-buffer.html
new file mode 100644
index 0000000000..dc527b9a32
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/buffer-full-set-to-current-buffer.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<title>This test validates that setResourceTimingBufferFull behaves appropriately when set to the current buffer level.</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performance-onresourcetimingbufferfull">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/resource-loaders.js"></script>
+<script src="resources/buffer-full-utilities.js"></script>
+</head>
+<body>
+<script>
+promise_test(async () => {
+ let result = '';
+ performance.addEventListener('resourcetimingbufferfull', () => {
+ result += 'Event Fired with ' +
+ performance.getEntriesByType('resource').length + ' entries.';
+ performance.clearResourceTimings();
+ });
+ result += 'Before adding entries. ';
+ await fillUpTheBufferWithTwoResources();
+ result += 'After adding entries. ';
+ load.script(scriptResources[2]);
+ await bufferFullFirePromise;
+ assert_equals(result, 'Before adding entries. After adding entries. Event Fired with 2 entries.');
+ const entries = performance.getEntriesByType('resource');
+ assert_equals(entries.length, 1,
+ 'Number of entries in resource timing buffer is unexpected');
+ assert_true(entries[0].name.includes(scriptResources[2]),
+ 'The entry must correspond to the last resource loaded.')
+}, "Test that adding entries and firing the buffer full event happen in the right order.");
+</script>
diff --git a/testing/web-platform/tests/resource-timing/buffer-full-store-and-clear-during-callback.html b/testing/web-platform/tests/resource-timing/buffer-full-store-and-clear-during-callback.html
new file mode 100644
index 0000000000..3ea0577256
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/buffer-full-store-and-clear-during-callback.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<head onload>
+<meta charset="utf-8" />
+<title>This test validates the behavior of read and clear operation in onresourcetimingbufferfull callback of resource timing.</title>
+<link rel="author" title="Intel" href="http://www.intel.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performance-onresourcetimingbufferfull"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/resource-loaders.js"></script>
+<script src="resources/buffer-full-utilities.js"></script>
+</head>
+<body>
+<script>
+promise_test(async () => {
+ await fillUpTheBufferWithSingleResource();
+ const entryBuffer = [];
+ performance.addEventListener('resourcetimingbufferfull', () => {
+ entryBuffer.push(...performance.getEntriesByType('resource'));
+ performance.clearResourceTimings();
+ });
+ load.script(scriptResources[1]);
+ await bufferFullFirePromise;
+ const entries = performance.getEntriesByType('resource');
+ assert_equals(entries.length, 1,
+ "Only the last entry should be stored in resource timing buffer since it's cleared once it overflows.");
+ assert_true(entries[0].name.includes(scriptResources[1]),
+ scriptResources[1] + " is in the entries buffer");
+ assert_equals(entryBuffer.length, 1,
+ '1 resource timing entry should be moved to entryBuffer.');
+ assert_true(entryBuffer[0].name.includes(scriptResources[0]),
+ scriptResources[0] + ' is in the entryBuffer');
+}, "Test that entries overflowing the buffer trigger the buffer full event, can be stored, and make their way to the primary buffer after it's cleared in the buffer full event.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/buffer-full-then-decreased.html b/testing/web-platform/tests/resource-timing/buffer-full-then-decreased.html
new file mode 100644
index 0000000000..21912d978b
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/buffer-full-then-decreased.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>This test validates that reducing the buffer size after entries were
+ queued does not drop those entries, nor does it call the
+ resourcetimingbufferfull event callback.</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help"
+ href="https://www.w3.org/TR/resource-timing-2/#dom-performance-onresourcetimingbufferfull"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/resource-loaders.js"></script>
+<script src="resources/buffer-full-utilities.js"></script>
+</head>
+<body>
+<script>
+promise_test(async t => {
+ addAssertUnreachedBufferFull(t);
+ await fillUpTheBufferWithTwoResources();
+ performance.setResourceTimingBufferSize(1);
+ await waitForNextTask();
+ checkEntries(2);
+}, "Test that if the buffer is reduced after entries were added to it, those" +
+ " entries don't get cleared, nor is the resourcetimingbufferfull event" +
+ " being called.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/buffer-full-then-increased.html b/testing/web-platform/tests/resource-timing/buffer-full-then-increased.html
new file mode 100644
index 0000000000..de517bf405
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/buffer-full-then-increased.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<head onload>
+<meta charset="utf-8" />
+<title>This test validates that synchronously adding entries in onresourcetimingbufferfull callback results in these entries being properly handled.</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performance-onresourcetimingbufferfull"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/resource-loaders.js"></script>
+<script src="resources/buffer-full-utilities.js"></script>
+</head>
+<body>
+<script>
+promise_test(async t => {
+ addAssertUnreachedBufferFull(t);
+ await fillUpTheBufferWithSingleResource();
+ // These resources overflow the entry buffer, and go into the secondary buffer.
+ load.xhr_sync(scriptResources[1]);
+ load.xhr_sync(scriptResources[2]);
+ // Immediately increase the size: the bufferfull event should not be fired.
+ performance.setResourceTimingBufferSize(3);
+ await waitForNextTask();
+ checkEntries(3);
+}, "Test that overflowing the buffer and immediately increasing its limit does not trigger the resourcetimingbufferfull event");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/buffer-full-when-populate-entries.html b/testing/web-platform/tests/resource-timing/buffer-full-when-populate-entries.html
new file mode 100644
index 0000000000..f4b1a2e7e7
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/buffer-full-when-populate-entries.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>This test validates the functionality of onresourcetimingbufferfull in resource timing.</title>
+<link rel="author" title="Intel" href="http://www.intel.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performance-onresourcetimingbufferfull"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/resource-loaders.js"></script>
+<script src="resources/buffer-full-utilities.js"></script>
+</head>
+<body>
+<script>
+promise_test(async () => {
+ let bufferFullCount = 0;
+ performance.addEventListener('resourcetimingbufferfull', e => {
+ assert_equals(e.bubbles, false, "Event bubbles attribute is false");
+ bufferFullCount++;
+ });
+ await fillUpTheBufferWithTwoResources();
+ // Overflow the buffer
+ await load.script(scriptResources[2]);
+ await waitForNextTask();
+ checkEntries(2);
+ assert_equals(bufferFullCount, 1, 'onresourcetimingbufferfull should have been invoked once.');
+}, "Test that a buffer full event does not bubble and that resourcetimingbufferfull is called only once per overflow");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/buffered-flag.any.js b/testing/web-platform/tests/resource-timing/buffered-flag.any.js
new file mode 100644
index 0000000000..b46fd00e69
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/buffered-flag.any.js
@@ -0,0 +1,18 @@
+async_test(t => {
+ performance.clearResourceTimings();
+ // First observer creates second in callback to ensure the entry has been dispatched by the time
+ // the second observer begins observing.
+ new PerformanceObserver(() => {
+ // Second observer requires 'buffered: true' to see an entry.
+ new PerformanceObserver(t.step_func_done(list => {
+ const entries = list.getEntries();
+ assert_equals(entries.length, 1, 'There should be 1 resource entry.');
+ assert_equals(entries[0].entryType, 'resource');
+ assert_greater_than(entries[0].startTime, 0);
+ assert_greater_than(entries[0].responseEnd, entries[0].startTime);
+ assert_greater_than(entries[0].duration, 0);
+ assert_true(entries[0].name.endsWith('resources/empty.js'));
+ })).observe({'type': 'resource', buffered: true});
+ }).observe({'entryTypes': ['resource']});
+ fetch('resources/empty.js');
+}, 'PerformanceObserver with buffered flag sees previous resource entries.');
diff --git a/testing/web-platform/tests/resource-timing/cached-image-gets-single-entry.html b/testing/web-platform/tests/resource-timing/cached-image-gets-single-entry.html
new file mode 100644
index 0000000000..2d8c4e2e83
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/cached-image-gets-single-entry.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing: test behavior for cached resources</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/observe-entry.js"></script>
+</head>
+<body>
+<h1>Description</h1>
+<p>Test that a reused resource only appears in the buffer once.</p>
+<script>
+// Need our own image loading helper because the one in resource-loaders.js
+// is desgined to always side-step the HTTP cache but this test relies on the
+// second request being resolved from the cache.
+const load_image = path => new Promise(resolve => {
+ const img = document.createElement('img');
+ img.onload = img.onerror = () => resolve();
+ img.src = path;
+ document.body.append(img);
+});
+
+promise_test(async () => {
+ const blue = "resources/blue.png";
+
+ // First request. Should appear in the timeline.
+ await load_image(blue + "?cacheable");
+
+ // Second request. Should not appear in the timeline.
+ await load_image(blue + "?cacheable");
+
+ // Third request. When this request shows up in the timeline, we know that, if
+ // the second request would generate an entry, that entry would have already
+ // shown up in the timeline. Without this, we'd need to guess at how long to
+ // wait which tends to be flaky.
+ await load_image(blue + "?avoid-cache");
+
+ const entries = await new Promise(resolve => {
+ const accumulator = [];
+ new PerformanceObserver(entry_list => {
+ entry_list.getEntries().forEach(entry => {
+ if (!entry.name.includes("blue.png")) {
+ // Ignore resources other than blue images.
+ return;
+ }
+ accumulator.push(entry);
+
+ // Once we see the 'canary' resource, we don't need to wait anymore.
+ if (entry.name.endsWith('avoid-cache')) {
+ resolve(accumulator);
+ }
+ });
+ }).observe({'type': 'resource', 'buffered': true});
+ });
+
+ assert_equals(entries.length, 2, "There must be exactly 2 entries in the " +
+ "Performance Timeline");
+ assert_true(entries[0].name.endsWith("blue.png?cacheable"));
+ assert_true(entries[1].name.endsWith("blue.png?avoid-cache"));
+}, "When a resource is resolved from cache, there must not be a " +
+ "corresponding entry in the Performance Timeline");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/clear-resource-timings.html b/testing/web-platform/tests/resource-timing/clear-resource-timings.html
new file mode 100644
index 0000000000..7508f8432e
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/clear-resource-timings.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>This test validates the functionality of clearResourceTimings method
+in resource timing.</title>
+<link rel="author" title="Intel" href="http://www.intel.com/" />
+<link rel="help"
+ href="https://www.w3.org/TR/resource-timing-2/#dom-performance-clearresourcetimings">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(() => {
+ assert_equals(performance.getEntriesByType("resource").length, 2,
+ "Resource timing entries exist");
+ performance.clearResourceTimings();
+ assert_equals(performance.getEntriesByType("resource").length, 0,
+ "Resource timing entries are cleared");
+}, "Test that clearResourceTimings() clears the performance timeline buffer");
+</script>
+</head>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/connection-reuse.html b/testing/web-platform/tests/resource-timing/connection-reuse.html
new file mode 100644
index 0000000000..a1bc927cfd
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/connection-reuse.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing connection reuse</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/entry-invariants.js"></script>
+<script src="resources/connection-reuse-test.js"></script>
+<script>
+ const {HTTPS_ORIGIN} = get_host_info();
+
+ // Fetches the given subresource a couple times with the same connection.
+ const http_path = "resources/fake_responses.py";
+ connection_reuse_test(http_path,
+ {
+ 'on_200': invariants.assert_tao_pass_no_redirect_http,
+ 'on_304': invariants.assert_tao_pass_304_not_modified_http,
+ }, "Reuse HTTP connection");
+
+ // Like above, but the subresource is fetched over HTTPS while this page is
+ // fetched over HTTP.
+ const https_url = `${HTTPS_ORIGIN}/resource-timing/${http_path}`;
+ connection_reuse_test(https_url,
+ {
+ 'on_200': invariants.assert_tao_pass_no_redirect_https,
+ 'on_304': invariants.assert_tao_pass_304_not_modified_https,
+ }, "Reuse HTTPS connection from HTTP page");
+
+ // Like the above mixed-content test but the final resource is behind an HTTP
+ // redirect response.
+ const redirect_path = (() => {
+ // The resource behind the redirect is the same fake_responses.py handler
+ // on the HTTPS origin. Pass it through encodeURIComponent so that it can
+ // be passed through a query-parameter.
+ const redirect_url = encodeURIComponent(https_url)
+ // The request is made to the HTTPS origin with a query parameter that will
+ // cause a 302 response.
+ return `${https_url}?redirect=${redirect_url}`;
+ })();
+ connection_reuse_test(redirect_path,
+ {
+ 'on_200': invariants.assert_tao_enabled_cross_origin_redirected_resource,
+ 'on_304': invariants.assert_tao_enabled_cross_origin_redirected_resource,
+ }, "Reuse HTTPS connection with redirects from an HTTP page");
+</script>
+</head>
+<body>
+<h1>Description</h1>
+<p>See <a href="resources/connection-reuse-test.js">the included test
+ script</a></p>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/connection-reuse.https.html b/testing/web-platform/tests/resource-timing/connection-reuse.https.html
new file mode 100644
index 0000000000..3461eed472
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/connection-reuse.https.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing connection reuse</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/entry-invariants.js"></script>
+<script src="resources/connection-reuse-test.js"></script>
+<script>
+ connection_reuse_test("resources/fake_responses.py",
+ {
+ 'on_200': invariants.assert_tao_pass_no_redirect_https,
+ 'on_304': invariants.assert_tao_pass_304_not_modified_https,
+ }, "Reuse an HTTPS connection");
+</script>
+</head>
+<body>
+<h1>Description</h1>
+<p>See <a href="resources/connection-reuse-test.js">the included test
+ script</a></p>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/content-type-parsing.html b/testing/web-platform/tests/resource-timing/content-type-parsing.html
new file mode 100644
index 0000000000..c0081eb413
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/content-type-parsing.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<head>
+<meta charset="utf-8" />
+<title>This test validates the parsing of content-type of resources.</title>
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+
+// Utility function picked from https://github.com/web-platform-tests/wpt/blob/master/mimesniff/mime-types/charset-parameter.window.js
+function isByteCompatible(str) {
+ // see https://fetch.spec.whatwg.org/#concept-header-value-normalize
+ if(/^[\u0009\u0020\u000A\u000D]+|[\u0009\u0020\u000A\u000D]+$/.test(str)) {
+ return "header-value-incompatible";
+ }
+
+ for(let i = 0; i < str.length; i++) {
+ const charCode = str.charCodeAt(i);
+ // See https://fetch.spec.whatwg.org/#concept-header-value
+ if(charCode > 0xFF) {
+ return "incompatible";
+ } else if(charCode === 0x00 || charCode === 0x0A || charCode === 0x0D) {
+ return "header-value-incompatible";
+ }
+ }
+ return "compatible";
+}
+
+// Test for content-type parsing.
+const run_content_type_parsing_tests = (json_entries) => {
+ json_entries.forEach( (json_entry, i) => {
+ promise_test(async t => {
+ let url = "/fetch/content-type/resources/content-type.py?single_header&";
+ json_entry.contentType.forEach(val => {
+ url += "value=" + encodeURIComponent(val) + "&";
+ });
+ fetch(url);
+ const entry = await new Promise(resolve => new PerformanceObserver((entryList, observer) => {
+ observer.disconnect();
+ resolve(entryList.getEntries()[0]);
+ }).observe({entryTypes: ['resource']}));
+ assert_equals(entry.contentType, json_entry["mimeType"]);
+ }, "content-type " + i + " : " + json_entry.contentType);
+ });
+}
+
+// Test for mime-type parsing.
+const run_mime_type_parsing_tests = (json_entries) => {
+ json_entries.forEach( (val, i) => {
+ if(typeof val === "string" || val.navigable === undefined || isByteCompatible(val.input) !== "compatible") {
+ return;
+ }
+ const output = val.output === null ? "" : val.output
+ promise_test(async t => {
+ let url = `/fetch/content-type/resources/content-type.py?single_header&value=${val.input}`;
+ fetch(url);
+ const entry = await new Promise(resolve => new PerformanceObserver((entryList, observer) => {
+ observer.disconnect();
+ resolve(entryList.getEntries()[0]);
+ }).observe({entryTypes: ['resource']}));
+ assert_equals(entry.contentType, output);
+ }, "mime-type " + i + " : " + val.input);
+ });
+}
+
+Promise.all([
+ fetch("/fetch/content-type/resources/content-types.json"),
+ fetch("/mimesniff/mime-types/resources/mime-types.json")
+ ]).then(([res, res2]) => res.json().then(run_content_type_parsing_tests)
+ .then(() => res2.json().then(run_mime_type_parsing_tests)));
+
+</script>
+</body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/resource-timing/content-type.html b/testing/web-platform/tests/resource-timing/content-type.html
new file mode 100644
index 0000000000..f6b1db7d9f
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/content-type.html
@@ -0,0 +1,117 @@
+<!DOCTYPE html>
+<head>
+<meta charset="utf-8" />
+<title>This test validates the content-type of resources.</title>
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/entry-invariants.js"></script>
+<script src="resources/resource-loaders.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+</head>
+<body>
+<script>
+const {ORIGIN, REMOTE_ORIGIN} = get_host_info();
+const SAME_ORIGIN = location.origin;
+
+
+// Content-type for same origin resources is exposed.
+const run_test = (loader, contentType) => {
+ let path = `/resource-timing/resources/content-type.py?content_type=${contentType}`;
+ const url = new URL(path, ORIGIN);
+ attribute_test(
+ loader, url,
+ entry => {
+ assert_equals(entry.contentType, contentType,
+ `content-type for ${entry.name} should be ${contentType}`);
+ });
+}
+
+// Content-type is empty string when a no-cors request is made for cross
+// origin resource.
+// Content-type is empty for cross origin iframes.
+const run_test_cross_origin = (loader, contentType) => {
+ let path = `/resource-timing/resources/content-type.py?content_type=${contentType}`;
+ const url = new URL(path, REMOTE_ORIGIN);
+ attribute_test(
+ loader, url,
+ entry => {
+ assert_equals(entry.contentType, "",
+ `content-type for ${entry.name} should be ""`);
+ });
+}
+
+const resource_loaders_and_types = [
+ [load.font, ["font/woff", "font/otf"]],
+ [load.image, ["image/png", "image/jpg"]],
+ [load.script, ["application/javascript", "text/javascript"]],
+ [load.stylesheet, ["text/css"]],
+ [load.xhr_async, ["application/x-gzip", "application/pdf"]],
+ [load.iframe, ["text/html"]]
+];
+
+resource_loaders_and_types.forEach(resource => {
+ let loader = resource[0];
+ let content_types = resource[1];
+ content_types.forEach(type => {
+ run_test(loader, type);
+ run_test_cross_origin(loader, type);
+ })
+});
+
+
+// Content-type is exposed for cors request for cross-origin resources.
+const run_test_cross_origin_allow_origin = (loader_with_attr,contentType) => {
+ let path = `/resource-timing/resources/content-type.py?content_type=${contentType}&allow_origin=${ORIGIN}`;
+ const url = new URL(path, REMOTE_ORIGIN);
+ loader_with_crossOrigin_attr = async url => {
+ return loader_with_attr(url, {"crossOrigin": "anonymous"});
+ }
+ attribute_test(
+ loader_with_crossOrigin_attr, url,
+ entry => {
+ assert_equals(entry.contentType, contentType,
+ `content-type for ${entry.name} should be ${contentType}`);
+ });
+}
+
+const resource_loaders_with_attrs_and_types = [
+ [load.image_with_attrs, ["image/gif", "image/jpeg"]],
+ [load.script_with_attrs, ["application/javascript", "text/javascript"]],
+ [load.stylesheet_with_attrs, ["text/css"]],
+]
+
+resource_loaders_with_attrs_and_types.forEach(resource => {
+ let loader = resource[0];
+ let content_types = resource[1];
+ content_types.forEach(type => {
+ run_test_cross_origin_allow_origin(loader, type);
+ })
+});
+
+// Content-type for iframes is empty when cross origin redirects are present.
+var destUrl = `${SAME_ORIGIN}/resource-timing/resources/multi_redirect.py?`;
+destUrl += `page_origin=${SAME_ORIGIN}`;
+destUrl += `&cross_origin=${REMOTE_ORIGIN}`;
+destUrl += `&final_resource=/resource-timing/resources/content-type.py?content_type=text/html`;
+attribute_test(
+ load.iframe, new URL(destUrl),
+ entry => {
+ assert_equals(entry.contentType, "",
+ `content-type should be empty for iframes having cross origin redirects`);
+});
+
+
+// Content-type for iframes is exposed for same origin redirects.
+var destUrl = `${SAME_ORIGIN}/resource-timing/resources/redirect-cors.py`;
+destUrl += `?location=${SAME_ORIGIN}/resource-timing/resources/content-type.py?content_type=text/html`;
+attribute_test(
+ load.iframe, new URL(destUrl),
+ entry => {
+ assert_equals(entry.contentType, "text/html",
+ `content-type should be exposed for iframes having only same origin redirects`);
+});
+
+</script>
+</body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/resource-timing/cors-preflight.any.js b/testing/web-platform/tests/resource-timing/cors-preflight.any.js
new file mode 100644
index 0000000000..4b980e7d0a
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/cors-preflight.any.js
@@ -0,0 +1,49 @@
+// META: script=/common/utils.js
+// META: script=/common/get-host-info.sub.js
+
+// Because apache decrements the Keep-Alive max value on each request, the
+// transferSize will vary slightly between requests for the same resource.
+const fuzzFactor = 3; // bytes
+
+const {HTTP_REMOTE_ORIGIN} = get_host_info();
+const url = new URL('/resource-timing/resources/preflight.py',
+ HTTP_REMOTE_ORIGIN).href;
+
+// The header bytes are expected to be > |minHeaderSize| and
+// < |maxHeaderSize|. If they are outside this range the test will fail.
+const minHeaderSize = 100;
+const maxHeaderSize = 1024;
+
+promise_test(async () => {
+ const checkCorsAllowed = response => response.arrayBuffer();
+ const requirePreflight = {headers: {'X-Require-Preflight' : '1'}};
+ const collectEntries = new Promise(resolve => {
+ let entriesSeen = [];
+ new PerformanceObserver(entryList => {
+ entriesSeen = entriesSeen.concat(entryList.getEntries());
+ if (entriesSeen.length > 2) {
+ throw new Error(`Saw too many PerformanceResourceTiming entries ` +
+ `(${entriesSeen.length})`);
+ }
+ if (entriesSeen.length == 2) {
+ resolve(entriesSeen);
+ }
+ }).observe({"type": "resource"});
+ });
+
+ // Although this fetch doesn't send a pre-flight request, the server response
+ // will allow cross-origin requests explicitly with the
+ // Access-Control-Allow-Origin header.
+ await fetch(url).then(checkCorsAllowed);
+
+ // This fetch will send a pre-flight request to do the CORS handshake
+ // explicitly.
+ await fetch(url, requirePreflight).then(checkCorsAllowed);
+
+ const entries = await collectEntries;
+ assert_greater_than(entries[0].transferSize, 0, 'No-preflight transferSize');
+ const lowerBound = entries[0].transferSize - fuzzFactor;
+ const upperBound = entries[0].transferSize + fuzzFactor;
+ assert_between_exclusive(entries[1].transferSize, lowerBound, upperBound,
+ 'Preflighted transferSize');
+}, 'PerformanceResourceTiming sizes fetch with preflight test');
diff --git a/testing/web-platform/tests/resource-timing/cross-origin-iframe.html b/testing/web-platform/tests/resource-timing/cross-origin-iframe.html
new file mode 100644
index 0000000000..69daebffaf
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/cross-origin-iframe.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test ResourceTiming reporting for cross-origin iframe.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/entry-invariants.js"></script>
+<script src="resources/observe-entry.js"></script>
+</head>
+<body>
+<body>
+<script>
+ const {REMOTE_ORIGIN} = get_host_info();
+
+ promise_test(async t => {
+ const iframe = document.createElement('iframe');
+ t.add_cleanup(() => iframe.remove());
+ iframe.src = `${REMOTE_ORIGIN}/resource-timing/resources/green.html`;
+ document.body.appendChild(iframe);
+ const entry = await observe_entry(iframe.src);
+ invariants.assert_tao_failure_resource(entry);
+ }, "A cross-origin iframe should report an opaque RT entry");
+
+ promise_test(async t => {
+ const iframe = document.createElement('iframe');
+ t.add_cleanup(() => iframe.remove());
+ iframe.src = `${REMOTE_ORIGIN}/resource-timing/resources/TAOResponse.py?tao=wildcard`;
+ document.body.appendChild(iframe);
+ const entry = await observe_entry(iframe.src);
+ invariants.assert_tao_pass_no_redirect_http(entry);
+ }, "A cross-origin iframe with TAO enabled should report a full RT entry");
+
+ </script>
diff --git a/testing/web-platform/tests/resource-timing/cross-origin-redirects.html b/testing/web-platform/tests/resource-timing/cross-origin-redirects.html
new file mode 100644
index 0000000000..0bdc0547e5
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/cross-origin-redirects.html
@@ -0,0 +1,102 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>This test validates the values in resource timing for cross-origin
+redirects.</title>
+<link rel="author" title="Intel" href="http://www.intel.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/custom-cors-response.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/entry-invariants.js"></script>
+<script src="resources/resource-loaders.js"></script>
+<script src="resources/tao-response.js"></script>
+</head>
+<body>
+<script>
+const {ORIGIN, REMOTE_ORIGIN} = get_host_info();
+
+const HTTP_SO_to_XO_redirect_url = url => {
+ // Make an initial request to a same-domain resource that will return a 302
+ // redirect to the given (possibly cross-origin) url.
+ return `/resource-timing/resources/redirect-cors.py?location=${url}`;
+};
+
+const HTTP_SO_resource = () => {
+ if (location.protocol != "http:") {
+ throw new Error("Can only make an HTTP SO request if this page was " +
+ "served over HTTP.");
+ }
+ return tao_response("*", ORIGIN);
+};
+
+const HTTP_XO_redirect = (url, tao) => {
+ const ret = new URL(
+ `${REMOTE_ORIGIN}/resource-timing/resources/redirect-cors.py`);
+ ret.searchParams.append("location", url);
+ ret.searchParams.append("allow_origin", "*");
+ ret.searchParams.append("timing_allow_origin", tao);
+ return ret.href;
+};
+
+attribute_test(
+ load.iframe, HTTP_SO_to_XO_redirect_url(custom_cors_response({},
+ REMOTE_ORIGIN)),
+ invariants.assert_http_to_cross_origin_redirected_resource,
+ "Verify that cross-origin resources' timings aren't exposed through HTTP " +
+ "redirects.");
+
+attribute_test(
+ load.iframe, HTTP_SO_to_XO_redirect_url(remote_tao_response("no-match")),
+ invariants.assert_cross_origin_redirected_resource,
+ "Verify that a redirected cross-origin resources' timings aren't exposed " +
+ "when the TAO check fails.");
+
+attribute_test(
+ load.iframe, HTTP_SO_to_XO_redirect_url(remote_tao_response("*")),
+ invariants.assert_http_to_tao_enabled_cross_origin_https_redirected_resource,
+ "Verify that cross-origin resources' timings are exposed when the TAO " +
+ "check succeeds. Also verify that secureConnectionStart is 0 since the " +
+ "original request was over HTTP.");
+
+attribute_test(
+ load.iframe, HTTP_XO_redirect(HTTP_XO_redirect(HTTP_SO_resource(), "*"), "*"),
+ invariants.assert_http_to_tao_enabled_cross_origin_https_redirected_resource,
+ "Verify that a redirect chain through cross-origin resources have their " +
+ "timings exposed when all TAO checks succeed. Also verify that " +
+ "secureConnectionStart is 0 since the original request was over HTTP.");
+
+const failure_permutations = [
+ ["fail", "fail", "fail"],
+ ["fail", "fail", "*" ],
+ ["fail", "*", "fail"],
+ ["fail", "*", "*" ],
+ ["*", "fail", "fail"],
+ ["*", "fail", "*" ],
+ ["*", "*", "fail"],
+];
+const test_case = (so_tao, xo1_tao, xo2_tao) => {
+ return HTTP_XO_redirect(HTTP_XO_redirect(HTTP_SO_resource(
+ so_tao), xo2_tao), xo1_tao);
+};
+const test_label = perm => {
+ return perm.map(x => {
+ if (x == "*" ) return "PASS";
+ if (x == "fail" ) return "FAIL";
+ throw new Error(`unexpected element ${x}`);
+ }).join(" -> ");
+};
+for (const permutation of failure_permutations) {
+ attribute_test(
+ load.iframe, test_case.apply(permutation),
+ invariants.assert_tao_failure_resource,
+ `Verify that a redirect chain through cross-origin resources do not have ` +
+ `their timings exposed when any of the TAO checks fail. ` +
+ `(${test_label(permutation)})`);
+}
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/cross-origin-start-end-time-with-redirects.html b/testing/web-platform/tests/resource-timing/cross-origin-start-end-time-with-redirects.html
new file mode 100644
index 0000000000..8e368d1380
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/cross-origin-start-end-time-with-redirects.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>This test validates the values in resource timing for cross-origin
+redirects.</title>
+<link rel="author" title="Noam Rosenthal" href="noam@webkit.org">
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/resource-loaders.js"></script>
+<script src="resources/entry-invariants.js"></script>
+</head>
+<body>
+<script>
+const {REMOTE_ORIGIN} = get_host_info();
+const delay = 2
+const blank_page = `/resource-timing/resources/blank_page_green.htm`;
+const destUrl = `/common/slow-redirect.py?delay=${delay}&location=${REMOTE_ORIGIN}/${blank_page}`;
+
+const timeBefore = performance.now();
+(async () => {
+ // Wait 10 ms, to ensure the difference between startTime and timeBefore is
+ // larger than 1 ms, to avoid flakiness in browsers that clamp timestamps to
+ // 1 ms.
+ await new Promise(r => step_timeout(r, 10));
+ attribute_test(load.iframe, destUrl, entry => {
+ assert_equals(entry.startTime, entry.fetchStart, 'startTime and fetchStart should be equal');
+ assert_greater_than(entry.startTime, timeBefore, 'startTime and fetchStart should be greater than the time before fetching');
+ // See https://github.com/w3c/resource-timing/issues/264
+ assert_less_than(Math.round(entry.startTime - timeBefore), delay * 1000, 'startTime should not expose redirect delays');
+ }, "Verify that cross-origin resources don't implicitly expose their redirect timings")
+})();
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/cross-origin-status-codes.html b/testing/web-platform/tests/resource-timing/cross-origin-status-codes.html
new file mode 100644
index 0000000000..197a766339
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/cross-origin-status-codes.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html>
+<head>
+<title>Resource Timing: PerformanceResourceTiming attributes shouldn't change
+ if the HTTP status code changes</title>
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=/common/get-host-info.sub.js></script>
+</head>
+<body>
+<img id="img_200">
+<img id="img_307">
+<img id="img_404">
+<img id="img_502">
+<script id="script_200"></script>
+<script id="script_307"></script>
+<script id="script_404"></script>
+<script id="script_502"></script>
+<script>
+
+const listenForPerformanceEntries = num_expected => {
+ return new Promise(resolve => {
+ let results = [];
+ new PerformanceObserver(entryList => {
+ entryList.getEntries().forEach(entry => {
+ if (!entry.name.includes("status-code"))
+ return;
+
+ results.push(entry);
+ if (results.length == num_expected) {
+ resolve(results);
+ }
+ });
+ }).observe({entryTypes: ['resource']});
+ });
+}
+
+promise_test(async t => {
+ const destUrl = get_host_info().HTTP_REMOTE_ORIGIN + '/resource-timing/resources/';
+ const statusCodes = ['200', '307', '404', '502'];
+
+ let expected_entry_count = 0;
+ statusCodes.forEach(status => {
+ document.getElementById(`img_${status}`).src = `${destUrl}status-code.py?status=${status}`;
+ document.getElementById(`script_${status}`).src = `${destUrl}status-code.py?status=${status}&script=1`;
+ expected_entry_count += 2;
+ });
+
+ const entries = await listenForPerformanceEntries(expected_entry_count);
+
+ // We will check that the non-timestamp values of the entry match for all
+ // entries.
+ const keys = [
+ 'entryType',
+ 'nextHopProtocol',
+ 'transferSize',
+ 'encodedBodySize',
+ 'decodedBodySize',
+ ];
+
+ const first = entries[0];
+ entries.slice(1).forEach(entry => {
+ keys.forEach(attribute => {
+ assert_equals(entry[attribute], first[attribute],
+ `There must be no discernible difference for the ${attribute} ` +
+ `attribute but found a difference for the ${entry.name} resource.`);
+ })});
+}, "Make sure cross origin resource fetch failures with different status codes are indistinguishable");
+</script>
diff --git a/testing/web-platform/tests/resource-timing/delivery-type.tentative.any.js b/testing/web-platform/tests/resource-timing/delivery-type.tentative.any.js
new file mode 100644
index 0000000000..e2b408fdd7
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/delivery-type.tentative.any.js
@@ -0,0 +1,90 @@
+// META: global=window,worker
+// META: script=/common/get-host-info.sub.js
+// META: script=/resource-timing/resources/resource-loaders.js
+
+// TODO(crbug/1358591): Rename this file from "tentative" once
+// `w3c/resource-timing#343` is merged.
+
+const {REMOTE_ORIGIN, ORIGIN} = get_host_info();
+
+const redirectBase = new URL(
+ '/resource-timing/resources/redirect-cors.py', REMOTE_ORIGIN).href;
+const cacheAndValidatedBase = new URL(
+ '/resource-timing/resources/cacheable-and-validated.py?content=content',
+ ORIGIN).href;
+
+const mustRevalidate = {headers: {'Cache-Control': 'max-age=0'}};
+
+const fetchAndEatBody = (url, fetchOption) => {
+ return fetch(url, fetchOption).then(response => response.arrayBuffer());
+};
+
+const accumulateEntries = () => {
+ return new Promise(resolve => {
+ const po = new PerformanceObserver(list => {
+ resolve(list);
+ });
+ po.observe({type: "resource", buffered: true});
+ });
+};
+
+const checkDeliveryTypeBase =
+ (list, lookupURL, deliveryTypeForCachedResources) => {
+ const entries = list.getEntriesByName(lookupURL);
+ assert_equals(entries.length, 3, 'Wrong number of entries');
+
+ // 200 response (`cacheMode` is an empty string)
+ assert_equals(entries[0].deliveryType, "",
+ "Expect empty deliveryType for 200 response.");
+ // Cached response (`cacheMode` is "local") or 304 response (`cacheMode` is
+ // "validated").
+ assert_equals(entries[1].deliveryType, deliveryTypeForCachedResources,
+ `Expect "${deliveryTypeForCachedResources}" deliveryType for a
+ cached response.`);
+ assert_equals(entries[2].deliveryType, deliveryTypeForCachedResources,
+ `Expect "${deliveryTypeForCachedResources}" deliveryType for a
+ revalidated response.`);
+};
+
+promise_test(() => {
+ // Use a different URL every time so that the cache behaviour does not depend
+ // on execution order.
+ const initialURL = load.cache_bust(cacheAndValidatedBase);
+ const checkDeliveryType =
+ list => checkDeliveryTypeBase(list, initialURL, "cache");
+ return fetchAndEatBody(initialURL, {}) // 200.
+ .then(() => fetchAndEatBody(initialURL, {})) // Cached.
+ .then(() => fetchAndEatBody(initialURL, mustRevalidate)) // 304.
+ .then(accumulateEntries)
+ .then(checkDeliveryType);
+}, 'PerformanceResourceTiming deliveryType test, same origin.');
+
+promise_test(() => {
+ const cacheAndValidatedURL = load.cache_bust(
+ cacheAndValidatedBase + '&timing_allow_origin=*');
+ const redirectURL = redirectBase
+ + "?timing_allow_origin=*"
+ + `&allow_origin=${encodeURIComponent(ORIGIN)}`
+ + `&location=${encodeURIComponent(cacheAndValidatedURL)}`;
+ const checkDeliveryType =
+ list => checkDeliveryTypeBase(list, redirectURL, "cache");
+ return fetchAndEatBody(redirectURL, {}) // 200.
+ .then(() => fetchAndEatBody(redirectURL, {})) // Cached.
+ .then(() => fetchAndEatBody(redirectURL, mustRevalidate)) // 304.
+ .then(accumulateEntries)
+ .then(checkDeliveryType);
+}, 'PerformanceResourceTiming deliveryType test, cross origin, TAO passes.');
+
+promise_test(() => {
+ const cacheAndValidatedURL = load.cache_bust(cacheAndValidatedBase);
+ const redirectURL = redirectBase
+ + `?allow_origin=${encodeURIComponent(ORIGIN)}`
+ + `&location=${encodeURIComponent(cacheAndValidatedURL)}`;
+ const checkDeliveryType =
+ list => checkDeliveryTypeBase(list, redirectURL, "");
+ return fetchAndEatBody(redirectURL, {}) // 200.
+ .then(() => fetchAndEatBody(redirectURL, {})) // Cached.
+ .then(() => fetchAndEatBody(redirectURL, mustRevalidate)) // 304.
+ .then(accumulateEntries)
+ .then(checkDeliveryType);
+}, 'PerformanceResourceTiming deliveryType test, cross origin, TAO fails.');
diff --git a/testing/web-platform/tests/resource-timing/document-domain-no-impact-opener.html b/testing/web-platform/tests/resource-timing/document-domain-no-impact-opener.html
new file mode 100644
index 0000000000..69df2f27fa
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/document-domain-no-impact-opener.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script>
+// Open a document on one of hosts on the web-platform test domain, so that
+// document.domain will set a valid domain, turning the frame into a
+// cross-origin frame.
+const {OTHER_ORIGIN} = get_host_info();
+const openee = window.open(OTHER_ORIGIN +
+ "/resource-timing/resources/document-domain-no-impact.html");
+fetch_tests_from_window(openee);
+</script>
diff --git a/testing/web-platform/tests/resource-timing/entries-for-network-errors.sub.https.html b/testing/web-platform/tests/resource-timing/entries-for-network-errors.sub.https.html
new file mode 100644
index 0000000000..95849d2826
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/entries-for-network-errors.sub.https.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>This test validates that a failed cross-origin fetch creates an opaque network timing entry.
+</title>
+<link rel="author" title="Noam Rosenthal" href="noam@webkit.org">
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/entry-invariants.js"></script>
+</head>
+<body>
+<script>
+const validDataURL = 'data:,Hello%2C%20World%21'
+const {REMOTE_ORIGIN, ORIGINAL_HOST, HTTP_PORT} = get_host_info();
+const validXmlUrl = '/common/dummy.xml';
+
+network_error_entry_test(
+ `${REMOTE_ORIGIN}${validXmlUrl}`, null, `failed cross-origin requests`);
+
+network_error_entry_test(`/common/redirect.py?location=${validDataURL}`, null, "non-HTTP redirect");
+network_error_entry_test('//{{hosts[][nonexistent]}}/common/dummy.xml', null, "DNS failure");
+network_error_entry_test(`http://${ORIGINAL_HOST}:${HTTP_PORT}/commo/dummy.xml`, null, "Mixed content");
+
+network_error_entry_test('/common/dummy.xml', {cache: 'only-if-cached', mode: 'same-origin'},
+ "only-if-cached resource that was not cached");
+
+network_error_entry_test(
+ `/element-timing/resources/multiple-redirects.py?redirect_count=22&final_resource=${validXmlUrl}`,
+ null, "too many redirects");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/entry-attributes.html b/testing/web-platform/tests/resource-timing/entry-attributes.html
new file mode 100644
index 0000000000..94f219f229
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/entry-attributes.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing: PerformanceResourceTiming attributes</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help"
+ href="https://www.w3.org/TR/resource-timing-2/#timing-allow-origin"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/resource-loaders.js"></script>
+<script src="resources/entry-invariants.js"></script>
+<script>
+attribute_test(
+ load.image, "resources/fake_responses.py#hash=1",
+ entry => {
+ assert_true(entry.name.includes('#hash=1'),
+ "There should be a hash in the resource name");
+ invariants.assert_tao_pass_no_redirect_http(entry);
+ },
+ "Image resources should generate conformant entries");
+
+attribute_test(
+ load.font, "/fonts/Ahem.ttf",
+ invariants.assert_tao_pass_no_redirect_http,
+ "Font resources should generate conformant entries");
+
+attribute_test(
+ load.image, "/common/redirect.py?location=resources/fake_responses.py",
+ invariants.assert_same_origin_redirected_resource,
+ "Same-origin redirects should populate redirectStart/redirectEnd");
+</script>
+</head>
+<body>
+<h1>Description</h1>
+<p>This test validates that PerformanceResourceTiming entries' attributes are
+populated with the correct values.</p>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/event-source-timing.html b/testing/web-platform/tests/resource-timing/event-source-timing.html
new file mode 100644
index 0000000000..917e7c3495
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/event-source-timing.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<meta name="timeout" content="long">
+<title>Resource Timing: EventSource timing behavior</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resource-timing/resources/observe-entry.js"></script>
+</head>
+</script>
+<script>
+ async_test(t => {
+ const repetitions = 2;
+ const url = new URL(`/eventsource/resources/message.py`, location.href);
+ const eventSource = new EventSource(url);
+ let messages = 0;
+ t.add_cleanup(() => eventSource.close());
+ eventSource.addEventListener('message', () => {
+ ++messages;
+ })
+
+ new PerformanceObserver(() => {
+ const entries = performance.getEntriesByName(url);
+ assert_greater_than_equal(entries.length, messages - 1);
+ if (entries.length === repetitions)
+ t.done();
+ }).observe({type: 'resource'});
+ }, "ResourceTiming for EventSource should reflect number of re-connections to source");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/fetch-cross-origin-redirect.https.html b/testing/web-platform/tests/resource-timing/fetch-cross-origin-redirect.https.html
new file mode 100644
index 0000000000..1605e224ab
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/fetch-cross-origin-redirect.https.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test cross-origin fetch redirects have the right values.</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/entry-invariants.js"></script>
+<script src="resources/resource-loaders.js"></script>
+<script>
+
+const {REMOTE_ORIGIN, ORIGIN} = get_host_info();
+const redirect = "/common/redirect.py?" +
+ "location=/resource-timing/resources/empty_script.js";
+const cross_origin_redirect = REMOTE_ORIGIN + redirect;
+const same_origin_redirect = ORIGIN + redirect;
+
+attribute_test(
+ url => fetch(url, {mode: "no-cors", credentials: "include"}),
+ new URL(cross_origin_redirect).href,
+ invariants.assert_cross_origin_redirected_resource,
+ "Test fetching through a cross-origin redirect URL"
+);
+
+attribute_test(
+ url => fetch(url, {mode: "no-cors", credentials: "include"}),
+ new URL(same_origin_redirect).href,
+ invariants.assert_same_origin_redirected_resource,
+ "Test fetching through a same-origin redirect URL"
+);
+
+</script>
diff --git a/testing/web-platform/tests/resource-timing/font-timestamps.html b/testing/web-platform/tests/resource-timing/font-timestamps.html
new file mode 100644
index 0000000000..56ecb5c4bf
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/font-timestamps.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test cross-origin fetch redirects have the right values.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<body>
+<script>
+const load_font = url => {
+ document.body.innerHTML = `
+ <style>
+ @font-face {
+ font-family: ahem;
+ src: url('${url}');
+ }
+ </style>
+ <div style="font-family: ahem;">This fetches ahem font.</div>
+ `;
+ return document.fonts.ready;
+};
+
+const run_test = async (t, url) => {
+ // Set up PerformanceObserver
+ const href = new URL(url).href;
+ const setPerformanceObserver = new Promise(resolve => {
+ const po = new PerformanceObserver(resolve);
+ po.observe({type: "resource"});
+ });
+
+ // Load the font resource and wait for it to be fetched.
+ await load_font(href);
+
+ // Wait for an entry
+ const timeout = new Promise(resolve => t.step_timeout(resolve, 3000));
+ const list = await Promise.race([setPerformanceObserver, timeout]);
+ assert_equals(typeof(list), "object", "No iframe entry was fired");
+ const entries = list.getEntriesByName(url);
+ assert_equals(entries.length, 1);
+
+ // Test entry values
+ const entry = entries[0];
+ assert_greater_than(entry.fetchStart, 0, "fetchStart should be greater than 0 in redirects.");
+ assert_greater_than_equal(entry.domainLookupStart, entry.fetchStart, "domainLookupStart should be more than 0 in same-origin redirect.");
+ assert_greater_than_equal(entry.domainLookupEnd, entry.domainLookupStart, "domainLookupEnd should be more than 0 in same-origin redirect.");
+ assert_greater_than_equal(entry.connectStart, entry.domainLookupEnd, "connectStart should be more than 0 in same-origin redirect.");
+ assert_greater_than_equal(entry.secureConnectionStart, entry.connectStart, "secureConnectionStart should be more than 0 in same-origin redirect.");
+ assert_greater_than_equal(entry.connectEnd, entry.secureConnectionStart, "connectEnd should be more than 0 in same-origin redirect.");
+ assert_greater_than_equal(entry.requestStart, entry.connectEnd, "requestStart should be more than 0 in same-origin redirect.");
+ assert_greater_than_equal(entry.responseStart, entry.requestStart, "responseStart should be more than 0 in same-origin redirect.");
+ assert_greater_than_equal(entry.responseEnd, entry.responseStart, "responseEnd should be greater than 0 in redirects.");
+ assert_greater_than_equal(entry.duration, 0, "duration should be greater than 0 in redirects.");
+}
+
+const {HTTPS_REMOTE_ORIGIN} = get_host_info();
+promise_test(t => {
+ return run_test(t, HTTPS_REMOTE_ORIGIN + "/fonts/Ahem.ttf");
+}, "Test a font's timestamps");
+
+promise_test(t => {
+ return run_test(t, HTTPS_REMOTE_ORIGIN + "/resource-timing/resources/cors-ahem.py?pipe=trickle(d1)");
+}, "Test a font's timestamps with delays");
+</script>
diff --git a/testing/web-platform/tests/resource-timing/frameset-timing.html b/testing/web-platform/tests/resource-timing/frameset-timing.html
new file mode 100644
index 0000000000..1a6facbfc4
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/frameset-timing.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test the sequence of events when reporting timing for frames.</title>
+<frameset>
+ <frame src="resources/frameset-timing-frame.html" />
+</frameset>
diff --git a/testing/web-platform/tests/resource-timing/idlharness.any.js b/testing/web-platform/tests/resource-timing/idlharness.any.js
new file mode 100644
index 0000000000..aa860d3dd1
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/idlharness.any.js
@@ -0,0 +1,24 @@
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+// META: timeout=long
+
+'use strict';
+
+// https://w3c.github.io/resource-timing/
+
+idl_test(
+ ['resource-timing'],
+ ['performance-timeline', 'hr-time', 'dom', 'html'],
+ idl_array => {
+ try {
+ self.resource = performance.getEntriesByType('resource')[0];
+ } catch (e) {
+ // Will be surfaced when resource is undefined below.
+ }
+
+ idl_array.add_objects({
+ Performance: ['performance'],
+ PerformanceResourceTiming: ['resource']
+ });
+ }
+);
diff --git a/testing/web-platform/tests/resource-timing/iframe-failed-commit.html b/testing/web-platform/tests/resource-timing/iframe-failed-commit.html
new file mode 100644
index 0000000000..91094072a6
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/iframe-failed-commit.html
@@ -0,0 +1,131 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing - test that unsuccessful iframes create entries</title>
+<meta name="timeout" content="long">
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href=
+ "https://www.w3.org/TR/resource-timing-2/#resources-included-in-the-performanceresourcetiming-interface"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/entry-invariants.js"></script>
+<script src="resources/resource-loaders.js"></script>
+<body>
+<script>
+
+// Like load.iframe but fetches the iframe under a "default-src 'none'"
+// Content-Security-Policy.
+const load_iframe_with_csp = async path => {
+ return load.iframe_with_attrs(path, {"csp": "default-src 'none'"});
+};
+
+const load_iframe_with_csp_no_navigation = async path => {
+ return load.iframe_with_attrs(path, {"csp": "default-src 'none'"}, () => {}, true);
+}
+
+// Runs a test (labeled by the given label) to verify that loading an iframe
+// with the given URL generates a PerformanceResourceTiming entry and that the
+// entry does not expose sensitive timing attributes.
+const masked_entry_test = (url, label) => {
+ return attribute_test(load.iframe, url,
+ invariants.assert_tao_failure_resource, label);
+};
+
+// Runs a test (labeled by the given label) to verify that loading an iframe
+// with the given URL generates a PerformanceResourceTiming entry and that the
+// entry does expose sensitive timing attributes.
+const unmasked_entry_with_csp_test = (url, label) => {
+ return attribute_test(load_iframe_with_csp, url,
+ invariants.assert_tao_pass_no_redirect_http, label);
+};
+
+// Runs a test (labeled by the given label) to verify that loading an iframe
+// with the given URL under a "default-src 'none' Content-Security-Policy
+// generates a PerformanceResourceTiming entry and that the entry does not
+// expose sensitive timing attributes.
+const masked_entry_with_csp_test = (url, label) => {
+ return attribute_test(load_iframe_with_csp, url,
+ invariants.assert_tao_failure_resource, label);
+};
+
+// Runs a test (labeled by the given label) to verify that loading an iframe
+// with the given URL, an empty response body and under a "default-src 'none'
+// Content-Security-Policy generates a PerformanceResourceTiming entry and that
+// the entry does expose sensitive timing attributes.
+const empty_unmasked_entry_with_csp_test = (url, label) => {
+ return attribute_test(load_iframe_with_csp, url,
+ invariants.assert_tao_pass_no_redirect_http_empty, label);
+};
+
+// Runs a test (labeled by the given label) to verify that loading an iframe
+// with the given URL under a "default-src 'none' Content-Security-Policy
+// generates a PerformanceResourceTiming entry and that the entry does not
+// expose sensitive timing attributes.
+const non_navigating_masked_entry_with_csp_test = (url, label) => {
+ return attribute_test(load_iframe_with_csp_no_navigation, url,
+ invariants.assert_tao_failure_resource, label);
+};
+
+// Runs a test (labeled by the given label) to verify that loading an iframe
+// with the given URL, an empty response body and under a "default-src 'none'
+// Content-Security-Policy generates a PerformanceResourceTiming entry and that
+// the entry does expose sensitive timing attributes.
+const non_navigating_empty_unmasked_entry_with_csp_test = (url, label) => {
+ return attribute_test(load_iframe_with_csp_no_navigation, url,
+ invariants.assert_tao_pass_no_redirect_http_empty, label);
+};
+
+const {REMOTE_ORIGIN, ORIGINAL_HOST, HTTPS_PORT} = get_host_info();
+const unhosted_url = `https://nonexistent.${ORIGINAL_HOST}:${HTTPS_PORT}/`;
+
+masked_entry_test(
+ unhosted_url,
+ "Test iframe from non-existent host gets reported");
+
+masked_entry_test(
+ "/resource-timing/resources/fake_responses.py?redirect=" + unhosted_url,
+ "Test iframe redirecting to non-existent host gets reported");
+
+unmasked_entry_with_csp_test("/resource-timing/resources/csp-default-none.html",
+ "Same-origin iframe that complies with CSP attribute gets reported");
+
+unmasked_entry_with_csp_test("/resource-timing/resources/green-frame.html",
+ "Same-origin iframe that doesn't comply with CSP attribute gets reported");
+
+masked_entry_with_csp_test(
+ new URL("/resource-timing/resources/csp-default-none.html", REMOTE_ORIGIN),
+ "Cross-origin iframe that complies with CSP attribute gets reported");
+
+masked_entry_with_csp_test(
+ new URL("/resource-timing/resources/green-frame.html", REMOTE_ORIGIN),
+ "Cross-origin iframe that doesn't comply with CSP attribute gets reported");
+
+empty_unmasked_entry_with_csp_test(
+ "/resource-timing/resources/200_empty.asis",
+ "Same-origin empty iframe with a 200 status gets reported");
+
+masked_entry_with_csp_test(
+ new URL("/resource-timing/resources/200_empty.asis", REMOTE_ORIGIN),
+ "Cross-origin empty iframe with a 200 status gets reported");
+
+non_navigating_empty_unmasked_entry_with_csp_test(
+ new URL("/resource-timing/resources/204_empty.asis", location.origin),
+ "Same-origin empty iframe with a 204 status gets reported");
+
+non_navigating_empty_unmasked_entry_with_csp_test(
+ new URL("/resource-timing/resources/205_empty.asis", location.origin),
+ "Same-origin empty iframe with a 205 status gets reported");
+
+non_navigating_masked_entry_with_csp_test(
+ new URL("/resource-timing/resources/204_empty.asis", REMOTE_ORIGIN),
+ "Cross-origin empty iframe with a 204 status gets reported");
+
+non_navigating_masked_entry_with_csp_test(
+ new URL("/resource-timing/resources/205_empty.asis", REMOTE_ORIGIN),
+ "Cross-origin empty iframe with a 205 status gets reported");
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/iframe-non-html.html b/testing/web-platform/tests/resource-timing/iframe-non-html.html
new file mode 100644
index 0000000000..a5df3b0348
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/iframe-non-html.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test the sequence of events when reporting iframe timing.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/resource-loaders.js"></script>
+<body>
+<script>
+ function test(href, type) {
+ promise_test(async t => {
+ await load.iframe(href);
+ const entries = performance.getEntriesByType('resource').filter(({name}) => name.includes(href));
+ assert_equals(entries.length, 1);
+ assert_equals(entries[0].initiatorType, 'iframe');
+ }, `Iframes should report resource timing for ${type} iframes`);
+ }
+
+ test('/common/square.png', 'image');
+ test('/common/dummy.xhtml', 'xhtml');
+ test('/common/dummy.xml', 'xml');
+ test('/common/text-plain.txt', 'text');
+</script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/resource-timing/iframe-redirect-without-location.html b/testing/web-platform/tests/resource-timing/iframe-redirect-without-location.html
new file mode 100644
index 0000000000..bae5f3112f
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/iframe-redirect-without-location.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test the sequence of events when reporting iframe timing.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/resource-loaders.js"></script>
+<body>
+<script>
+ promise_test(async t => {
+ const href = new URL('resources/redirect-without-location.py', location.href);
+ await load.iframe(href);
+ const entries = performance.getEntriesByType('resource').filter(({name}) => name.startsWith(href));
+ assert_equals(entries.length, 1);
+ assert_equals(entries[0].initiatorType, 'iframe');
+ }, 'Iframes should report resource timing for redirect responses without a location');
+</script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/resource-timing/iframe-sequence-of-events.html b/testing/web-platform/tests/resource-timing/iframe-sequence-of-events.html
new file mode 100644
index 0000000000..02d1c362c9
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/iframe-sequence-of-events.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test the sequence of events when reporting iframe timing.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/frame-timing.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<body>
+<script>
+ test_frame_timing_before_load_event('iframe');
+
+ const host_info = get_host_info();
+ const types = ['ORIGIN', 'HTTP_REMOTE_ORIGIN', 'HTTP_NOTSAMESITE_ORIGIN'];
+ for (const a of types) {
+ for (const b of types) {
+ for (const tao of [true, false]) {
+ test_frame_timing_change_src('iframe', host_info[a], host_info[b], tao,
+ `Changing the src of an iframe (${a}->${b}) ${tao ? "with" : "without"} TAO should result in an RT entry`);
+ }
+ }
+ }
+</script>
+</body>
diff --git a/testing/web-platform/tests/resource-timing/iframe-with-download.html b/testing/web-platform/tests/resource-timing/iframe-with-download.html
new file mode 100644
index 0000000000..9583024dd8
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/iframe-with-download.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test the sequence of events when reporting iframe timing.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/resource-loaders.js"></script>
+<body>
+<script>
+ promise_test(async t => {
+ const href = new URL('resources/download.asis', location.href);
+ const iframe = document.createElement('iframe');
+ iframe.src = href;
+ const errored = new Promise(resolve => iframe.addEventListener('error', resolve));
+ const loaded = new Promise(resolve => iframe.addEventListener('load', resolve));
+ document.body.appendChild(iframe);
+ const timeout = 2000;
+ t.add_cleanup(() => iframe.remove());
+ const expired = new Promise(resolve => t.step_timeout(resolve, timeout));
+ await Promise.any([loaded, expired, errored]);
+ const entries = performance.getEntriesByType('resource').filter(({name}) => name.startsWith(href));
+ assert_equals(entries.length, 0);
+ }, 'Iframes should not report resource timing for non-handled mime-types (downloads)');
+</script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/resource-timing/image-sequence-of-events.html b/testing/web-platform/tests/resource-timing/image-sequence-of-events.html
new file mode 100644
index 0000000000..630fed78c9
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/image-sequence-of-events.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test the sequence of events when reporting image timing.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+ function test_image_sequence(src, event, t) {
+ const image = document.createElement('img');
+ const absoluteURL = new URL(src, location.href).toString();
+ document.body.appendChild(image);
+ t.add_cleanup(() => image.remove());
+ return new Promise(resolve => {
+ image.addEventListener(event, t.step_func(() => {
+ assert_equals(performance.getEntriesByName(absoluteURL).length, 1);
+ resolve();
+ }));
+ image.src = src;
+ });
+ }
+ promise_test(t => test_image_sequence('resources/blue.png', 'load', t),
+ "An image should receive its load event after the ResourceTiming entry is available");
+
+ promise_test(t => test_image_sequence('resources/nothing-at-all.png', 'error', t),
+ "A non-existent (404) image should receive its error event after the ResourceTiming entry is available");
+
+ promise_test(t => test_image_sequence('resources/invalid.png', 'error', t),
+ "An invalid image should receive its error event after the ResourceTiming entry is available");
+</script>
diff --git a/testing/web-platform/tests/resource-timing/initiator-type-for-script.html b/testing/web-platform/tests/resource-timing/initiator-type-for-script.html
new file mode 100644
index 0000000000..72173398d5
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/initiator-type-for-script.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>This test validates that the initiatorType information for various
+Resource Timing entries is accurate for scripts.</title>
+<link rel="help"
+ href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/resource-loaders.js"></script>
+<script src="resources/entry-invariants.js"></script>
+<!-- Tested resources -->
+<script src="resources/empty_script.js?id=blocking"></script>
+<script src="resources/empty_script.js?id=async" async></script>
+<script src="resources/empty_script.js?id=async_false" async=false></script>
+<script src="resources/empty_script.js?id=defer" defer></script>
+<script>
+document.write("<script src='resources/empty_script.js?id=doc_written'></scr"
+ + "ipt>");
+
+const head = document.getElementsByTagName("head")[0];
+const s1 = document.createElement("script");
+s1.src = "empty_script.js?id=appended";
+head.appendChild(s1);
+
+const s2 = document.createElement("script");
+s2.src = "empty_script.js?id=appended_async";
+s2.async = true;
+head.appendChild(s2);
+
+const s3 = document.createElement("script");
+s3.src = "empty_script.js?id=appended_aync_false";
+s3.async = false;
+head.appendChild(s3);
+
+const s4 = document.createElement("script");
+s4.src = "empty_script.js?id=appended_defer";
+s4.defer = true;
+head.appendChild(s4);
+</script>
+</head>
+<body>
+<script>
+
+const wait_for_onload = () => {
+ return new Promise(resolve => {
+ window.addEventListener("load", resolve);
+})};
+
+promise_test(
+ async () => {
+ await wait_for_onload();
+
+ const entry_list = performance.getEntriesByType("resource");
+ for (entry of entry_list) {
+ if (entry.name.includes("empty_script.js")) {
+ assert_equals(entry.initiatorType, "script",
+ "initiatorType should be 'script' for " + entry.name);
+ }
+ }
+}, "Validate initiatorType for scripts is 'script'");
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/resource-timing/initiator-type/audio.html b/testing/web-platform/tests/resource-timing/initiator-type/audio.html
new file mode 100644
index 0000000000..f09fc61847
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/initiator-type/audio.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing initiator type: audio</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resource-timing/resources/observe-entry.js"></script>
+<script src="resources/initiator-type-test.js"></script>
+</head>
+<body>
+<audio src="/resource-timing/resources/empty.py?id=src"></audio>
+<audio>
+ <source src="/resource-timing/resources/empty.py?id=source-wav"
+ type="audio/wav" />
+</audio>
+<audio>
+ <source src="/resource-timing/resources/empty.py?id=source-mpeg"
+ type="audio/mpeg" />
+</audio>
+<audio>
+ <source src="/resource-timing/resources/empty.py?id=source-ogg"
+ type="audio/ogg" />
+</audio>
+<script>
+ initiator_type_test("empty.py?id=src", "audio", "<audio src> without 'type' attribute");
+ initiator_type_test("empty.py?id=source-wav", "audio", "<source src> with type 'audio/wav'");
+ initiator_type_test("empty.py?id=source-mpeg", "audio", "<source src> with type 'audio/mpeg'");
+ initiator_type_test("empty.py?id=source-ogg", "audio", "<source src> with type 'audio/ogg'");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/initiator-type/dynamic-insertion.html b/testing/web-platform/tests/resource-timing/initiator-type/dynamic-insertion.html
new file mode 100644
index 0000000000..8ce05b3cfe
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/initiator-type/dynamic-insertion.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing - initiatorType with dynamic insertion</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resource-timing/resources/entry-invariants.js"></script>
+<script src="/resource-timing/resources/resource-loaders.js"></script>
+<script>
+ const dynamic_initiator_type_test = (loader, path, expected_type,
+ resource_type) => {
+ attribute_test(loader, path, entry => {
+ assert_equals(entry.initiatorType, expected_type);
+ }, `A ${resource_type} should have the '${expected_type}' initiator type.`);
+ };
+
+ dynamic_initiator_type_test(load.image, "resources/resource_timing_test0.png",
+ "img", "image");
+ // Note that, to download a font, 'load.font' uses a <style> element to
+ // construct a font-face that is then applied to a <div>. Since it's a <style>
+ // element requesting the resource, the initiator type is 'css', not 'font'.
+ dynamic_initiator_type_test(load.font, "/fonts/Ahem.ttf", "css", "font");
+ dynamic_initiator_type_test(load.stylesheet,
+ "resources/resource_timing_test0.css", "link", "stylesheet");
+ dynamic_initiator_type_test(load.iframe, "resources/green.html", "iframe",
+ "iframe");
+ dynamic_initiator_type_test(load.script, "resources/empty.js", "script",
+ "script");
+ dynamic_initiator_type_test(load.xhr_sync, "resources/empty.py",
+ "xmlhttprequest", "XMLHttpRequest");
+</script>
+</head>
+<body>
+<h1>Description</h1>
+<p>This test validates that the initiatorType field is correct even when an
+element is dynamically inserted.</p>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/initiator-type/embed.html b/testing/web-platform/tests/resource-timing/initiator-type/embed.html
new file mode 100644
index 0000000000..c7a505afac
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/initiator-type/embed.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing initiator type: embed</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resource-timing/resources/observe-entry.js"></script>
+<script src="resources/initiator-type-test.js"></script>
+</head>
+<body>
+<embed src="/resource-timing/resources/resource_timing_test0.css"
+ type="text/css">
+<script>
+ initiator_type_test("resource_timing_test0.css", "embed", "<embed>");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/initiator-type/frameset.html b/testing/web-platform/tests/resource-timing/initiator-type/frameset.html
new file mode 100644
index 0000000000..697549a14d
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/initiator-type/frameset.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing initiator type: frameset</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resource-timing/resources/observe-entry.js"></script>
+<script src="resources/initiator-type-test.js"></script>
+<script>
+ initiator_type_test("green.html", "frame", "<frame> in a <frameset>");
+</script>
+</head>
+<!-- Although framesets were deprecated in HTML5, we still want to make sure
+ Resource Timing is emitting entries for the underlying resources' requests.
+-->
+<frameset>
+ <frame src="/resource-timing/resources/green.html">
+</frameset>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/initiator-type/iframe.html b/testing/web-platform/tests/resource-timing/initiator-type/iframe.html
new file mode 100644
index 0000000000..0becd86894
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/initiator-type/iframe.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing initiator type: iframe</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resource-timing/resources/observe-entry.js"></script>
+<script src="resources/initiator-type-test.js"></script>
+</head>
+<body>
+<iframe src="/resource-timing/resources/green.html"></iframe>
+<script>
+ initiator_type_test("green.html", "iframe", "<iframe>");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/initiator-type/img-srcset.html b/testing/web-platform/tests/resource-timing/initiator-type/img-srcset.html
new file mode 100644
index 0000000000..b8c81fbb25
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/initiator-type/img-srcset.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing initiator type: img with srcset attribute</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resource-timing/resources/observe-entry.js"></script>
+<script src="resources/initiator-type-test.js"></script>
+</head>
+<body>
+<img src="/resource-timing/resources/resource_timing_test0.png"
+ srcset="/resource-timing/resources/resource_timing_test0.png?id=srcset 67w"
+ sizes="67px"></img>
+<script>
+ initiator_type_test("resource_timing_test0.png?id=srcset", "img", "<img srcset>");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/initiator-type/img.html b/testing/web-platform/tests/resource-timing/initiator-type/img.html
new file mode 100644
index 0000000000..8e2d305048
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/initiator-type/img.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing initiator type: img</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resource-timing/resources/observe-entry.js"></script>
+<script src="resources/initiator-type-test.js"></script>
+</head>
+<body>
+<img src="/resource-timing/resources/resource_timing_test0.png"></img>
+<script>
+ initiator_type_test("resource_timing_test0.png", "img", "<img>");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/initiator-type/input.html b/testing/web-platform/tests/resource-timing/initiator-type/input.html
new file mode 100644
index 0000000000..a46d416671
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/initiator-type/input.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing initiator type: input</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resource-timing/resources/observe-entry.js"></script>
+<script src="resources/initiator-type-test.js"></script>
+</head>
+<body>
+<input type="image" src="/resource-timing/resources/resource_timing_test0.png">
+<script>
+ initiator_type_test("resource_timing_test0.png", "input", "<input type=image>");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/initiator-type/link.html b/testing/web-platform/tests/resource-timing/initiator-type/link.html
new file mode 100644
index 0000000000..43367ac3d5
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/initiator-type/link.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8" />
+ <title>Resource Timing initiator type: link</title>
+ <link rel="author" title="Google" href="http://www.google.com/" />
+ <link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype" />
+ <link rel="stylesheet" href="/resource-timing/resources/nested.css">
+ <link rel="prefetch" href="/resource-timing/resources/resource_timing_test0.css?id=prefetch">
+ <link rel="preload" as="style" href="/resource-timing/resources/resource_timing_test0.css?id=preload">
+ <link rel="prerender" href="/resource-timing/resources/green.html?id=prerender">
+ <link rel="manifest" href="/resource-timing/resources/manifest.json">
+ <link rel="modulepreload" href="resources/empty.js?id=modulePreload">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resource-timing/resources/observe-entry.js"></script>
+ <script src="resources/initiator-type-test.js"></script>
+</head>
+<body>
+ <script>
+ initiator_type_test("nested.css", "link", "<link>");
+
+ // Verify there are enries for each of nested.css' nested resources.
+ initiator_type_test("resource_timing_test0.css?id=n1", "css", "css resources embedded in css");
+ initiator_type_test("fonts/Ahem.ttf?id=n1", "css", "font resources embedded in css");
+ initiator_type_test("blue.png?id=n1", "css", "image resources embedded in css");
+ initiator_type_test("resource_timing_test0.css?id=prefetch", "link", "<link prefetch>");
+ initiator_type_test("resource_timing_test0.css?id=preload", "link", "<link preload>");
+ initiator_type_test("manifest.json", "link", "<link manifest>");
+ initiator_type_test("resources/empty.js?id=modulePreload", "other", "module preload");
+ </script>
+ <ol>This content forces a font to get fetched</ol>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/initiator-type/misc.html b/testing/web-platform/tests/resource-timing/initiator-type/misc.html
new file mode 100644
index 0000000000..02d01a1633
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/initiator-type/misc.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing initiator type: miscellaneous elements</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resource-timing/resources/observe-entry.js"></script>
+<script src="resources/initiator-type-test.js"></script>
+</head>
+<body background="/resource-timing/resources/blue.png?id=body">
+<input type="image" src="/resource-timing/resources/blue.png?id=input">
+<object type="image/png" data="/resource-timing/resources/blue.png?id=object">
+</object>
+<script>
+ navigator.sendBeacon('/resource-timing/resources/empty.py?id=beacon');
+ fetch('/resource-timing/resources/empty.py?id=fetch');
+ const evtSource = new EventSource('/resource-timing/resources/eventsource.py?id=eventsource');
+</script>
+<script>
+ initiator_type_test("blue.png?id=body", "body", "<body background>");
+ initiator_type_test("blue.png?id=input", "input", "<input type='image'>");
+ initiator_type_test("blue.png?id=object", "object", "<object type='image/png'>");
+ initiator_type_test("empty.py?id=beacon", "beacon", "sendBeacon()");
+ initiator_type_test("empty.py?id=fetch", "fetch", "for fetch()");
+ initiator_type_test("eventsource.py?id=eventsource", "other", "new EventSource()");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/initiator-type/picture.html b/testing/web-platform/tests/resource-timing/initiator-type/picture.html
new file mode 100644
index 0000000000..e384b9e97d
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/initiator-type/picture.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing initiator type: picture</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resource-timing/resources/observe-entry.js"></script>
+<script src="resources/initiator-type-test.js"></script>
+</head>
+<body>
+<picture>
+ <source srcset="blue.png?id=picture-source" type="image/png" />
+ <img src="blue.png?id=picture-img" />
+</picture>
+<picture>
+ <source srcset="blue.png?id=picture-notsupported-source" type="image/notsupported" />
+ <img src="blue.png?id=picture-notsupported-img" />
+</picture>
+<picture>
+ <img src="blue.png?id=picture-img-src"
+ srcset="blue.png?id=picture-img-srcset"
+ sizes="67px"></img>
+</picture>
+<picture>
+ <img src="blue.png?id=picture-99x-img-src"
+ srcset="blue.png?id=picture-99x-img-srcset 99x"
+ sizes="67px"></img>
+</picture>
+<script>
+ initiator_type_test("blue.png?id=picture-source", "img", "<source> in a <picture>");
+ initiator_type_test("blue.png?id=picture-notsupported-img", "img", "<img> in a <picture>");
+ initiator_type_test("blue.png?id=picture-img-srcset", "img", "<img srcset> in a <picture>");
+ initiator_type_test("blue.png?id=picture-99x-img-src", "img", "<img src> in a <picture>");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/initiator-type/resources/initiator-type-test.js b/testing/web-platform/tests/resource-timing/initiator-type/resources/initiator-type-test.js
new file mode 100644
index 0000000000..2b1f844376
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/initiator-type/resources/initiator-type-test.js
@@ -0,0 +1,15 @@
+
+if (observe_entry === undefined) {
+ throw new Error("You must include resource-timing/resources/observe-entry.js "
+ + "before including this script.");
+}
+
+// Asserts that, for the given name, there is/will-be a
+// PerformanceResourceTiming entry that has the given 'initiatorType'. The test
+// is labeled according to the given descriptor.
+const initiator_type_test = (entry_name, expected_initiator, descriptor) => {
+ promise_test(async () => {
+ const entry = await observe_entry(entry_name);
+ assert_equals(entry.initiatorType, expected_initiator);
+ }, `The initiator type for ${descriptor} must be '${expected_initiator}'`);
+};
diff --git a/testing/web-platform/tests/resource-timing/initiator-type/script.html b/testing/web-platform/tests/resource-timing/initiator-type/script.html
new file mode 100644
index 0000000000..dbd6a131de
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/initiator-type/script.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing initiator type: script</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resource-timing/resources/observe-entry.js"></script>
+<script src="resources/initiator-type-test.js"></script>
+</head>
+<body>
+<script src="/resource-timing/resources/empty_script.js"></script>
+<script>
+ const async_xhr = new XMLHttpRequest;
+ async_xhr.open('GET', '/resource-timing/resources/blue.png?id=async_xhr',
+ true);
+ async_xhr.send();
+</script>
+<script>
+ initiator_type_test("empty_script.js", "script", "<script>");
+ initiator_type_test("blue.png?id=async_xhr", "xmlhttprequest", "an asynchronous XmlHTTPRequest");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/initiator-type/style.html b/testing/web-platform/tests/resource-timing/initiator-type/style.html
new file mode 100644
index 0000000000..051496b766
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/initiator-type/style.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing initiator type: style</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resource-timing/resources/observe-entry.js"></script>
+<script src="resources/initiator-type-test.js"></script>
+</head>
+<body>
+<style>
+ iframe {
+ background: url('/resource-timing/resources/blue.png?id=background');
+ }
+ body {
+ cursor: url('/resource-timing/resources/blue.png?id=cursor'), pointer;
+ }
+ ul {
+ list-style-image: url('/resource-timing/resources/blue.png?id=list-style');
+ }
+
+ @font-face {
+ font-family: remoteFontAhem;
+ src: url('/fonts/Ahem.ttf');
+ }
+ .ahem {
+ font-family: remoteFontAhem;
+ }
+</style>
+<iframe>This iframe forces the 'background' resource to be fetched.</iframe>
+<ul>
+ <li>This content forces the 'list-style-image' resource to be fetched.</li>
+</ul>
+<div class="ahem">This content forces the '@font-face' resource to be fetched.</div>
+<script>
+ initiator_type_test("blue.png?id=background", "css", "'background' attributes in <style> elements");
+ initiator_type_test("blue.png?id=cursor", "css", "'cursor' attributes in <style> elements");
+ initiator_type_test("blue.png?id=list-style", "css", "'list-style-image' attributes in <style> elements");
+ initiator_type_test("fonts/Ahem.ttf", "css", "'@font-face' resources");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/initiator-type/svg.html b/testing/web-platform/tests/resource-timing/initiator-type/svg.html
new file mode 100644
index 0000000000..d92f5935d8
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/initiator-type/svg.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing initiator type: svg</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resource-timing/resources/observe-entry.js"></script>
+<script src="resources/initiator-type-test.js"></script>
+</head>
+<body>
+<svg width=200 height=200
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <image href="/resource-timing/resources/blue.png" height="200" width="200"/>
+</svg>
+<script>
+ initiator_type_test("blue.png", "image", "<image> in an <svg>");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/initiator-type/video.html b/testing/web-platform/tests/resource-timing/initiator-type/video.html
new file mode 100644
index 0000000000..16f3b3dea5
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/initiator-type/video.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing initiator type: video</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resource-timing/resources/observe-entry.js"></script>
+<script src="resources/initiator-type-test.js"></script>
+</head>
+<body>
+<video poster="/resource-timing/resources/blue.png?id=poster"></video>
+<video src="/media/test.mp4?id=src" autoplay="true"></video>
+<video autoplay="true">
+ <source src="/media/test.mp4?id=source-mp4" type="video/mp4">
+ <track kind="subtitles" srclang="en" default
+ src="/resource-timing/resources/empty.py?id=track">
+</video>
+<video autoplay="true">
+ <source src="/media/test.ogv?id=source-ogv" type="video/ogg">
+</video>
+<script>
+ initiator_type_test("blue.png?id=poster", "video", "<video poster>");
+ initiator_type_test("media/test.mp4?id=src", "video", "<video src>");
+ initiator_type_test("media/test.mp4?id=source-mp4", "video", "<source src> with type=\"video/mp4\"");
+ initiator_type_test("empty.py?id=track", "track", "<track src>");
+ initiator_type_test("media/test.ogv?id=source-ogv", "video", "<source src> with type=\"video/ogg\"");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/initiator-type/workers.html b/testing/web-platform/tests/resource-timing/initiator-type/workers.html
new file mode 100644
index 0000000000..3a23ad71a3
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/initiator-type/workers.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing initiatorType: worker resources</title>
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resource-timing/resources/observe-entry.js"></script>
+<script src="resources/initiator-type-test.js"></script>
+
+</head>
+
+<script>
+ const moduleWorkerURL = 'resources/empty.js?moduleWorker';
+ const workerURL = 'resources/empty.js?worker';
+ new Worker(moduleWorkerURL, {type: "module"});
+ new Worker(workerURL, {type: "classic"});
+ initiator_type_test(workerURL, "other", "classic worker");
+ initiator_type_test(moduleWorkerURL, "other", "module worker");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/input-sequence-of-events.html b/testing/web-platform/tests/resource-timing/input-sequence-of-events.html
new file mode 100644
index 0000000000..446e24a0bc
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/input-sequence-of-events.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test the sequence of events when reporting input timing.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+ async_test(t => {
+ const input = document.createElement('input');
+ input.type = "image";
+ const absoluteURL = new URL('resources/blue.png', location.href).toString();
+ t.add_cleanup(() => input.remove());
+ input.addEventListener('load', t.step_func(() => {
+ assert_equals(performance.getEntriesByName(absoluteURL).length, 1);
+ t.done();
+ }));
+ input.src = absoluteURL;
+ document.body.appendChild(input);
+ }, "An image input element should receive its load event after the ResourceTiming entry is available");
+
+</script>
diff --git a/testing/web-platform/tests/resource-timing/interim-response-times.h2.html b/testing/web-platform/tests/resource-timing/interim-response-times.h2.html
new file mode 100644
index 0000000000..4b1ca93ff7
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/interim-response-times.h2.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<meta name="timeout" content="long">
+<title>Resource Timing: PerformanceResourceTiming interim resource times</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<script src="/common/utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ const {REMOTE_HOST} = get_host_info();
+ function interim_response_time_test({origin, with100, with103}) {
+ promise_test(async t => {
+ const delay = 500;
+ const url = new URL('/resource-timing/resources/header-delay.h2.py',
+ origin == "same-origin" ?
+ location.href :
+ `${location.protocol}//${REMOTE_HOST}:${location.port}`);
+ url.searchParams.set("delay", delay);
+ if (origin === "cross-origin-with-TAO")
+ url.searchParams.set("tao", "*");
+ if (with100)
+ url.searchParams.set("with100", "true");
+ if (with103)
+ url.searchParams.set("with103", "true");
+ const response = await fetch(url.toString(), {mode: "cors"});
+ assert_equals(response.status, 200)
+ await response.text();
+ const [entry] = performance.getEntriesByName(url.toString());
+ if (origin === "cross-origin") {
+ assert_equals(entry.firstInterimResponseStart, 0);
+ return;
+ }
+ let total_delay = entry.requestStart;
+ if (with100) {
+ total_delay += delay;
+ assert_greater_than(entry.firstInterimResponseStart,
+ total_delay,
+ "firstInterimResponseStart > 100 response");
+ }
+
+ if (with103) {
+ total_delay += delay;
+ if (with100) {
+ assert_less_than_equal(entry.firstInterimResponseStart,
+ total_delay, "firstInterimResponseStart > 100 response");
+ } else {
+ assert_greater_than(entry.firstInterimResponseStart,
+ delay, "firstInterimResponseStart > 100 response");
+ }
+ }
+
+ total_delay += delay;
+ if (!with100 && !with103)
+ assert_equals(entry.firstInterimResponseStart, 0);
+
+ assert_greater_than(entry.responseStart, total_delay,
+ "responseStart");
+ }, `Fetch from ${origin} ${with103 ? "with" : "without"} early hints, ${
+ with100 ? "with" : "without"} 100 response`);
+ }
+
+ for (const with103 of [true, false]) {
+ for (const with100 of [true, false]) {
+ for (origin of ['same-origin', 'cross-origin', 'cross-origin-with-TAO']) {
+ interim_response_time_test({with100, with103, origin});
+ }
+ }
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/interim-response-times.html b/testing/web-platform/tests/resource-timing/interim-response-times.html
new file mode 100644
index 0000000000..a4d03f599e
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/interim-response-times.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<meta name="timeout" content="long">
+<title>Resource Timing: PerformanceResourceTiming interim resource times</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<script src="/common/utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ const {REMOTE_ORIGIN} = get_host_info();
+ function interim_response_time_test({origin, with100, with103}) {
+ promise_test(async t => {
+ const delay = 500;
+ const url = new URL('/resource-timing/resources/header-delay.py',
+ origin == "same-origin" ? location.href : REMOTE_ORIGIN);
+ url.searchParams.set("delay", delay);
+ if (origin === "cross-origin-with-TAO")
+ url.searchParams.set("tao", "*");
+ if (with100)
+ url.searchParams.set("with100", "true");
+ if (with103)
+ url.searchParams.set("with103", "true");
+ const response = await fetch(url.toString(), {mode: "cors"});
+ assert_equals(response.status, 200)
+ await response.text();
+ const [entry] = performance.getEntriesByName(url.toString());
+ if (origin === "cross-origin") {
+ assert_equals(entry.firstInterimResponseStart, 0);
+ return;
+ }
+ let total_delay = entry.requestStart;
+ if (with100) {
+ total_delay += delay;
+ assert_greater_than(entry.firstInterimResponseStart,
+ total_delay,
+ "firstInterimResponseStart > 100 response");
+ }
+
+ if (with103) {
+ total_delay += delay;
+ if (with100) {
+ assert_less_than_equal(entry.firstInterimResponseStart,
+ total_delay, "firstInterimResponseStart > 100 response");
+ } else {
+ assert_greater_than(entry.firstInterimResponseStart,
+ delay, "firstInterimResponseStart > 100 response");
+ }
+ }
+
+ total_delay += delay;
+ if (!with100 && !with103)
+ assert_equals(entry.firstInterimResponseStart, 0);
+
+ assert_greater_than(entry.responseStart, total_delay,
+ "responseStart");
+ }, `Fetch from ${origin} ${with103 ? "with" : "without"} early hints, ${
+ with100 ? "with" : "without"} 100 response`);
+ }
+
+ for (const with103 of [true, false]) {
+ for (const with100 of [true, false]) {
+ for (origin of ['same-origin', 'cross-origin', 'cross-origin-with-TAO']) {
+ interim_response_time_test({with100, with103, origin});
+ }
+ }
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/link-sequence-of-events.html b/testing/web-platform/tests/resource-timing/link-sequence-of-events.html
new file mode 100644
index 0000000000..be9db32cd9
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/link-sequence-of-events.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test the sequence of events when reporting link timing.</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+ promise_test(async t => {
+ const link = document.createElement('link');
+ const delay = 500;
+ const src = `./resources/import.sub.css?delay=${delay}`
+ const absoluteURL = new URL(src, location.href).toString();
+ new PerformanceObserver(t.step_func(() => {
+ const allPerformanceEntries = performance.getEntriesByType('resource');
+ const linkEntry = allPerformanceEntries.find(e => e.name.includes('import.sub.css'));
+ const importEntry = allPerformanceEntries.find(e => e.name.includes('delay-css'));
+ if (!linkEntry || !importEntry)
+ return;
+ const linkEndTime = linkEntry.startTime + linkEntry.duration;
+ const importEndTime = importEntry.startTime + importEntry.duration;
+ assert_greater_than_equal(importEndTime, linkEndTime + delay, "link load should be done before import load");
+ t.done();
+
+ })).observe({type: 'resource'});
+ link.href = src;
+ link.rel = 'stylesheet';
+ document.head.appendChild(link);
+ }, "test that @imports don't affect link resource timings");
+</script>
diff --git a/testing/web-platform/tests/resource-timing/load-from-mem-cache-transfer-size.html b/testing/web-platform/tests/resource-timing/load-from-mem-cache-transfer-size.html
new file mode 100644
index 0000000000..3d2d32d6e6
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/load-from-mem-cache-transfer-size.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <meta charset="utf-8" />
+ <title>This tests transfer size of resource timing when loaded from memory cache.</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="resources/entry-invariants.js"></script>
+</head>
+
+<body>
+ <script>
+ function getScript(url) {
+ const script = document.createElement("script");
+ const loaded = new Promise(resolve => {
+ script.onload = script.onerror = resolve;
+ });
+ script.src = url;
+ document.body.appendChild(script);
+ return loaded;
+ }
+ function add_iframe(url) {
+ return new Promise(function (resolve) {
+ var frame = document.createElement('iframe');
+ frame.src = url;
+ frame.onload = function () { resolve(frame); };
+ document.body.appendChild(frame);
+ });
+ }
+ promise_test(async t => {
+ // Add unique token to url so that each run the url is different to avoid
+ // flakiness.
+ let url = 'resources/resource_timing_test0.js?unique=' +
+ Math.random().toString().substr(2);
+ let frame;
+ return add_iframe('resources/iframe-load-from-mem-cache-transfer-size.html')
+ .then((f) => {
+ frame = f;
+ // Load script onto iframe in order to get it into the memory cache.
+ return frame.contentWindow.getScript(url.split('/')[1])
+ })
+ .then(() => {
+ // Verify that the transferSize in case of normal load is greater than
+ // 0.
+ assert_positive_(
+ frame.contentWindow.performance.getEntriesByType('resource')
+ .filter(e => e.name.includes(url))[0], ['transferSize']);
+
+ // Load the same script onto the parent document. This time the script
+ // is coming from memory cache.
+ return getScript(url);
+ })
+ .then(() => {
+ // Verify that the transferSize in case of memory cache load is 0.
+ assert_zeroed_(
+ window.performance.getEntriesByType('resource')
+ .filter(e => e.name.includes(url))[0], ['transferSize']);
+ });
+ }, "The transferSize of resource timing entries should be 0 when resource \
+ is loaded from memory cache.");
+ </script>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/resource-timing/nested-context-navigations-embed.html b/testing/web-platform/tests/resource-timing/nested-context-navigations-embed.html
new file mode 100644
index 0000000000..f804adbb8a
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/nested-context-navigations-embed.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<meta name=timeout content=long>
+<title>Resource Timing embed navigate - back button navigation</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/nested-contexts.js"></script>
+<script>
+ open_test_window("resources/embed-navigate-back.html",
+ "Test that embed navigations are not observable by the parent, even " +
+ "after history navigations by the parent");
+ open_test_window("resources/embed-navigate-back.html?crossorigin",
+ "Test that crossorigin embed navigations are not observable by the " +
+ "parent, even after history navigations by the parent");
+ open_test_window("resources/embed-navigate-back.html?cross-site",
+ "Test that cross-site embed navigations are not observable by the " +
+ "parent, even after history navigations by the parent");
+
+ open_test_window("resources/embed-navigate.html",
+ "Test that embed navigations are not observable by the parent");
+ open_test_window("resources/embed-navigate.html?crossorigin",
+ "Test that crossorigin embed navigations are not observable by the parent");
+ open_test_window("resources/embed-navigate.html?cross-site",
+ "Test that cross-site embed navigations are not observable by the parent");
+ open_test_window("resources/embed-refresh.html",
+ "Test that embed refreshes are not observable by the parent");
+ open_test_window("resources/embed-refresh.html?crossorigin",
+ "Test that crossorigin embed refreshes are not observable by the parent");
+ open_test_window("resources/embed-refresh.html?cross-site",
+ "Test that cross-site embed refreshes are not observable by the parent");
+
+</script>
+
diff --git a/testing/web-platform/tests/resource-timing/nested-context-navigations-iframe.html b/testing/web-platform/tests/resource-timing/nested-context-navigations-iframe.html
new file mode 100644
index 0000000000..32ab21633c
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/nested-context-navigations-iframe.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<meta name=timeout content=long>
+<title>Resource Timing embed navigate - back button navigation</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/nested-contexts.js"></script>
+<script>
+ open_test_window("resources/iframe-navigate-back.html",
+ "Test that iframe navigations are not observable by the parent, even after history navigations by the parent");
+ open_test_window("resources/iframe-navigate-back.html?crossorigin",
+ "Test that crossorigin iframe navigations are not observable by the parent, even after history navigations by the parent");
+ open_test_window("resources/iframe-navigate-back.html?cross-site",
+ "Test that cross-site iframe navigations are not observable by the parent, even after history navigations by the parent");
+ open_test_window("resources/iframe-navigate.html",
+ "Test that iframe navigations are not observable by the parent");
+ open_test_window("resources/iframe-navigate.html?crossorigin",
+ "Test that crossorigin iframe navigations are not observable by the parent");
+ open_test_window("resources/iframe-navigate.html?cross-site",
+ "Test that cross-site iframe navigations are not observable by the parent");
+ open_test_window("resources/iframe-refresh.html",
+ "Test that iframe refreshes are not observable by the parent");
+ open_test_window("resources/iframe-refresh.html?crossorigin",
+ "Test that crossorigin iframe refreshes are not observable by the parent");
+ open_test_window("resources/iframe-refresh.html?cross-site",
+ "Test that cross-site iframe refreshes are not observable by the parent");
+
+</script>
+
diff --git a/testing/web-platform/tests/resource-timing/nested-context-navigations-object.html b/testing/web-platform/tests/resource-timing/nested-context-navigations-object.html
new file mode 100644
index 0000000000..1508d8829f
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/nested-context-navigations-object.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<meta name=timeout content=long>
+<title>Resource Timing embed navigate - back button navigation</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/nested-contexts.js"></script>
+<script>
+ open_test_window("resources/object-navigate-back.html",
+ "Test that object navigations are not observable by the parent, even " +
+ "after history navigations by the parent");
+ open_test_window("resources/object-navigate-back.html?crossorigin",
+ "Test that crossorigin object navigations are not observable by the " +
+ "parent, even after history navigations by the parent");
+ open_test_window("resources/object-navigate-back.html?cross-site",
+ "Test that cross-site object navigations are not observable by the " +
+ "parent, even after history navigations by the parent");
+ open_test_window("resources/object-navigate.html",
+ "Test that object navigations are not observable by the parent");
+ open_test_window("resources/object-navigate.html?crossorigin",
+ "Test that crossorigin object navigations are not observable by the " +
+ "parent");
+ open_test_window("resources/object-navigate.html?cross-site",
+ "Test that cross-site object navigations are not observable by the " +
+ "parent");
+ open_test_window("resources/object-refresh.html",
+ "Test that object refreshes are not observable by the parent");
+ open_test_window("resources/object-refresh.html?crossorigin",
+ "Test that crossorigin object refreshes are not observable by the parent");
+ open_test_window("resources/object-refresh.html?cross-site",
+ "Test that cross-site object refreshes are not observable by the parent");
+
+</script>
+
diff --git a/testing/web-platform/tests/resource-timing/nested-nav-fallback-timing.html b/testing/web-platform/tests/resource-timing/nested-nav-fallback-timing.html
new file mode 100644
index 0000000000..b8bba5614d
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/nested-nav-fallback-timing.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test ResourceTiming reporting for cross-origin iframe.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/observe-entry.js"></script>
+</head>
+<body>
+<body>
+<script>
+ const {REMOTE_ORIGIN} = get_host_info();
+
+ promise_test(async t => {
+ const iframe = document.createElement('iframe');
+ t.add_cleanup(() => iframe.remove());
+ iframe.src = `${REMOTE_ORIGIN}/resource-timing/resources/delay-load.html`;
+ document.body.appendChild(iframe);
+ const entry = await observe_entry(iframe.src);
+ assert_greater_than(entry.duration, 1000);
+ }, "Cross-origin TAO-fail IFrame entries should report window load time");
+
+ promise_test(async t => {
+ const object = document.createElement('object');
+ object.type = "text/html";
+ t.add_cleanup(() => object.remove());
+ object.data = `${REMOTE_ORIGIN}/resource-timing/resources/delay-load.html`;
+ document.body.appendChild(object);
+ const entry = await observe_entry(object.data);
+ assert_greater_than(entry.duration, 1000);
+ }, "Cross-origin TAO-fail object entries should report window load time");
+
+ </script>
diff --git a/testing/web-platform/tests/resource-timing/nextHopProtocol-is-tao-protected.https.html b/testing/web-platform/tests/resource-timing/nextHopProtocol-is-tao-protected.https.html
new file mode 100644
index 0000000000..b16ff7af75
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/nextHopProtocol-is-tao-protected.https.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing - Check that nextHopProtocol is TAO protected</title>
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/custom-cors-response.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/entry-invariants.js"></script>
+<script src="resources/resource-loaders.js"></script>
+<script src="resources/tao-response.js"></script>
+</head>
+<body>
+<script>
+
+const {HTTPS_REMOTE_ORIGIN} = get_host_info();
+
+const tao_protected_next_hop_test = (loader, item) => {
+ attribute_test(
+ loader, custom_cors_response({}, HTTPS_REMOTE_ORIGIN),
+ entry => assert_equals(entry.nextHopProtocol, "",
+ "nextHopProtocol should be the empty string."),
+ `Fetch TAO-less ${item} from remote origin. Make sure nextHopProtocol ` +
+ "is the empty string."
+ );
+
+ attribute_test(
+ loader, remote_tao_response('*'),
+ entry => assert_not_equals(entry.nextHopProtocol, "",
+ "nextHopProtocol should not be the empty string."),
+ `Fetch TAO'd ${item} from remote origin. Make sure nextHopProtocol ` +
+ "is not the empty string."
+ );
+}
+
+tao_protected_next_hop_test(load.font, "font");
+tao_protected_next_hop_test(load.iframe, "iframe");
+tao_protected_next_hop_test(load.image, "image");
+tao_protected_next_hop_test(path => load.object(path, "text/plain"), "object");
+tao_protected_next_hop_test(load.script, "script");
+tao_protected_next_hop_test(load.stylesheet, "stylesheet");
+tao_protected_next_hop_test(load.xhr_sync, "synchronous xhr");
+tao_protected_next_hop_test(load.xhr_async, "asynchronous xhr");
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/no-entries-for-cross-origin-css-fetched-memory-cache.sub.html b/testing/web-platform/tests/resource-timing/no-entries-for-cross-origin-css-fetched-memory-cache.sub.html
new file mode 100644
index 0000000000..6b60305ded
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/no-entries-for-cross-origin-css-fetched-memory-cache.sub.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Make sure that resources fetched by cross origin CSS are not in the timeline.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<!--
+ This test is mostly the same as no-entries-for-cross-origin-css-fetched.html
+ but loads the sub-resources from another Document (<iframe>) earlier before
+ they are loaded via cross-origin CSSs, so that the resources are loaded from
+ cache and therefore no resource timing entries are created.
+-->
+
+<body>
+ <script>
+ const addLink = () => {
+ return new Promise(resolve => {
+ const link = document.createElement('LINK');
+ link.rel = 'stylesheet';
+ link.id = 'cross_origin_style';
+ link.href = 'http://{{hosts[][www1]}}:{{ports[http][1]}}/resource-timing/resources/nested.css';
+ link.addEventListener('load', resolve);
+ document.body.appendChild(link);
+ });
+ }
+
+ const addFrame = () => {
+ return new Promise(resolve => {
+ const iframe = document.createElement('iframe');
+ iframe.src = 'resources/no-entries-for-cross-origin-css-fetched-memory-cache-iframe.sub.html';
+ iframe.addEventListener('load', resolve);
+ document.body.appendChild(iframe);
+ });
+ }
+
+ promise_test(async t => {
+ await addFrame();
+ await addLink();
+ // Assert no resource timing entries are created when the link is attached.
+ const url = (new URL(document.getElementById('cross_origin_style').href));
+ const prefix = url.protocol + '//' + url.host;
+ assert_equals(performance.getEntriesByName(
+ prefix + '/resource-timing/resources/resource_timing_test0.css?id=n1').length, 0,
+ 'Import should not be in timeline');
+ assert_equals(performance.getEntriesByName(
+ prefix + '/fonts/Ahem.ttf?id=n1').length, 0, 'Font should not be in timeline');
+ assert_equals(performance.getEntriesByName(
+ prefix + '/resource-timing/resources/blue.png?id=n1').length, 0,
+ 'Image should not be in timeline');
+ }, 'Make sure that resources fetched by cross origin CSS are not in the timeline.');
+ </script>
+ <ol>Some content</ol>
+</body>
diff --git a/testing/web-platform/tests/resource-timing/no-entries-for-cross-origin-css-fetched.sub.html b/testing/web-platform/tests/resource-timing/no-entries-for-cross-origin-css-fetched.sub.html
new file mode 100644
index 0000000000..63f9e06e19
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/no-entries-for-cross-origin-css-fetched.sub.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Make sure that resources fetched by cross origin CSS are not in the timeline.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<body>
+<script>
+ const host = get_host_info();
+
+ const link = document.createElement("LINK");
+ link.rel = "stylesheet";
+ link.id = "cross_origin_style";
+
+ /*
+ This stylesheet is fetched from one of:
+ //www1.web–platform.test:64941/resource-timing/resources/nested.css
+ //127.0.0.1:64941/resource-timing/resources/nested.css
+ */
+ link.href = "//" + host.REMOTE_HOST + ":{{ports[http][1]}}{{location[path]}}/../resources/nested.css"
+ document.currentScript.parentNode.insertBefore(link, document.currentScript);
+</script>
+<script>
+ const t = async_test("Make sure that resources fetched by cross origin CSS are not in the timeline.");
+ window.addEventListener("load", function() {
+ // A timeout is needed as entries are not guaranteed to be in the timeline before onload triggers.
+ t.step_timeout(function() {
+ const url = (new URL(document.getElementById("cross_origin_style").href));
+ const prefix = url.protocol + "//" + url.host;
+ assert_equals(performance.getEntriesByName(prefix + "/resource-timing/resources/resource_timing_test0.css?id=n1").length, 0, "Import should not be in timeline");
+ assert_equals(performance.getEntriesByName(prefix + "/fonts/Ahem.ttf?id=n1").length, 0, "Font should not be in timeline");
+ assert_equals(performance.getEntriesByName(prefix + "/resource-timing/resources/blue.png?id=n1").length, 0, "Image should not be in timeline");
+ t.done();
+ }, 200);
+ });
+</script>
+<ol>Some content</ol>
+</body>
+
diff --git a/testing/web-platform/tests/resource-timing/object-not-found-adds-entry.html b/testing/web-platform/tests/resource-timing/object-not-found-adds-entry.html
new file mode 100644
index 0000000000..d11823dd9d
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/object-not-found-adds-entry.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>This test validates that object resource emit resource timing entries.</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/entry-invariants.js"></script>
+<script src="resources/resource-loaders.js"></script>
+</head>
+<body>
+<script>
+const load_image_object = async path => {
+ return load.object(path, "image/png");
+}
+
+const load_null_object = async path => {
+ return load.object(path, null);
+}
+
+attribute_test(
+ load_null_object, "resources/status-code.py?status=200&type=none",
+ invariants.assert_tao_pass_no_redirect_http,
+ "Verify that a 200 null-typed object emits an entry.");
+
+attribute_test(
+ load_null_object, "resources/status-code.py?status=404&type=none",
+ invariants.assert_tao_pass_no_redirect_http,
+ "Verify that a 404 null-typed object emits an entry.");
+
+attribute_test(
+ load_image_object, "resources/status-code.py?status=404&type=img",
+ invariants.assert_tao_pass_no_redirect_http,
+ "Verify that a 404 img-typed object emits an entry.");
+</script>
diff --git a/testing/web-platform/tests/resource-timing/object-not-found-after-TAO-cross-origin-redirect.html b/testing/web-platform/tests/resource-timing/object-not-found-after-TAO-cross-origin-redirect.html
new file mode 100644
index 0000000000..d0dad9381b
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/object-not-found-after-TAO-cross-origin-redirect.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>This test validates resource timing information for a timing allowed cross-origin redirect chain.</title>
+<link rel="help" href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=/common/get-host-info.sub.js></script>
+<script src="resources/webperftestharness.js"></script>
+<script src="resources/webperftestharnessextension.js"></script>
+
+<script>
+ setup({explicit_done: true});
+ test_namespace('getEntriesByName');
+
+ function onload_test()
+ {
+ const context = new PerformanceContext(performance);
+ const entries = context.getEntriesByName(document.querySelector('object').data, 'resource');
+ test_equals(entries.length, 1, 'There should be one entry.');
+ const entry = entries[0];
+
+ test_greater_than(entry.redirectStart, 0, 'redirectStart > 0 in timing allowed cross-origin redirect.');
+ test_equals(entry.redirectStart, entry.startTime, 'redirectStart == startTime in timing allowed cross-origin redirect.');
+ test_greater_than(entry.redirectEnd, entry.redirectStart, 'redirectEnd > redirectStart in timing allowed cross-origin redirect.');
+ test_greater_or_equals(entry.fetchStart, entry.redirectEnd, 'fetchStart >= redirectEnd in timing allowed cross-origin redirect.');
+ done();
+ }
+</script>
+
+</head>
+<body>
+<script>
+ let destUrl = get_host_info().HTTP_REMOTE_ORIGIN + '/resource-timing/resources/multi_redirect.py?';
+ destUrl += 'page_origin=' + 'http://' + document.location.host;
+ destUrl += '&cross_origin=' + get_host_info().HTTP_REMOTE_ORIGIN;
+ destUrl += '&final_resource=' + encodeURIComponent("/resource-timing/resources/status-code.py?status=404&tao_value=*");
+ destUrl += '&tao_steps=3';
+ const objElement = document.createElement('object');
+ objElement.style = 'width: 0px; height: 0px;';
+ objElement.data = destUrl;
+ objElement.onerror = onload_test;
+ document.body.appendChild(objElement);
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/object-not-found-after-cross-origin-redirect.html b/testing/web-platform/tests/resource-timing/object-not-found-after-cross-origin-redirect.html
new file mode 100644
index 0000000000..6990c6c060
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/object-not-found-after-cross-origin-redirect.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>This test validates the values in resource timing for cross-origin
+redirects.</title>
+<meta name="timeout" content="long">
+<link rel="author" title="Noam Rosenthal" href="noam@webkit.org">
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/resource-loaders.js"></script>
+<script src="resources/entry-invariants.js"></script>
+</head>
+<body>
+<script>
+const {REMOTE_ORIGIN} = get_host_info();
+const delay = 1;
+const not_found_page = encodeURIComponent("/resource-timing/resources/status-code.py?status=404");
+const load_null_object = async path => {
+ return load.object(path, null);
+}
+const destUrl = `/common/slow-redirect.py?delay=${delay}&location=${REMOTE_ORIGIN}${not_found_page}`;
+
+const timeBefore = performance.now();
+(async () => {
+ // Wait 10 ms, to ensure the difference between startTime and timeBefore is
+ // larger than 1 ms, to avoid flakiness in browsers that clamp timestamps to
+ // 1 ms.
+ await new Promise(r => step_timeout(r, 10));
+ attribute_test(load_null_object, destUrl, entry => {
+ assert_equals(entry.startTime, entry.fetchStart, 'startTime and fetchStart should be equal');
+ assert_greater_than(entry.startTime, timeBefore, 'startTime and fetchStart should be greater than the time before fetching');
+ // See https://github.com/w3c/resource-timing/issues/264
+ assert_less_than(Math.round(entry.startTime - timeBefore), delay * 1000, 'startTime should not expose redirect delays');
+ }, "Verify that cross-origin object resources don't implicitly expose their redirect timings")
+})();
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/opaque-origin.html b/testing/web-platform/tests/resource-timing/opaque-origin.html
new file mode 100644
index 0000000000..598ee50a59
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/opaque-origin.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing TAO - "null" and opaque origin</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#timing-allow-origin"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+</head>
+<body>
+<h1>Description</h1>
+<p>This test validates that, for a cross origin resource, the timing allow
+check algorithm will correctly distinguish between 'null' and 'Null' values in
+the Timing-Allow-Origin header. An opaque origin's serialization is the string
+"null" and the timing allow origin check needs to do a case-sensitive comparison
+to the Timing-Allow-Origin header.
+</p>
+<iframe id="frameContext"></iframe>
+<script>
+const {ORIGIN} = get_host_info();
+const url = `${ORIGIN}/resource-timing/resources/TAOResponse.py`;
+const frame_content = `data:text/html;utf8,<body>
+ <script src="${ORIGIN}/resources/testharness.js"></` + `script>
+ <script src="${ORIGIN}/resource-timing/resources/entry-invariants.js">
+ </` + `script>
+ <script>
+ attribute_test(fetch, "${url}?tao=null",
+ invariants.assert_tao_pass_no_redirect_http,
+ "An opaque origin should be authorized to see resource timings when the" +
+ "TAO header is the string 'null'");
+ attribute_test(fetch, "${url}?tao=Null",
+ invariants.assert_tao_failure_resource,
+ "An opaque origin must not be authorized to see resource timings when " +
+ "the TAO header is the string 'Null'. (The check for 'null' must be " +
+ "case-sensitive)");
+ </` + `script>
+</body>`;
+
+frameContext.style = "display:none";
+frameContext.src = frame_content;
+fetch_tests_from_window(frameContext.contentWindow);
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/ping-rt-entries.html b/testing/web-platform/tests/resource-timing/ping-rt-entries.html
new file mode 100644
index 0000000000..34dad10b9f
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/ping-rt-entries.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing Entry For hyperlink audit (ping)</title>
+<link rel="help" href="https://w3c.github.io/resource-timing/"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resource-timing/resources/observe-entry.js"></script>
+</head>
+<body>
+<script>
+ promise_test(async t => {
+ const link = document.createElement('a');
+ const delay = 500;
+ const ping = `/xhr/resources/delay.py?ms=${delay}`;
+ link.setAttribute('href', 'resources/close.html');
+ link.setAttribute('target', '_blank');
+ link.setAttribute('ping', ping);
+ link.innerText = 'Link';
+ document.body.appendChild(link);
+ link.click();
+ const entry = await observe_entry(ping);
+ assert_equals(entry.initiatorType, 'ping');
+ assert_greater_than(entry.duration, delay);
+ }, "Hyperlink auditing (<a ping>) should have a resource timing entry");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/queue-entry-regardless-buffer-size.html b/testing/web-platform/tests/resource-timing/queue-entry-regardless-buffer-size.html
new file mode 100644
index 0000000000..ea47ae3a79
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/queue-entry-regardless-buffer-size.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<link rel="help" href="https://w3c.github.io/resource-timing/#dfn-mark-resource-timing">
+<title>This test validates that resource timing entires should always be queued regardless the size of the buffer.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/resource-loaders.js"></script>
+<script src="resources/buffer-full-utilities.js"></script>
+</head>
+<body>
+<script>
+promise_test(async t => {
+ clearBufferAndSetSize(1);
+ let count = 0;
+ const allEntriesReceived = new Promise(resolve => {
+ new PerformanceObserver(t.step_func((list) => {
+ for (const entry of list.getEntries()) {
+ if (entry.name.includes(scriptResources[0])) {
+ count += 1;
+ } else if (entry.name.includes(scriptResources[1])) {
+ count += 1;
+ }
+ }
+ if (count == 2) {
+ resolve();
+ }
+ })).observe({type: 'resource'});
+ });
+ load.script(scriptResources[0]);
+ load.script(scriptResources[1]);
+
+ // Two resource timing entries should be observed regardless the
+ // fact that we've set the buffer size to 1.
+ await allEntriesReceived;
+}, "Test that buffer size has no impact to whether an entry is queued or not");
+</script>
diff --git a/testing/web-platform/tests/resource-timing/redirects.html b/testing/web-platform/tests/resource-timing/redirects.html
new file mode 100644
index 0000000000..ba69907a5f
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/redirects.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing: resources fetched through same-origin redirects</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/resource-loaders.js"></script>
+<script src="resources/entry-invariants.js"></script>
+<script>
+const {HTTPS_NOTSAMESITE_ORIGIN} = get_host_info();
+const redirect_url = `/common/redirect.py`;
+const url_prefix = `${redirect_url}?location=/resource-timing/resources/`;
+const https_url_prefix = `${redirect_url}?location=${HTTPS_NOTSAMESITE_ORIGIN}/resource-timing/resources/`;
+
+attribute_test(
+ load.stylesheet, url_prefix + "resource_timing_test0.css",
+ invariants.assert_same_origin_redirected_resource,
+ "Verify attributes of a redirected stylesheet's PerformanceResourceTiming");
+
+attribute_test(
+ load.image, url_prefix + "blue.png",
+ invariants.assert_same_origin_redirected_resource,
+ "Verify attributes of a redirected image's PerformanceResourceTiming");
+
+attribute_test(
+ load.iframe, url_prefix + "green.html",
+ invariants.assert_same_origin_redirected_resource,
+ "Verify attributes of a redirected iframe's PerformanceResourceTiming");
+
+attribute_test(
+ load.script, url_prefix + "empty_script.js",
+ invariants.assert_same_origin_redirected_resource,
+ "Verify attributes of a redirected script's PerformanceResourceTiming");
+
+attribute_test(
+ load.xhr_sync, url_prefix + "green.html?id=xhr",
+ invariants.assert_same_origin_redirected_resource,
+ "Verify attributes of a redirected synchronous XMLHttpRequest's " +
+ "PerformanceResourceTiming");
+
+attribute_test(
+ load.xhr_sync, https_url_prefix + "green.html?id=xhr",
+ invariants.assert_cross_origin_redirected_resource,
+ "Verify attributes of a synchronous XMLHttpRequest's " +
+ "PerformanceResourceTiming where the initial HTTP request is redirected " +
+ "to a cross-origin HTTPS resource."
+);
+
+</script>
+</head>
+<body>
+<h1>Description</h1>
+<p>This test validates that, when a fetching resources that encounter
+same-origin redirects, attributes of the PerformanceResourceTiming entry
+conform to the specification.</p>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/render-blocking-status-link.html b/testing/web-platform/tests/resource-timing/render-blocking-status-link.html
new file mode 100644
index 0000000000..8c6544db48
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/render-blocking-status-link.html
@@ -0,0 +1,222 @@
+<!DOCTYPE html>
+<head>
+<meta charset="utf-8" />
+<title>This test validates the render blocking status of resources.</title>
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<!-- Start of test cases -->
+<script>
+ // Dynamic style using document.write in head
+ document.write(`
+ <link rel=stylesheet
+ href='resources/empty_style.css?stylesheet-head-dynamic-docWrite'>
+ `);
+ document.write(`
+ <link rel=stylesheet
+ href='resources/empty_style.css?stylesheet-head-dynamic-docWrite-print'
+ media=print>
+ `);
+</script>
+
+<link rel=stylesheet href="resources/empty_style.css?stylesheet-head">
+<link rel=stylesheet href="resources/empty_style.css?stylesheet-head-media-print"
+ media=print>
+<link rel="alternate stylesheet"
+ href="resources/empty_style.css?stylesheet-head-alternate">
+<link rel=preload as=style href="resources/empty_style.css?link-style-head-preload">
+<link rel=preload as=style href="resources/empty_style.css?link-style-preload-used">
+<link rel=stylesheet href="resources/importer.css?stylesheet-importer-head">
+<link rel=stylesheet id="link-head-remove-attr" blocking="render"
+ href="resources/empty_style.css?stylesheet-head-blocking-render-remove-attr">
+<link rel=modulepreload href="resources/empty_script.js?link-head-modulepreload">
+
+<style>@import url(resources/empty_style.css?stylesheet-inline-imported);</style>
+<style media=print>
+ @import url(resources/empty_style.css?stylesheet-inline-imported-print);
+</style>
+</head>
+
+<body>
+
+<link rel=stylesheet href="resources/empty_style.css?stylesheet-body">
+<link rel=stylesheet href="resources/importer.css?stylesheet-importer-body">
+<link rel=stylesheet href="resources/empty_style.css?stylesheet-body-media-print"
+ media=print>
+<link rel=stylesheet blocking="render"
+ href="resources/empty_style.css?stylesheet-body-blocking-render">
+
+<!-- https://html.spec.whatwg.org/multipage/urls-and-fetching.html#blocking-attributes
+ mentions that an element is potentially render-blocking if its blocking
+ tokens set contains "render", or if it is implicitly potentially
+ render-blocking. By default, an element is not implicitly potentially
+ render-blocking.
+ https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet
+ specifies that a link element of type stylesheet is implicitly potentially
+ render-blocking only if the element was created by its node document's parser. -->
+<script>
+ // Dynamic style using document.write in body
+ document.write(`
+ <link rel=stylesheet
+ href='resources/empty_style.css?stylesheet-body-dynamic-docWrite'>
+ `);
+ document.write(`
+ <link rel=stylesheet
+ href='resources/empty_style.css?stylesheet-body-dynamic-docWrite-print'
+ media=print>
+ `);
+
+ // Dynamic style using innerHTML
+ document.head.innerHTML += `
+ <link rel=stylesheet
+ href='resources/empty_style.css?stylesheet-head-dynamic-innerHTML'>
+ `;
+ document.head.innerHTML += `
+ <link rel=stylesheet
+ href='resources/empty_style.css?stylesheet-head-dynamic-innerHTML-print'
+ media=print>
+ `;
+ document.head.innerHTML += `
+ <link rel=stylesheet blocking=render
+ href='resources/empty_style.css?stylesheet-head-blocking-render-dynamic-innerHTML'>
+ `;
+
+ // Dynamic style using DOM API
+ var link = document.createElement("link");
+ link.href = "resources/empty_style.css?stylesheet-head-dynamic-dom";
+ link.rel = "stylesheet";
+ document.head.appendChild(link);
+
+ // Add a dynamic render-blocking style with DOM API
+ link = document.createElement("link");
+ link.href = "resources/empty_style.css?stylesheet-head-blocking-render-dynamic-dom";
+ link.rel = "stylesheet";
+ link.blocking = "render";
+ document.head.appendChild(link);
+
+ // Dynamic style preload using DOM API
+ link = document.createElement("link");
+ link.href = "resources/empty_style.css?link-style-head-preload-dynamic-dom";
+ link.rel = "preload";
+ link.as = "style";
+ document.head.appendChild(link);
+
+ // Dynamic module via modulepreload using DOM API
+ link = document.createElement("link");
+ link.href = "resources/empty_script.js?link-head-modulepreload-dynamic-dom";
+ link.rel = "modulepreload";
+ document.head.appendChild(link);
+
+ // Add a style preload with DOM API to be used later
+ link = document.createElement("link");
+ link.href = "resources/empty_style.css?link-style-preload-used-dynamic";
+ link.rel = "preload";
+ link.as = "style";
+ document.head.appendChild(link);
+ // Use the preload
+ link = document.createElement("link");
+ link.href = "resources/empty_style.css?link-style-preload-used-dynamic";
+ link.rel = "stylesheet";
+ document.head.appendChild(link);
+
+ // Dynamic inline CSS
+ // Add an inline CSS importer
+ document.write(`
+ <style>
+ @import url('resources/empty_style.css?stylesheet-inline-imported-dynamic-docwrite')
+ </style>
+ `);
+ document.write(`
+ <style media=print>
+ @import url('resources/empty_style.css?stylesheet-inline-imported-dynamic-docwrite-print')
+ </style>
+ `);
+
+ // Add a dynamic inline CSS importer using DOM API
+ let style = document.createElement("style");
+ style.textContent = "@import url('resources/empty_style.css?stylesheet-inline-imported-dynamic-dom')";
+ document.head.appendChild(style);
+
+ // Add a dynamic render-blocking inline CSS importer
+ style = document.createElement("style");
+ style.textContent = "@import url('resources/empty_style.css?stylesheet-inline-imported-blocking-render-dynamic-dom')";
+ style.blocking = "render";
+ document.head.appendChild(style);
+
+ // Dynamic CSS importer
+ document.write(`
+ <link rel=stylesheet href='resources/importer_dynamic.css'>
+ `);
+ document.write(`
+ <link rel=stylesheet href='resources/importer_print.css' media=print>
+ `);
+
+ // Removing blocking render attribute after request is made
+ const sheet = document.getElementById("link-head-remove-attr");
+ sheet.blocking = "";
+</script>
+
+<link rel=stylesheet href="resources/empty_style.css?link-style-preload-used">
+
+<script>
+
+const wait_for_onload = () => {
+ return new Promise(resolve => {
+ window.addEventListener("load", resolve);
+})};
+
+promise_test(
+ async () => {
+ const expectedRenderBlockingStatus = {
+ 'stylesheet-head-dynamic-docWrite': 'blocking',
+ 'stylesheet-head-dynamic-docWrite-print': 'non-blocking',
+ 'stylesheet-head': 'blocking',
+ 'stylesheet-head-media-print' : 'non-blocking',
+ 'stylesheet-head-alternate' : 'non-blocking',
+ 'link-style-head-preload' : 'non-blocking',
+ 'stylesheet-importer-head' : 'blocking',
+ 'stylesheet-head-blocking-render-remove-attr' : 'blocking',
+ 'link-head-modulepreload' : 'non-blocking',
+ 'stylesheet-inline-imported' : 'blocking',
+ 'stylesheet-inline-imported-print' : 'non-blocking',
+ 'stylesheet-body': 'non-blocking',
+ 'stylesheet-importer-body' : 'non-blocking',
+ 'stylesheet-body-media-print' : 'non-blocking',
+ 'stylesheet-body-blocking-render' : 'non-blocking',
+ 'stylesheet-body-dynamic-docWrite' : 'non-blocking',
+ 'stylesheet-body-dynamic-docWrite-print': 'non-blocking',
+ 'stylesheet-head-dynamic-innerHTML' : 'non-blocking',
+ 'stylesheet-head-dynamic-innerHTML-print' : 'non-blocking',
+ 'stylesheet-head-blocking-render-dynamic-innerHTML' : 'blocking',
+ 'stylesheet-head-dynamic-dom' : 'non-blocking',
+ 'stylesheet-head-blocking-render-dynamic-dom' : 'blocking',
+ 'link-style-head-preload-dynamic-dom' : 'non-blocking',
+ 'link-head-modulepreload-dynamic-dom' : 'non-blocking',
+ 'link-style-preload-used' : 'non-blocking',
+ 'link-style-preload-used-dynamic' : 'non-blocking',
+ 'stylesheet-inline-imported-dynamic-docwrite': 'blocking',
+ 'stylesheet-inline-imported-dynamic-docwrite-print' : 'non-blocking',
+ 'stylesheet-inline-imported-dynamic-dom' : 'non-blocking',
+ 'stylesheet-inline-imported-blocking-render-dynamic-dom' : 'blocking',
+ 'stylesheet-imported' : 'blocking',
+ 'stylesheet-imported-print' : 'non-blocking',
+ 'stylesheet-imported-dynamic' : 'non-blocking'
+ };
+
+ await wait_for_onload();
+
+ const entry_list = performance.getEntriesByType("resource");
+ for (entry of entry_list) {
+ if (entry.name.includes("empty_style.css") ||
+ entry.name.includes("importer.css") ||
+ entry.name.includes("empty_script.js")) {
+ key = entry.name.split("?").pop();
+ expectedStatus = expectedRenderBlockingStatus[key];
+ assert_equals(entry.renderBlockingStatus, expectedStatus,
+ `render blocking status for ${entry.name} should be ${expectedStatus}`);
+ }
+ }
+}, "Validate render blocking status of link resources in PerformanceResourceTiming");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/resource-timing/render-blocking-status-script.html b/testing/web-platform/tests/resource-timing/render-blocking-status-script.html
new file mode 100644
index 0000000000..bcd55b8994
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/render-blocking-status-script.html
@@ -0,0 +1,196 @@
+<!DOCTYPE html>
+<head>
+<meta charset="utf-8" />
+<title>This test validates the render blocking status of resources.</title>
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<!-- Start of test cases -->
+<script src="resources/empty_script.js?script-head"></script>
+<script type="module" src="resources/empty_script.js?script-head-module"></script>
+<script async type=module
+ src="resources/empty_script.js?script-head-async-module">
+</script>
+<script async src="resources/empty_script.js?script-head-async"></script>
+<script defer src="resources/empty_script.js?script-head-defer"></script>
+<script blocking=render
+ src="resources/empty_script.js?script-head-blocking-render">
+</script>
+<script async blocking=render
+ src="resources/empty_script.js?script-head-async-blocking-render">
+</script>
+<script type=module blocking=render
+ src="resources/empty_script.js?script-head-module-blocking-render">
+</script>
+<script async type=module blocking=render
+ src="resources/empty_script.js?script-head-async-module-blocking-render">
+</script>
+<script defer blocking=render
+ src="resources/empty_script.js?script-head-defer-blocking-render">
+</script>
+
+<script id="script-head-remove-attr" blocking=render
+ src="resources/empty_script.js?script-head-blocking-render-remove-attr">
+</script>
+
+<script>
+ document.write(`
+ <script defer
+ src="resources/empty_script.js?script-head-defer-dynamic-docwrite">
+ <\/script>`);
+</script>
+</head>
+
+<body>
+
+<script src="resources/empty_script.js?script-body"></script>
+<script type="module" src="resources/empty_script.js?script-body-module"></script>
+<script async type=module
+ src="resources/empty_script.js?script-body-async-module">
+</script>
+<script async src="resources/empty_script.js?script-body-async"></script>
+<script defer src="resources/empty_script.js?script-body-defer"></script>
+
+<script>
+ const script = document.createElement("script");
+ script.src = "resources/empty_script.js?script-head-dynamic-dom";
+ document.head.appendChild(script);
+
+ // Dynamic explicitly async script
+ const async_script = document.createElement("script");
+ async_script.src = "resources/empty_script.js?script-head-async-dynamic-dom";
+ async_script.async = true;
+ document.head.appendChild(async_script);
+
+ // Dynamic non-async script
+ // https://html.spec.whatwg.org/multipage/scripting.html#script-processing-model
+ // mentions that a script element has to be parser-inserted to be
+ // implicitly potentially render-blocking
+ const non_async_script = document.createElement("script");
+ non_async_script.src = "resources/empty_script.js?script-head-non-async-dynamic-dom";
+ non_async_script.async = false;
+ document.head.appendChild(non_async_script);
+
+ // Dynamic defer script
+ const defer_script = document.createElement("script");
+ defer_script.src = "resources/empty_script.js?script-head-defer-dynamic-dom";
+ defer_script.defer = true;
+ document.head.appendChild(defer_script);
+
+ // Dynamic explicitly render-blocking script
+ const blocking_script = document.createElement("script");
+ blocking_script.src = "resources/empty_script.js?script-head-blocking-render-dynamic-dom";
+ blocking_script.blocking = "render";
+ document.head.appendChild(blocking_script);
+
+ // Dynamic explicitly render-blocking module script
+ const blocking_module_script = document.createElement("script");
+ blocking_module_script.src = "resources/empty_script.js?script-head-module-blocking-render-dynamic-dom";
+ blocking_module_script.type = "module";
+ blocking_module_script.blocking = "render";
+ document.head.appendChild(blocking_module_script);
+
+ // Dynamic async module script
+ const async_module_script = document.createElement("script");
+ async_module_script.src = "resources/empty_script.js?script-head-async-module-dynamic-dom";
+ async_module_script.type = "module";
+ async_module_script.async = true;
+ document.head.appendChild(async_module_script);
+
+ // Dynamic async render-blocking module script
+ const async_blocking_module_script = document.createElement("script");
+ async_blocking_module_script.src = "resources/empty_script.js?script-head-async-module-blocking-render-dynamic-dom";
+ async_blocking_module_script.type = "module";
+ async_blocking_module_script.async = true;
+ async_blocking_module_script.blocking = "render"
+ document.head.appendChild(async_blocking_module_script);
+
+ // Add a module that imports more modules
+ const importer_script = document.createElement("script");
+ importer_script.src = "resources/fake_responses.py?url=importer.js";
+ importer_script.type = "module";
+ document.head.appendChild(importer_script);
+
+ // Add an async module that imports more modules
+ const importer_async_script = document.createElement("script");
+ importer_async_script.src = "resources/fake_responses.py?url=importer_async.js";
+ importer_async_script.type = "module";
+ importer_async_script.async = true;
+ document.head.appendChild(importer_async_script);
+
+ // Removing blocking render attribute after request is made
+ const script_element = document.getElementById("script-head-remove-attr");
+ script_element.blocking = "";
+</script>
+
+
+<script>
+
+const wait_for_onload = () => {
+ return new Promise(resolve => {
+ window.addEventListener("load", resolve);
+})};
+
+promise_test(
+ async () => {
+ const expectedRenderBlockingStatus = {
+ 'script-head': 'blocking',
+ 'script-head-module' : 'non-blocking',
+ 'script-head-async-module' : 'non-blocking',
+ 'script-head-async' : 'non-blocking',
+ 'script-head-defer' : 'non-blocking',
+ 'script-head-blocking-render' : 'blocking',
+ 'script-head-async-blocking-render' : 'blocking',
+ 'script-head-module-blocking-render' : 'blocking',
+ 'script-head-async-module-blocking-render' : 'blocking',
+ 'script-head-defer-blocking-render' : 'blocking',
+ 'script-head-blocking-render-remove-attr' : 'blocking',
+ 'script-head-defer-dynamic-docwrite' : 'non-blocking',
+ 'script-body' : 'non-blocking',
+ 'script-body-module' : 'non-blocking',
+ 'script-body-async-module' : 'non-blocking',
+ 'script-body-async' : 'non-blocking',
+ 'script-body-defer' : 'non-blocking',
+ 'script-head-dynamic-dom': 'non-blocking',
+ 'script-head-async-dynamic-dom' : 'non-blocking',
+ 'script-head-non-async-dynamic-dom': 'non-blocking',
+ 'script-head-defer-dynamic-dom' : 'non-blocking',
+ 'script-head-blocking-render-dynamic-dom' : 'blocking',
+ 'script-head-module-blocking-render-dynamic-dom' : 'blocking',
+ 'script-head-async-module-dynamic-dom' : 'non-blocking',
+ 'script-head-async-module-blocking-render-dynamic-dom' : 'blocking',
+ 'script-head-import-defer' : 'non-blocking',
+ 'script-head-import-defer-dynamic' : 'non-blocking',
+ 'script-head-import-async' : 'non-blocking',
+ 'script-head-import-async-dynamic' : 'non-blocking',
+ 'script-importer' : 'non-blocking',
+ 'script-importer-async' : 'non-blocking'
+ };
+
+ await wait_for_onload();
+
+ const entry_list = performance.getEntriesByType("resource");
+ for (entry of entry_list) {
+ if (entry.name.includes("empty_script.js")) {
+ key = entry.name.split("?").pop();
+ expectedStatus = expectedRenderBlockingStatus[key];
+ assert_equals(entry.renderBlockingStatus, expectedStatus,
+ `render blocking status for ${entry.name} should be ${expectedStatus}`);
+ }
+ else if (entry.name.includes("importer.js")){
+ key = 'script-importer';
+ expectedStatus = expectedRenderBlockingStatus[key];
+ assert_equals(entry.renderBlockingStatus, expectedStatus,
+ `render blocking status for ${entry.name} should be ${expectedStatus}`);
+ }
+ else if (entry.name.includes("importer_async.js")){
+ key = 'script-importer-async';
+ expectedStatus = expectedRenderBlockingStatus[key];
+ assert_equals(entry.renderBlockingStatus, expectedStatus,
+ `render blocking status for ${entry.name} should be ${expectedStatus}`);
+ }
+ }
+}, "Validate render blocking status of script resources in PerformanceResourceTiming");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/resource-timing/resource-ignore-data-url.html b/testing/web-platform/tests/resource-timing/resource-ignore-data-url.html
new file mode 100644
index 0000000000..a7056a8080
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resource-ignore-data-url.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing ignores resources with data: URIs</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#resources-included-in-the-performanceresourcetiming-interface"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/resource-loaders.js"></script>
+</head>
+<body>
+<img src="data:image/gif;base64,R0lGODlhAQABAIAAAOTm7AAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=="></img>
+<script>
+ promise_test(async t => {
+ const promise = new Promise(resolve => {
+ new PerformanceObserver(t.step_func(list => {
+ const entries = list.getEntries();
+ const dataEntries = entries.filter(e => e.name.includes('data:'));
+ assert_equals(dataEntries.length, 0, 'There must be no entry for `data: URL`.');
+ const blueEntries = entries.filter(e => e.name.includes('blue.png'));
+ if (blueEntries.length) {
+ // We can finish the test once we see the entry with blue.png.
+ resolve();
+ }
+ })).observe({entryTypes: ['resource']});
+ });
+ // Wait until the document is loaded.
+ await new Promise(resolve => {
+ window.addEventListener('load', resolve);
+ });
+ // Add the blue.png image after document is loaded to ensure we've received
+ // all of the previous Resource Timing entries.
+ load.image('blue.png');
+ return promise;
+ }, 'Resources with data: URIs must not be surfaced in Resource Timing');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/resource-reload-TAO.html b/testing/web-platform/tests/resource-timing/resource-reload-TAO.html
new file mode 100644
index 0000000000..83a1e921bb
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resource-reload-TAO.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing - TAO on reload</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-timing-allow-origin"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<iframe id="test_iframe" src="resources/iframe-reload-TAO.html"></iframe>
+<script>
+window.onload = () => {
+ fetch_tests_from_window(test_iframe.contentWindow);
+};
+</script>
+</body>
diff --git a/testing/web-platform/tests/resource-timing/resource-timing-level1.js b/testing/web-platform/tests/resource-timing/resource-timing-level1.js
new file mode 100644
index 0000000000..6167777fe6
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resource-timing-level1.js
@@ -0,0 +1,476 @@
+"use strict";
+
+window.onload =
+ function () {
+ setup({ explicit_timeout: true });
+
+ /** Number of milliseconds to delay when the server injects pauses into the response.
+
+ This should be large enough that we can distinguish it from noise with high confidence,
+ but small enough that tests complete quickly. */
+ var serverStepDelay = 250;
+
+ var mimeHtml = "text/html";
+ var mimeText = "text/plain";
+ var mimePng = "image/png";
+ var mimeScript = "application/javascript";
+ var mimeCss = "text/css";
+
+ /** Hex encoding of a a 150x50px green PNG. */
+ var greenPng = "0x89504E470D0A1A0A0000000D494844520000006400000032010300000090FBECFD00000003504C544500FF00345EC0A80000000F49444154281563601805A36068020002BC00011BDDE3900000000049454E44AE426082";
+
+ /** Array containing test cases to run. Initially, it contains the one-off 'about:blank" test,
+ but additional cases are pushed below by expanding templates. */
+ var testCases = [
+ {
+ description: "No timeline entry for about:blank",
+ test:
+ function (test) {
+ // Insert an empty IFrame.
+ var frame = document.createElement("iframe");
+
+ // Wait for the IFrame to load and ensure there is no resource entry for it on the timeline.
+ //
+ // We use the 'createOnloadCallbackFn()' helper which is normally invoked by 'initiateFetch()'
+ // to avoid setting the IFrame's src. It registers a test step for us, finds our entry on the
+ // resource timeline, and wraps our callback function to automatically vet invariants.
+ frame.onload = createOnloadCallbackFn(test, frame, "about:blank",
+ function (initiator, entry) {
+ assert_equals(entry, undefined, "Inserting an IFrame with a src of 'about:blank' must not add an entry to the timeline.");
+ assertInvariants(
+ test,
+ function () {
+ test.done();
+ });
+ });
+
+ document.body.appendChild(frame);
+
+ // Paranoid check that the new IFrame has loaded about:blank.
+ assert_equals(
+ frame.contentWindow.location.href,
+ "about:blank",
+ "'Src' of new <iframe> must be 'about:blank'.");
+ }
+ },
+ ];
+
+ // Create cached/uncached tests from the following array of templates. For each template entry,
+ // we add two identical test cases to 'testCases'. The first case initiates a fetch to populate the
+ // cache. The second request initiates a fetch with the same URL to cover the case where we hit
+ // the cache (if the caching policy permits caching).
+ [
+ { initiator: "iframe", response: "(done)", mime: mimeHtml },
+ { initiator: "xmlhttprequest", response: "(done)", mime: mimeText },
+ // Multiple browsers seem to cheat a bit and race onLoad of images. Microsoft https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/2379187
+ // { initiator: "img", response: greenPng, mime: mimePng },
+ { initiator: "script", response: '"";', mime: mimeScript },
+ { initiator: "link", response: ".unused{}", mime: mimeCss },
+ ]
+ .forEach(function (template) {
+ testCases.push({
+ description: "'" + template.initiator + " (Populate cache): The initial request populates the cache (if appropriate).",
+ test: function (test) {
+ initiateFetch(
+ test,
+ template.initiator,
+ getSyntheticUrl(
+ "mime:" + encodeURIComponent(template.mime)
+ + "&send:" + encodeURIComponent(template.response),
+ /* allowCaching = */ true),
+ function (initiator, entry) {
+ test.done();
+ });
+ }
+ });
+
+ testCases.push({
+ description: "'" + template.initiator + " (Potentially Cached): Immediately fetch the same URL, exercising the cache hit path (if any).",
+ test: function (test) {
+ initiateFetch(
+ test,
+ template.initiator,
+ getSyntheticUrl(
+ "mime:" + encodeURIComponent(template.mime)
+ + "&send:" + encodeURIComponent(template.response),
+ /* allowCaching = */ true),
+ function (initiator, entry) {
+ test.done();
+ });
+ }
+ });
+ });
+
+ // Create responseStart/responseEnd tests from the following array of templates. In this test, the server delays before
+ // responding with responsePart1, then delays again before completing with responsePart2. The test looks for the expected
+ // pauses before responseStart and responseEnd.
+ [
+ { initiator: "iframe", responsePart1: serverStepDelay + "ms;", responsePart2: (serverStepDelay * 2) + "ms;(done)", mime: mimeHtml },
+ { initiator: "xmlhttprequest", responsePart1: serverStepDelay + "ms;", responsePart2: (serverStepDelay * 2) + "ms;(done)", mime: mimeText },
+ // Multiple browsers seem to cheat a bit and race img.onLoad and setting responseEnd. Microsoft https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/2379187
+ // { initiator: "img", responsePart1: greenPng.substring(0, greenPng.length / 2), responsePart2: "0x" + greenPng.substring(greenPng.length / 2, greenPng.length), mime: mimePng },
+ { initiator: "script", responsePart1: '"', responsePart2: '";', mime: mimeScript },
+ { initiator: "link", responsePart1: ".unused{", responsePart2: "}", mime: mimeCss },
+ ]
+ .forEach(function (template) {
+ testCases.push({
+ description: "'" + template.initiator + ": " + serverStepDelay + "ms delay before 'responseStart', another " + serverStepDelay + "ms delay before 'responseEnd'.",
+ test: function (test) {
+ initiateFetch(
+ test,
+ template.initiator,
+ getSyntheticUrl(serverStepDelay + "ms" // Wait, then echo back responsePart1
+ + "&mime:" + encodeURIComponent(template.mime)
+ + "&send:" + encodeURIComponent(template.responsePart1)
+ + "&" + serverStepDelay + "ms" // Wait, then echo back responsePart2
+ + "&send:" + encodeURIComponent(template.responsePart2)),
+
+ function (initiator, entry) {
+ // Per https://w3c.github.io/resource-timing/#performanceresourcetiming:
+ // If no redirects (or equivalent) occur, this redirectStart/End must return zero.
+ assert_equals(entry.redirectStart, 0, "When no redirect occurs, redirectStart must be 0.");
+ assert_equals(entry.redirectEnd, 0, "When no redirect occurs, redirectEnd must be 0.");
+
+ // Server creates a gap between 'requestStart' and 'responseStart'.
+ assert_greater_than_equal(
+ entry.responseStart,
+ entry.requestStart + serverStepDelay,
+ "'responseStart' must be " + serverStepDelay + "ms later than 'requestStart'.");
+
+ // Server creates a gap between 'responseStart' and 'responseEnd'.
+ assert_greater_than_equal(
+ entry.responseEnd,
+ entry.responseStart + serverStepDelay,
+ "'responseEnd' must be " + serverStepDelay + "ms later than 'responseStart'.");
+
+ test.done();
+ });
+ }
+ });
+ });
+
+ // Create redirectEnd/responseStart tests from the following array of templates. In this test, the server delays before
+ // redirecting to a new synthetic response, then delays again before responding with 'response'. The test looks for the
+ // expected pauses before redirectEnd and responseStart.
+ [
+ { initiator: "iframe", response: serverStepDelay + "ms;redirect;" + (serverStepDelay * 2) + "ms;(done)", mime: mimeHtml },
+ { initiator: "xmlhttprequest", response: serverStepDelay + "ms;redirect;" + (serverStepDelay * 2) + "ms;(done)", mime: mimeText },
+ // Multiple browsers seem to cheat a bit and race img.onLoad and setting responseEnd. Microsoft https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/2379187
+ // { initiator: "img", response: greenPng, mime: mimePng },
+ { initiator: "script", response: '"";', mime: mimeScript },
+ { initiator: "link", response: ".unused{}", mime: mimeCss },
+ ]
+ .forEach(function (template) {
+ testCases.push({
+ description: "'" + template.initiator + " (Redirected): " + serverStepDelay + "ms delay before 'redirectEnd', another " + serverStepDelay + "ms delay before 'responseStart'.",
+ test: function (test) {
+ initiateFetch(
+ test,
+ template.initiator,
+ getSyntheticUrl(serverStepDelay + "ms" // Wait, then redirect to a second page that waits
+ + "&redirect:" // before echoing back the response.
+ + encodeURIComponent(
+ getSyntheticUrl(serverStepDelay + "ms"
+ + "&mime:" + encodeURIComponent(template.mime)
+ + "&send:" + encodeURIComponent(template.response)))),
+ function (initiator, entry) {
+ // Per https://w3c.github.io/resource-timing/#performanceresourcetiming:
+ // "[If redirected, startTime] MUST return the same value as redirectStart.
+ assert_equals(entry.startTime, entry.redirectStart, "startTime must be equal to redirectStart.");
+
+ // Server creates a gap between 'redirectStart' and 'redirectEnd'.
+ assert_greater_than_equal(
+ entry.redirectEnd,
+ entry.redirectStart + serverStepDelay,
+ "'redirectEnd' must be " + serverStepDelay + "ms later than 'redirectStart'.");
+
+ // Server creates a gap between 'requestStart' and 'responseStart'.
+ assert_greater_than_equal(
+ entry.responseStart,
+ entry.requestStart + serverStepDelay,
+ "'responseStart' must be " + serverStepDelay + "ms later than 'requestStart'.");
+
+ test.done();
+ });
+ }
+ });
+ });
+
+ // Ensure that responseStart only measures the time up to the first few
+ // bytes of the header response. This is tested by writing an HTTP 1.1
+ // status line, followed by a flush, then a pause before the end of the
+ // headers. The test makes sure that responseStart is not delayed by
+ // this pause.
+ [
+ { initiator: "iframe", response: "(done)", mime: mimeHtml },
+ { initiator: "xmlhttprequest", response: "(done)", mime: mimeText },
+ { initiator: "script", response: '"";', mime: mimeScript },
+ { initiator: "link", response: ".unused{}", mime: mimeCss },
+ ]
+ .forEach(function (template) {
+ testCases.push({
+ description: "'" + template.initiator + " " + serverStepDelay + "ms delay in headers does not affect responseStart'",
+ test: function (test) {
+ initiateFetch(
+ test,
+ template.initiator,
+ getSyntheticUrl("status:200"
+ + "&flush"
+ + "&" + serverStepDelay + "ms"
+ + "&mime:" + template.mime
+ + "&send:" + encodeURIComponent(template.response)),
+ function (initiator, entry) {
+ // Test that the delay between 'responseStart' and
+ // 'responseEnd' includes the delay, which implies
+ // that 'responseStart' was measured at the time of
+ // status line receipt.
+ assert_greater_than_equal(
+ entry.responseEnd,
+ entry.responseStart + serverStepDelay,
+ "Delay after HTTP/1.1 status should not affect 'responseStart'.");
+
+ test.done();
+ });
+ }
+ });
+ });
+
+ // Function to run the next case in the queue.
+ var currentTestIndex = -1;
+ function runNextCase() {
+ var testCase = testCases[++currentTestIndex];
+ if (testCase !== undefined) {
+ async_test(testCase.test, testCase.description);
+ }
+ }
+
+ // When a test completes, run the next case in the queue.
+ add_result_callback(runNextCase);
+
+ // Start the first test.
+ runNextCase();
+
+ /** Iterates through all resource entries on the timeline, vetting all invariants. */
+ function assertInvariants(test, done) {
+ // Multiple browsers seem to cheat a bit and race img.onLoad and setting responseEnd. Microsoft https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/2379187
+ // Yield for 100ms to workaround a suspected race where window.onload fires before
+ // script visible side-effects from the wininet/urlmon thread have finished.
+ test.step_timeout(
+ test.step_func(
+ function () {
+ performance
+ .getEntriesByType("resource")
+ .forEach(
+ function (entry, index, entries) {
+ assertResourceEntryInvariants(entry);
+ });
+
+ done();
+ }),
+ 100);
+ }
+
+ /** Assets the invariants for a resource timeline entry. */
+ function assertResourceEntryInvariants(actual) {
+ // Example from http://w3c.github.io/resource-timing/#resources-included:
+ // "If an HTML IFRAME element is added via markup without specifying a src attribute,
+ // the user agent may load the about:blank document for the IFRAME. If at a later time
+ // the src attribute is changed dynamically via script, the user agent may fetch the new
+ // URL resource for the IFRAME. In this case, only the fetch of the new URL would be
+ // included as a PerformanceResourceTiming object in the Performance Timeline."
+ assert_not_equals(
+ actual.name,
+ "about:blank",
+ "Fetch for 'about:blank' must not appear in timeline.");
+
+ assert_not_equals(actual.startTime, 0, "startTime");
+
+ // Per https://w3c.github.io/resource-timing/#performanceresourcetiming:
+ // "[If redirected, startTime] MUST return the same value as redirectStart. Otherwise,
+ // [startTime] MUST return the same value as fetchStart."
+ assert_in_array(actual.startTime, [actual.redirectStart, actual.fetchStart],
+ "startTime must be equal to redirectStart or fetchStart.");
+
+ // redirectStart <= redirectEnd <= fetchStart <= domainLookupStart <= domainLookupEnd <= connectStart
+ assert_less_than_equal(actual.redirectStart, actual.redirectEnd, "redirectStart <= redirectEnd");
+ assert_less_than_equal(actual.redirectEnd, actual.fetchStart, "redirectEnd <= fetchStart");
+ assert_less_than_equal(actual.fetchStart, actual.domainLookupStart, "fetchStart <= domainLookupStart");
+ assert_less_than_equal(actual.domainLookupStart, actual.domainLookupEnd, "domainLookupStart <= domainLookupEnd");
+ assert_less_than_equal(actual.domainLookupEnd, actual.connectStart, "domainLookupEnd <= connectStart");
+
+ // Per https://w3c.github.io/resource-timing/#performanceresourcetiming:
+ // "This attribute is optional. User agents that don't have this attribute available MUST set it
+ // as undefined. [...] If the secureConnectionStart attribute is available but HTTPS is not used,
+ // this attribute MUST return zero."
+ assert_true(actual.secureConnectionStart == undefined ||
+ actual.secureConnectionStart == 0 ||
+ actual.secureConnectionStart >= actual.connectEnd, "secureConnectionStart time");
+
+ // connectStart <= connectEnd <= requestStart <= responseStart <= responseEnd
+ assert_less_than_equal(actual.connectStart, actual.connectEnd, "connectStart <= connectEnd");
+ assert_less_than_equal(actual.connectEnd, actual.requestStart, "connectEnd <= requestStart");
+ assert_less_than_equal(actual.requestStart, actual.responseStart, "requestStart <= responseStart");
+ assert_less_than_equal(actual.responseStart, actual.responseEnd, "responseStart <= responseEnd");
+ }
+
+ /** Helper function to resolve a relative URL */
+ function canonicalize(url) {
+ var div = document.createElement('div');
+ div.innerHTML = "<a></a>";
+ div.firstChild.href = url;
+ div.innerHTML = div.innerHTML;
+ return div.firstChild.href;
+ }
+
+ /** Generates a unique string, used by getSyntheticUrl() to avoid hitting the cache. */
+ function createUniqueQueryArgument() {
+ var result =
+ "ignored_"
+ + Date.now()
+ + "-"
+ + ((Math.random() * 0xFFFFFFFF) >>> 0)
+ + "-"
+ + syntheticRequestCount;
+
+ return result;
+ }
+
+ /** Count of the calls to getSyntheticUrl(). Used by createUniqueQueryArgument() to generate unique strings. */
+ var syntheticRequestCount = 0;
+
+ /** Return a URL to a server that will synthesize an HTTP response using the given
+ commands. (See SyntheticResponse.aspx). */
+ function getSyntheticUrl(commands, allowCache) {
+ syntheticRequestCount++;
+
+ var url =
+ canonicalize("./SyntheticResponse.py") // ASP.NET page that will synthesize the response.
+ + "?" + commands; // Commands that will be used.
+
+ if (allowCache !== true) { // If caching is disallowed, append a unique argument
+ url += "&" + createUniqueQueryArgument(); // to the URL's query string.
+ }
+
+ return url;
+ }
+
+ /** Given an 'initiatorType' (e.g., "img") , it triggers the appropriate type of fetch for the specified
+ url and invokes 'onloadCallback' when the fetch completes. If the fetch caused an entry to be created
+ on the resource timeline, the entry is passed to the callback. */
+ function initiateFetch(test, initiatorType, url, onloadCallback) {
+ assertInvariants(
+ test,
+ function () {
+ log("--- Begin: " + url);
+
+ switch (initiatorType) {
+ case "script":
+ case "img":
+ case "iframe": {
+ var element = document.createElement(initiatorType);
+ document.body.appendChild(element);
+ element.onload = createOnloadCallbackFn(test, element, url, onloadCallback);
+ element.src = url;
+ break;
+ }
+ case "link": {
+ var element = document.createElement(initiatorType);
+ element.rel = "stylesheet";
+ document.body.appendChild(element);
+ element.onload = createOnloadCallbackFn(test, element, url, onloadCallback);
+ element.href = url;
+ break;
+ }
+ case "xmlhttprequest": {
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', url, true);
+ xhr.onreadystatechange = createOnloadCallbackFn(test, xhr, url, onloadCallback);
+ xhr.send();
+ break;
+ }
+ default:
+ assert_unreached("Unsupported initiatorType '" + initiatorType + "'.");
+ break;
+ }});
+ }
+
+ /** Used by 'initiateFetch' to register a test step for the asynchronous callback, vet invariants,
+ find the matching resource timeline entry (if any), and pass it to the given 'onloadCallback'
+ when invoked. */
+ function createOnloadCallbackFn(test, initiator, url, onloadCallback) {
+ // Remember the number of entries on the timeline prior to initiating the fetch:
+ var beforeEntryCount = performance.getEntriesByType("resource").length;
+
+ return test.step_func(
+ function() {
+ // If the fetch was initiated by XHR, we're subscribed to the 'onreadystatechange' event.
+ // Ignore intermediate callbacks and wait for the XHR to complete.
+ if (Object.getPrototypeOf(initiator) === XMLHttpRequest.prototype) {
+ if (initiator.readyState != 4) {
+ return;
+ }
+ }
+
+ var entries = performance.getEntriesByType("resource");
+ var candidateEntry = entries[entries.length - 1];
+
+ switch (entries.length - beforeEntryCount)
+ {
+ case 0:
+ candidateEntry = undefined;
+ break;
+ case 1:
+ // Per https://w3c.github.io/resource-timing/#performanceresourcetiming:
+ // "This attribute MUST return the resolved URL of the requested resource. This attribute
+ // MUST NOT change even if the fetch redirected to a different URL."
+ assert_equals(candidateEntry.name, url, "'name' did not match expected 'url'.");
+ logResourceEntry(candidateEntry);
+ break;
+ default:
+ assert_unreached("At most, 1 entry should be added to the performance timeline during a fetch.");
+ break;
+ }
+
+ assertInvariants(
+ test,
+ function () {
+ onloadCallback(initiator, candidateEntry);
+ });
+ });
+ }
+
+ /** Log the given text to the document element with id='output' */
+ function log(text) {
+ var output = document.getElementById("output");
+ output.textContent += text + "\r\n";
+ }
+
+ add_completion_callback(function () {
+ var output = document.getElementById("output");
+ var button = document.createElement('button');
+ output.parentNode.insertBefore(button, output);
+ button.onclick = function () {
+ var showButton = output.style.display == 'none';
+ output.style.display = showButton ? null : 'none';
+ button.textContent = showButton ? 'Hide details' : 'Show details';
+ }
+ button.onclick();
+ var iframes = document.querySelectorAll('iframe');
+ for (var i = 0; i < iframes.length; i++)
+ iframes[i].parentNode.removeChild(iframes[i]);
+ });
+
+ /** pretty print a resource timeline entry. */
+ function logResourceEntry(entry) {
+ log("[" + entry.entryType + "] " + entry.name);
+
+ ["startTime", "redirectStart", "redirectEnd", "fetchStart", "domainLookupStart", "domainLookupEnd", "connectStart", "secureConnectionStart", "connectEnd", "requestStart", "responseStart", "responseEnd"]
+ .forEach(
+ function (property, index, array) {
+ var value = entry[property];
+ log(property + ":\t" + value);
+ });
+
+ log("\r\n");
+ }
+ };
diff --git a/testing/web-platform/tests/resource-timing/resource-timing-level1.sub.html b/testing/web-platform/tests/resource-timing/resource-timing-level1.sub.html
new file mode 100644
index 0000000000..093d254221
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resource-timing-level1.sub.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Resource-Timing Level 1</title>
+ <meta name="timeout" content="long">
+ <!-- To aid debugability, explicitly link the testharness's CSS to avoid demand
+ loading it while the test executes. -->
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+ <link rel="help" href="https://w3c.github.io/resource-timing/">
+</head>
+<body>
+ <div id="log"></div>
+ <pre id="output"></pre>
+ <script src="resource-timing-level1.js"></script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/resource_connection_reuse_mixed_content.html b/testing/web-platform/tests/resource-timing/resource_connection_reuse_mixed_content.html
new file mode 100644
index 0000000000..51c04ee604
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resource_connection_reuse_mixed_content.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing connection reuse</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/webperftestharness.js"></script>
+<script src="resources/webperftestharnessextension.js"></script>
+<script>
+setup({explicit_done: true});
+let iframe;
+let d;
+let body;
+
+// Explicitly test the namespace before we start testing.
+test_namespace('getEntriesByType');
+
+function setup_iframe() {
+ iframe = document.getElementById('frameContext');
+ d = iframe.contentWindow.document;
+ iframe.addEventListener('load', onload_test, false);
+}
+
+function onload_test() {
+ const entries = iframe.contentWindow.performance.getEntriesByType('resource');
+
+ // When a persistent connection is used, follow-on resources should be included as PerformanceResourceTiming objects.
+ test_equals(entries.length, 2, 'There should be 2 PerformanceEntries');
+
+ if (entries.length >= 2) {
+ // When a persistent connection is used, for the resource that reuses the socket, connectStart and connectEnd should have the same value as fetchStart.
+ const entry = entries[1];
+ test_equals(entry.fetchStart, entry.connectStart, 'connectStart and fetchStart should be the same');
+ test_equals(entry.fetchStart, entry.connectEnd, 'connectEnd and fetchStart should be the same');
+ // secureConnectionStart is the same as fetchStart since the subresource is fetched over https
+ test_equals(entry.fetchStart, entry.secureConnectionStart, 'secureConnectionStart and fetchStart should be the same');
+ test_equals(entry.fetchStart, entry.domainLookupStart, 'domainLookupStart and fetchStart should be the same')
+ test_equals(entry.fetchStart, entry.domainLookupEnd, 'domainLookupEnd and fetchStart should be the same')
+ }
+
+ done();
+}
+
+window.setup_iframe = setup_iframe;
+</script>
+</head>
+<body>
+<h1>Description</h1>
+<p>This test validates that connectStart and connectEnd are the same when a connection is reused (e.g. when a persistent connection is used).</p>
+<div id="log"></div>
+<iframe id="frameContext" src="resources/fake_responses_https.sub.html"></iframe>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/resource_connection_reuse_mixed_content_redirect.html b/testing/web-platform/tests/resource-timing/resource_connection_reuse_mixed_content_redirect.html
new file mode 100644
index 0000000000..a46d14c9f1
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resource_connection_reuse_mixed_content_redirect.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing connection reuse</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/webperftestharness.js"></script>
+<script src="resources/webperftestharnessextension.js"></script>
+<script>
+setup({explicit_done: true});
+let iframe;
+let d;
+let body;
+
+// Explicitly test the namespace before we start testing.
+test_namespace('getEntriesByType');
+
+function setup_iframe() {
+ iframe = document.getElementById('frameContext');
+ d = iframe.contentWindow.document;
+ iframe.addEventListener('load', onload_test, false);
+}
+
+function onload_test() {
+ const entries = iframe.contentWindow.performance.getEntriesByType('resource');
+
+ // When a persistent connection is used, follow-on resources should be included as PerformanceResourceTiming objects.
+ test_equals(entries.length, 2, 'There should be 2 PerformanceEntries');
+
+ if (entries.length >= 2) {
+ // When a persistent connection is used, for the resource that reuses the socket, connectStart and connectEnd should have the same value as fetchStart.
+ const entry = entries[1];
+ test_equals(entry.fetchStart, entry.connectStart, 'connectStart and fetchStart should be the same');
+ test_equals(entry.fetchStart, entry.connectEnd, 'connectEnd and fetchStart should be the same');
+ // secureConnectionStart is the same as fetchStart since the subresource is eventually redirected to https.
+ test_equals(entry.fetchStart, entry.secureConnectionStart, 'secureConnectionStart and fetchStart should be the same');
+ test_equals(entry.fetchStart, entry.domainLookupStart, 'domainLookupStart and fetchStart should be the same')
+ test_equals(entry.fetchStart, entry.domainLookupEnd, 'domainLookupEnd and fetchStart should be the same')
+ }
+
+ done();
+}
+
+window.setup_iframe = setup_iframe;
+</script>
+</head>
+<body>
+<h1>Description</h1>
+<p>This test validates that connectStart and connectEnd are the same when a connection is reused (e.g. when a persistent connection is used).</p>
+<div id="log"></div>
+<iframe id="frameContext" src="resources/fake_responses_https_redirect.sub.html"></iframe>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/resource_dedicated_worker.html b/testing/web-platform/tests/resource-timing/resource_dedicated_worker.html
new file mode 100644
index 0000000000..6d27245ab9
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resource_dedicated_worker.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing in dedicated workers</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="http://www.w3.org/TR/resource-timing/"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/webperftestharness.js"></script>
+<script src="resources/webperftestharnessextension.js"></script>
+<link rel="stylesheet" href="resources/resource_timing_test0.css" />
+<script>
+setup({explicit_done: true});
+const worker = new Worker("resources/worker_with_images.js");
+worker.onmessage = function(event) {
+ const context = new PerformanceContext(window.performance);
+ const entries = context.getEntriesByType('resource');
+ test_equals(entries.length, 6, "There should be six entries: 4 scripts, 1 stylesheet, and the worker itself");
+ done();
+}
+</script>
+</head>
+<body>
+<h1>Description</h1>
+<p>This test validates that resources requested by dedicated workers don't appear in the main document.</p>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/resource_nested_dedicated_worker.worker.js b/testing/web-platform/tests/resource-timing/resource_nested_dedicated_worker.worker.js
new file mode 100644
index 0000000000..2c9f5f9542
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resource_nested_dedicated_worker.worker.js
@@ -0,0 +1,17 @@
+importScripts("/resources/testharness.js");
+
+async_test(function() {
+ const worker = new Worker('resources/worker_with_images.js');
+ worker.onmessage = this.step_func_done((event) => {
+ const childNumEntries = event.data;
+ assert_equals(2, childNumEntries,
+ "There should be two resource timing entries: 2 image XHRs");
+
+ const parentNumEntries = performance.getEntries().length;
+ assert_equals(2, parentNumEntries,
+ "There should be two resource timing entries: " +
+ "one is for importScripts() and the another is for a nested worker");
+ worker.terminate();
+ });
+}, "Resource timing for nested dedicated workers");
+done();
diff --git a/testing/web-platform/tests/resource-timing/resource_reparenting.html b/testing/web-platform/tests/resource-timing/resource_reparenting.html
new file mode 100644
index 0000000000..7d4947fa77
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resource_reparenting.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing reparenting elements</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="http://www.w3.org/TR/resource-timing/#dom-performanceresourcetiming-initiatortype"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/webperftestharness.js"></script>
+<script src="resources/webperftestharnessextension.js"></script>
+<script>
+let iframe;
+function setup_iframe() {
+ iframe = document.getElementById('frameContext');
+ const d = iframe.contentWindow.document;
+ const iframeBody = d.createElement('body');
+
+ const move_to_parent = d.createElement('img');
+ move_to_parent.src = 'blue.png?id=move_to_parent';
+ iframeBody.appendChild(move_to_parent);
+ iframeBody.removeChild(move_to_parent);
+
+ const parentBody = document.getElementsByTagName('body')[0];
+ parentBody.appendChild(move_to_parent);
+
+ const move_to_child = document.createElement('img');
+ move_to_child.src = 'blue.png?id=move_to_child';
+ parentBody.appendChild(move_to_child);
+ parentBody.removeChild(move_to_child);
+ iframeBody.appendChild(move_to_child);
+}
+function onload_test() {
+ const context = new PerformanceContext(iframe.contentWindow.performance);
+ const entries = context.getEntriesByType('resource');
+
+ const index = window.location.pathname.lastIndexOf('/');
+ const pathname = window.location.pathname.substring(0, index);
+ let expected_entries = {};
+ expected_entries[pathname + '/resources/blue.png?id=move_to_child'] = 'img';
+
+ test_resource_entries(entries, expected_entries);
+}
+window.setup_iframe = setup_iframe;
+</script>
+</head>
+<body>
+<h1>Description</h1>
+<p>This test validates that reparenting an element doesn't change the initiator document.</p>
+<div id="log"></div>
+<iframe id="frameContext" onload="onload_test();" src="resources/inject_resource_test.html"></iframe>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/resource_subframe_self_navigation.html b/testing/web-platform/tests/resource-timing/resource_subframe_self_navigation.html
new file mode 100644
index 0000000000..5843f88307
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resource_subframe_self_navigation.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+function numberOfDownloads(url) {
+ let absoluteURL = new URL(url, location.href).href;
+ return performance.getEntriesByName(absoluteURL).length;
+}
+
+function waitForSubFrameLoad() {
+ return new Promise((resolve) => {
+ window.subFrameLoaded = () => {
+ window.subFrameLoaded = null;
+ resolve();
+ };
+ });
+}
+
+function runTest(type) {
+ performance.clearResourceTimings();
+ let elem = document.createElement(type);
+ if (type === 'object')
+ elem.data = 'resources/self_navigation.html?' + type;
+ else
+ elem.src = 'resources/self_navigation.html?' + type;
+ document.body.appendChild(elem);
+ return waitForSubFrameLoad().then(() => {
+ let resources = performance.getEntriesByType('resource');
+ assert_equals(numberOfDownloads('resources/self_navigation.html?' + type), 1);
+ assert_equals(numberOfDownloads('resources/notify_parent.html?redirected'), 0);
+ document.body.removeChild(elem);
+ });
+}
+
+promise_test(
+ () => runTest('iframe'),
+ "Subsequent <iframe> navigations don't appear in the resource-timing buffer.");
+
+promise_test(
+ () => runTest('frame'),
+ "Subsequent <frame> navigations don't appear in the resource-timing buffer.");
+
+promise_test(
+ () => runTest('embed'),
+ "Subsequent <embed> navigations don't appear in the resource-timing buffer.");
+
+promise_test(
+ () => runTest('object'),
+ "Subsequent <object> navigations don't appear in the resource-timing buffer.");
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/resource-timing/resource_timing.worker.js b/testing/web-platform/tests/resource-timing/resource_timing.worker.js
new file mode 100644
index 0000000000..dafd2e9af6
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resource_timing.worker.js
@@ -0,0 +1,64 @@
+importScripts("/resources/testharness.js");
+
+function check(initiatorType, protocol) {
+ let entries = performance.getEntries();
+ assert_equals(entries.length, 1);
+
+ assert_true(entries[0] instanceof PerformanceEntry);
+ assert_equals(entries[0].entryType, "resource");
+ assert_true(entries[0].startTime > 0);
+ assert_true(entries[0].duration > 0);
+
+ assert_true(entries[0] instanceof PerformanceResourceTiming);
+ assert_equals(entries[0].initiatorType, initiatorType);
+ assert_equals(entries[0].nextHopProtocol, protocol);
+}
+
+async_test(t => {
+ performance.clearResourceTimings();
+
+ // Fetch
+ fetch("resources/empty.js")
+ .then(r => r.blob())
+ .then(blob => {
+ check("fetch", "http/1.1");
+ })
+
+ // XMLHttpRequest
+ .then(() => {
+ return new Promise(resolve => {
+ performance.clearResourceTimings();
+ let xhr = new XMLHttpRequest();
+ xhr.onload = () => {
+ check("xmlhttprequest", "http/1.1");
+ resolve();
+ };
+ xhr.open("GET", "resources/empty.js");
+ xhr.send();
+ });
+ })
+
+ // Sync XMLHttpREquest
+ .then(() => {
+ performance.clearResourceTimings();
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", "resources/empty.js", false);
+ xhr.send();
+
+ check("xmlhttprequest", "http/1.1");
+ })
+
+ // ImportScripts
+ .then(() => {
+ performance.clearResourceTimings();
+ importScripts(["resources/empty.js"]);
+ check("other", "http/1.1");
+ })
+
+ // All done.
+ .then(() => {
+ t.done();
+ });
+}, "Performance Resource Entries in workers");
+
+done();
diff --git a/testing/web-platform/tests/resource-timing/resource_timing_content_length.html b/testing/web-platform/tests/resource-timing/resource_timing_content_length.html
new file mode 100644
index 0000000000..32bd8a97e0
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resource_timing_content_length.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>This test validates the value of encodedBodySize in certain situations.</title>
+<link rel="help" href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+ function test_resource_timing_for_content_length({actualContentLength, lengthHeader}, title) {
+ promise_test(async t => {
+ const content = new Array(actualContentLength).fill('x').join('')
+ const url = `resources/resource-timing-content-length.py?content=${content}&length=${lengthHeader}`
+ fetch(url)
+ const entry = await new Promise(resolve => new PerformanceObserver((entryList, observer) => {
+ observer.disconnect()
+ resolve(entryList.getEntries()[0])
+ }).observe({entryTypes: ['resource']}))
+
+ const expectedContentLength = Number.isInteger(lengthHeader) ? Math.min(actualContentLength, lengthHeader) : actualContentLength
+ assert_equals(entry.encodedBodySize, expectedContentLength)
+ }, title);
+ }
+
+ test_resource_timing_for_content_length({actualContentLength: 3, lengthHeader: 'auto'},
+ "encodedBodySize should be equal to the actual byte size of the content")
+ test_resource_timing_for_content_length({actualContentLength: 13, lengthHeader: 'none'},
+ "encodedBodySize should be equal to the actual byte size of the content when no header present")
+ test_resource_timing_for_content_length({actualContentLength: 7, lengthHeader: 3},
+ "encodedBodySize should be equal to the actual byte size of the content when header value is lower than actual content")
+ test_resource_timing_for_content_length({actualContentLength: 8, lengthHeader: 40},
+ "encodedBodySize should be equal to the actual byte size of the content when header value is higher than actual content")
+</script>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/resources/200.https.asis b/testing/web-platform/tests/resource-timing/resources/200.https.asis
new file mode 100644
index 0000000000..5b7c25f4ca
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/200.https.asis
@@ -0,0 +1,5 @@
+HTTP/1.0 200 OK
+Content-Length: 0
+Timing-Allow-Origin: *
+
+
diff --git a/testing/web-platform/tests/resource-timing/resources/200_empty.asis b/testing/web-platform/tests/resource-timing/resources/200_empty.asis
new file mode 100644
index 0000000000..b5d10bda32
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/200_empty.asis
@@ -0,0 +1,3 @@
+HTTP/1.0 200 OK
+Content-Length: 0
+
diff --git a/testing/web-platform/tests/resource-timing/resources/204_empty.asis b/testing/web-platform/tests/resource-timing/resources/204_empty.asis
new file mode 100644
index 0000000000..3d9151326b
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/204_empty.asis
@@ -0,0 +1,3 @@
+HTTP/1.0 204 OK
+Content-Length: 0
+
diff --git a/testing/web-platform/tests/resource-timing/resources/205_empty.asis b/testing/web-platform/tests/resource-timing/resources/205_empty.asis
new file mode 100644
index 0000000000..2c06998c1e
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/205_empty.asis
@@ -0,0 +1,3 @@
+HTTP/1.0 205 OK
+Content-Length: 0
+
diff --git a/testing/web-platform/tests/resource-timing/resources/TAOResponse.py b/testing/web-platform/tests/resource-timing/resources/TAOResponse.py
new file mode 100644
index 0000000000..60392b8e10
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/TAOResponse.py
@@ -0,0 +1,64 @@
+import os
+
+def main(request, response):
+ if b'origin' in request.headers:
+ origin = request.headers[b'origin']
+ response.headers.set(b'Access-Control-Allow-Origin', origin)
+
+ tao = request.GET.first(b'tao')
+ img = request.GET.first(b'img') if b'img' in request.GET else None
+
+ if tao == b'zero':
+ # zero TAO value, fail
+ pass
+ elif tao == b'wildcard':
+ # wildcard, pass
+ response.headers.set(b'Timing-Allow-Origin', b'*')
+ elif tao == b'null':
+ # null, fail unless it's an opaque origin
+ response.headers.set(b'Timing-Allow-Origin', b'null')
+ elif tao == b'Null':
+ # case-insensitive null, fail
+ response.headers.set(b'Timing-Allow-Origin', b'Null')
+ elif tao == b'origin':
+ # case-sensitive match for origin, pass
+ response.headers.set(b'Timing-Allow-Origin', origin)
+ elif tao.startswith(b'origin_port'):
+ # case-sensitive match for origin and port, pass
+ origin_parts = origin.split(b':')
+ host = origin_parts[0] + b':' + origin_parts[1]
+ port = tao.split(b'origin_port_')[1]
+ response.headers.set(b'Timing-Allow-Origin', host + b':' + port)
+ elif tao == b'space':
+ # space separated list of origin and wildcard, fail
+ response.headers.set(b'Timing-Allow-Origin', (origin + b' *'))
+ elif tao == b'multi':
+ # more than one TAO values, separated by comma, pass
+ response.headers.set(b'Timing-Allow-Origin', origin)
+ response.headers.append(b'Timing-Allow-Origin', b'*')
+ elif tao == b'multi_wildcard':
+ # multiple wildcards, separated by comma, pass
+ response.headers.set(b'Timing-Allow-Origin', b'*')
+ response.headers.append(b'Timing-Allow-Origin', b'*')
+ elif tao == b'match_origin':
+ # contains a match of origin, separated by comma, pass
+ response.headers.set(b'Timing-Allow-Origin', origin)
+ response.headers.append(b'Timing-Allow-Origin', b"fake")
+ elif tao == b'match_wildcard':
+ # contains a wildcard, separated by comma, pass
+ response.headers.set(b'Timing-Allow-Origin', b"fake")
+ response.headers.append(b'Timing-Allow-Origin', b'*')
+ elif tao == b'uppercase':
+ # non-case-sensitive match for origin, fail
+ response.headers.set(b'Timing-Allow-Origin', origin.upper())
+ else:
+ pass
+ response.status = 200
+ if img:
+ response.headers.set(b"Content-Type", b"image/png")
+ with open(request.doc_root + "/resource-timing/resources/blue.png", "rb") as f:
+ response.content = f.read()
+ f.close()
+ else:
+ response.headers.set(b"Content-Type", b"text/plain")
+ response.content = "TEST"
diff --git a/testing/web-platform/tests/resource-timing/resources/blank-with-tao.html b/testing/web-platform/tests/resource-timing/resources/blank-with-tao.html
new file mode 100644
index 0000000000..b8a1947b77
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/blank-with-tao.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
+ <title>Green Test Page</title>
+ </head>
+ <body style="background-color:#00FF00;">
+ <h1>Placeholder</h1>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/resources/blank-with-tao.html.headers b/testing/web-platform/tests/resource-timing/resources/blank-with-tao.html.headers
new file mode 100644
index 0000000000..7296361df3
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/blank-with-tao.html.headers
@@ -0,0 +1 @@
+Timing-Allow-Origin: *
diff --git a/testing/web-platform/tests/resource-timing/resources/blue-with-tao.png b/testing/web-platform/tests/resource-timing/resources/blue-with-tao.png
new file mode 100644
index 0000000000..820f8cace2
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/blue-with-tao.png
Binary files differ
diff --git a/testing/web-platform/tests/resource-timing/resources/blue-with-tao.png.headers b/testing/web-platform/tests/resource-timing/resources/blue-with-tao.png.headers
new file mode 100644
index 0000000000..7296361df3
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/blue-with-tao.png.headers
@@ -0,0 +1 @@
+Timing-Allow-Origin: *
diff --git a/testing/web-platform/tests/resource-timing/resources/blue.png b/testing/web-platform/tests/resource-timing/resources/blue.png
new file mode 100644
index 0000000000..820f8cace2
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/blue.png
Binary files differ
diff --git a/testing/web-platform/tests/resource-timing/resources/buffer-full-utilities.js b/testing/web-platform/tests/resource-timing/resources/buffer-full-utilities.js
new file mode 100644
index 0000000000..6cb1753b2e
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/buffer-full-utilities.js
@@ -0,0 +1,75 @@
+// This script relies on resources/resource-loaders.js. Include it before in order for the below
+// methods to work properly.
+
+// The resources used to trigger new entries.
+const scriptResources = [
+ 'resources/empty.js',
+ 'resources/empty_script.js',
+ 'resources/empty.js?id'
+];
+
+const waitForNextTask = () => {
+ return new Promise(resolve => {
+ step_timeout(resolve, 0);
+ });
+};
+
+const clearBufferAndSetSize = size => {
+ performance.clearResourceTimings();
+ performance.setResourceTimingBufferSize(size);
+}
+
+const forceBufferFullEvent = async () => {
+ clearBufferAndSetSize(1);
+ return new Promise(async resolve => {
+ performance.addEventListener('resourcetimingbufferfull', resolve);
+ // Load 2 resources to ensure onresourcetimingbufferfull is fired.
+ // Load them in order in order to get the entries in that order!
+ await load.script(scriptResources[0]);
+ await load.script(scriptResources[1]);
+ });
+};
+
+const fillUpTheBufferWithSingleResource = async (src = scriptResources[0]) => {
+ clearBufferAndSetSize(1);
+ await load.script(src);
+};
+
+const fillUpTheBufferWithTwoResources = async () => {
+ clearBufferAndSetSize(2);
+ // Load them in order in order to get the entries in that order!
+ await load.script(scriptResources[0]);
+ await load.script(scriptResources[1]);
+};
+
+const addAssertUnreachedBufferFull = t => {
+ performance.addEventListener('resourcetimingbufferfull', t.step_func(() => {
+ assert_unreached("resourcetimingbufferfull should not fire")
+ }));
+};
+
+const checkEntries = numEntries => {
+ const entries = performance.getEntriesByType('resource');
+ assert_equals(entries.length, numEntries,
+ 'Number of entries does not match the expected value.');
+ assert_true(entries[0].name.includes(scriptResources[0]),
+ scriptResources[0] + " is in the entries buffer");
+ if (entries.length > 1) {
+ assert_true(entries[1].name.includes(scriptResources[1]),
+ scriptResources[1] + " is in the entries buffer");
+ }
+ if (entries.length > 2) {
+ assert_true(entries[2].name.includes(scriptResources[2]),
+ scriptResources[2] + " is in the entries buffer");
+ }
+}
+
+const bufferFullFirePromise = new Promise(resolve => {
+ performance.addEventListener('resourcetimingbufferfull', async () => {
+ // Wait for the next task just to ensure that all bufferfull events have fired, and to ensure
+ // that the secondary buffer is copied (as this is an event, there may be microtask checkpoints
+ // right after running an event handler).
+ await waitForNextTask();
+ resolve();
+ });
+});
diff --git a/testing/web-platform/tests/resource-timing/resources/cacheable-and-validated.py b/testing/web-platform/tests/resource-timing/resources/cacheable-and-validated.py
new file mode 100644
index 0000000000..97de866277
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/cacheable-and-validated.py
@@ -0,0 +1,30 @@
+def main(request, response):
+ # Headers need to be set before `response.writer` writes out the response.
+ tao = request.GET.get(b'timing_allow_origin')
+ if tao:
+ response.headers.set(b"Timing-Allow-Origin", tao)
+
+ if b'origin' in request.headers:
+ origin = request.headers[b'origin']
+ response.headers.set(b'Access-Control-Allow-Origin', origin)
+
+ content = request.GET.first(b'content')
+ response.headers.set(b'Cache-Control', b'max-age=60')
+ response.headers.set(b'ETag', b'assdfsdfe')
+
+ # Handle CORS-preflights of non-simple requests.
+ if request.method == 'OPTIONS':
+ response.status = 204
+ requested_method = request.headers.get(b"Access-Control-Request-Method")
+ if requested_method:
+ response.headers.set(b"Access-Control-Allow-Methods", requested_method)
+ requested_headers = request.headers.get(b"Access-Control-Request-Headers")
+ if requested_headers:
+ response.headers.set(b"Access-Control-Allow-Headers", requested_headers)
+ else:
+ if 'Cache-Control' in request.headers:
+ response.status = (304, b'NotModified')
+ else:
+ response.status = (200, b'OK')
+ response.write_status_headers()
+ response.writer.write(content)
diff --git a/testing/web-platform/tests/resource-timing/resources/close.html b/testing/web-platform/tests/resource-timing/resources/close.html
new file mode 100644
index 0000000000..02c275f37b
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/close.html
@@ -0,0 +1 @@
+<script>window.close()</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/resource-timing/resources/connection-reuse-test.js b/testing/web-platform/tests/resource-timing/resources/connection-reuse-test.js
new file mode 100644
index 0000000000..453fbd3405
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/connection-reuse-test.js
@@ -0,0 +1,63 @@
+// This script is loaded in HTTP and HTTPS contexts to validate
+// PerformanceResourceTiming entries' attributes when reusing connections.
+//
+// Note: to ensure that we reuse the connection to fetch multiple resources, we
+// use the same XMLHttpRequest object throughout an individual test. Although
+// it doesn't seem to be specified, each browser tested by WPT will reuse the
+// underlying TCP connection with this approach. Pre-establishing the XHR's
+// connection helps us to test connection reuse also in browsers that may key
+// their connections on the related request's credentials mode.
+
+const connection_reuse_test = (path, follow_on_assertions, test_label) => {
+ const {on_200, on_304} = follow_on_assertions;
+
+ // Make the first request before calling 'attribute_test' so that only the
+ // second request's PerformanceResourceTiming entry will be interrogated. We
+ // don't check the first request's PerformanceResourceTiming entry because
+ // that's not what this test is trying to validate.
+ const client = new XMLHttpRequest();
+ const identifier = Math.random();
+ path = `${path}?tag=${identifier}`;
+ client.open("GET", path, false);
+ client.send();
+
+ attribute_test(
+ async () => {
+ client.open("GET", path + "&same_resource=false", false);
+ client.send();
+
+ // We expect to get a 200 Ok response because we've requested a different
+ // resource than previous requests.
+ if (client.status != 200) {
+ throw new Error(`Got something other than a 200 response. ` +
+ `client.status: ${client.status}`);
+ }
+ }, path, entry => {
+ invariants.assert_connection_reused(entry);
+ on_200(entry);
+ },
+ `PerformanceResrouceTiming entries need to conform to the spec when a ` +
+ `distinct resource is fetched over a persistent connection ` +
+ `(${test_label})`);
+
+ attribute_test(
+ async () => {
+ client.open("GET", path, false);
+ client.setRequestHeader("If-None-Match", identifier);
+ client.send();
+
+ // We expect to get a 304 Not Modified response because we've used a
+ // matching 'identifier' for the If-None-Match header.
+ if (client.status != 304) {
+ throw new Error(`Got something other than a 304 response. ` +
+ `client.status: ${client.status}, response: ` +
+ `'${client.responseText}'`);
+ }
+ }, path, entry => {
+ invariants.assert_connection_reused(entry);
+ on_304(entry);
+ },
+ `PerformanceResrouceTiming entries need to conform to the spec when the ` +
+ `resource is cache-revalidated over a persistent connection ` +
+ `(${test_label})`);
+}
diff --git a/testing/web-platform/tests/resource-timing/resources/content-type.py b/testing/web-platform/tests/resource-timing/resources/content-type.py
new file mode 100644
index 0000000000..23a4f0dbc6
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/content-type.py
@@ -0,0 +1,5 @@
+def main(request, response):
+ if b'content_type' in request.GET:
+ response.headers.set(b'content-type', request.GET.first(b'content_type'))
+ if b'allow_origin' in request.GET:
+ response.headers.set(b'access-control-allow-origin', request.GET.first(b'allow_origin')) \ No newline at end of file
diff --git a/testing/web-platform/tests/resource-timing/resources/cors-ahem.py b/testing/web-platform/tests/resource-timing/resources/cors-ahem.py
new file mode 100644
index 0000000000..dee5b62f00
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/cors-ahem.py
@@ -0,0 +1,19 @@
+import os.path
+
+from wptserve.utils import isomorphic_decode
+
+def main(request, response):
+ etag = b"123abc"
+ if etag == request.headers.get(b"If-None-Match", None):
+ response.headers.set(b"X-HTTP-STATUS", 304)
+ response.status = (304, b"Not Modified")
+ return u""
+
+ response.headers.set(b"Cache-Control", b"public, max-age=86400")
+ response.headers.set(b"Content-Type", b"font/truetype")
+ response.headers.set(b"Access-Control-Allow-Origin", b"*")
+ response.headers.set(b"Timing-Allow-Origin", b"*")
+ response.headers.set(b"ETag", etag)
+ font = u"../../fonts/Ahem.ttf"
+ path = os.path.join(os.path.dirname(isomorphic_decode(__file__)), font)
+ response.content = open(path, u"rb").read()
diff --git a/testing/web-platform/tests/resource-timing/resources/csp-default-none.html b/testing/web-platform/tests/resource-timing/resources/csp-default-none.html
new file mode 100644
index 0000000000..1f59d8c225
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/csp-default-none.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>empty page</title>
diff --git a/testing/web-platform/tests/resource-timing/resources/csp-default-none.html.headers b/testing/web-platform/tests/resource-timing/resources/csp-default-none.html.headers
new file mode 100644
index 0000000000..d66f886dd2
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/csp-default-none.html.headers
@@ -0,0 +1,2 @@
+Content-Security-Policy: default-src 'none'
+
diff --git a/testing/web-platform/tests/resource-timing/resources/delay-css.py b/testing/web-platform/tests/resource-timing/resources/delay-css.py
new file mode 100644
index 0000000000..9a905960ee
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/delay-css.py
@@ -0,0 +1,6 @@
+import time
+
+def main(request, response):
+ time.sleep(float(request.GET.first(b"delay", 1000)) / 1000)
+ response.headers.set('Content-Type', 'text/css')
+ return "/* */"
diff --git a/testing/web-platform/tests/resource-timing/resources/delay-load.html b/testing/web-platform/tests/resource-timing/resources/delay-load.html
new file mode 100644
index 0000000000..4898c1be8e
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/delay-load.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<body>
+<img src="/images/blue.png?pipe=trickle(d1)">
+</body>
diff --git a/testing/web-platform/tests/resource-timing/resources/document-domain-no-impact.html b/testing/web-platform/tests/resource-timing/resources/document-domain-no-impact.html
new file mode 100644
index 0000000000..64cdd8a870
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/document-domain-no-impact.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="resource-loaders.js"></script>
+<script src="entry-invariants.js"></script>
+<body>
+<script>
+const path = location.origin +
+ "/resource-timing/resources/iframe-setdomain.sub.html";
+attribute_test_with_validator(load.iframe, path,
+ el => {
+ try {
+ el.contentWindow.document;
+ throw new Error("iframe document.domain was not set");
+ } catch(error) {
+ if (error.name != "SecurityError") {
+ throw(error);
+ }
+ }
+ },
+ invariants.assert_tao_pass_no_redirect_http,
+ "test that document.domain being set doesn't have an impact on the " +
+ "resource timing entry."
+);
+</script>
+
diff --git a/testing/web-platform/tests/resource-timing/resources/document-navigated.html b/testing/web-platform/tests/resource-timing/resources/document-navigated.html
new file mode 100644
index 0000000000..bedae77082
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/document-navigated.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+</head>
+<body>
+ Navigated document!
+<script>
+ top.postMessage("navigated", "*");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/resources/document-refreshed.html b/testing/web-platform/tests/resource-timing/resources/document-refreshed.html
new file mode 100644
index 0000000000..568f7f27c7
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/document-refreshed.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+</head>
+<body>
+ Refreshed document!
+<script>
+ top.postMessage("refreshed", "*");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/resources/document-that-navigates.html b/testing/web-platform/tests/resource-timing/resources/document-that-navigates.html
new file mode 100644
index 0000000000..a59e9f3ab2
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/document-that-navigates.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+</head>
+<body>
+<script>
+ location.href="document-navigated.html";
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/resources/document-that-refreshes.html b/testing/web-platform/tests/resource-timing/resources/document-that-refreshes.html
new file mode 100644
index 0000000000..659513a642
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/document-that-refreshes.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<meta http-equiv="refresh" content="0;document-refreshed.html">
+</head>
+<body>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/resources/download.asis b/testing/web-platform/tests/resource-timing/resources/download.asis
new file mode 100644
index 0000000000..167386d7a5
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/download.asis
@@ -0,0 +1,6 @@
+HTTP/1.0 200 OK
+Content-Type: application/octet-stream
+
+12312313
+
+
diff --git a/testing/web-platform/tests/resource-timing/resources/embed-navigate-back.html b/testing/web-platform/tests/resource-timing/resources/embed-navigate-back.html
new file mode 100644
index 0000000000..c9c7340f53
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/embed-navigate-back.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing embed navigate - back button navigation</title>
+</head>
+<body onunload="/*disable bfcache*/">
+<script src="/common/get-host-info.sub.js"></script>
+<script src="nested-contexts.js"></script>
+<script>
+ setup_back_navigation("embed-navigate-back.html");
+</script>
+<embed id="target" type="text/html">
+<script>
+ target.src = pre_navigate_url;
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/resources/embed-navigate.html b/testing/web-platform/tests/resource-timing/resources/embed-navigate.html
new file mode 100644
index 0000000000..24c9d3c462
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/embed-navigate.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing embed navigate</title>
+</head>
+<body>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="nested-contexts.js"></script>
+<script>
+ setup_navigate_test();
+</script>
+<embed id="target" type="text/html">
+<script>
+ target.src = pre_navigate_url;
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/resources/embed-refresh.html b/testing/web-platform/tests/resource-timing/resources/embed-refresh.html
new file mode 100644
index 0000000000..bd4b5a14de
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/embed-refresh.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing embed refresh</title>
+</head>
+<body>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="nested-contexts.js"></script>
+<script>
+ setup_refresh_test();
+</script>
+<embed id="target" type="text/html">
+<script>
+ target.src = pre_refresh_url;
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/resources/empty.js b/testing/web-platform/tests/resource-timing/resources/empty.js
new file mode 100644
index 0000000000..3b44754e30
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/empty.js
@@ -0,0 +1 @@
+/* Nothing here */
diff --git a/testing/web-platform/tests/resource-timing/resources/empty.py b/testing/web-platform/tests/resource-timing/resources/empty.py
new file mode 100644
index 0000000000..cae83c146e
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/empty.py
@@ -0,0 +1,3 @@
+def main(request, response):
+ response.headers.set(b"Content-Type", b"text/plain")
+ return u""
diff --git a/testing/web-platform/tests/resource-timing/resources/empty_script.js b/testing/web-platform/tests/resource-timing/resources/empty_script.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/empty_script.js
diff --git a/testing/web-platform/tests/resource-timing/resources/empty_style.css b/testing/web-platform/tests/resource-timing/resources/empty_style.css
new file mode 100644
index 0000000000..eb90b432e8
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/empty_style.css
@@ -0,0 +1 @@
+/*Nothing here*/
diff --git a/testing/web-platform/tests/resource-timing/resources/entry-invariants.js b/testing/web-platform/tests/resource-timing/resources/entry-invariants.js
new file mode 100644
index 0000000000..bbc913b722
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/entry-invariants.js
@@ -0,0 +1,510 @@
+const await_with_timeout = async (delay, message, promise, cleanup = ()=>{}) => {
+ let timeout_id;
+ const timeout = new Promise((_, reject) => {
+ timeout_id = step_timeout(() =>
+ reject(new DOMException(message, "TimeoutError")), delay)
+ });
+ let result = null;
+ try {
+ result = await Promise.race([promise, timeout]);
+ clearTimeout(timeout_id);
+ } finally {
+ cleanup();
+ }
+ return result;
+};
+
+// Asserts that the given attributes are present in 'entry' and hold equal
+// values.
+const assert_all_equal_ = (entry, attributes) => {
+ let first = attributes[0];
+ attributes.slice(1).forEach(other => {
+ assert_equals(entry[first], entry[other],
+ `${first} should be equal to ${other}`);
+ });
+}
+
+// Asserts that the given attributes are present in 'entry' and hold values
+// that are sorted in the same order as given in 'attributes'.
+const assert_ordered_ = (entry, attributes) => {
+ let before = attributes[0];
+ attributes.slice(1).forEach(after => {
+ assert_greater_than_equal(entry[after], entry[before],
+ `${after} should be greater than ${before}`);
+ before = after;
+ });
+}
+
+// Asserts that the given attributes are present in 'entry' and hold a value of
+// 0.
+const assert_zeroed_ = (entry, attributes) => {
+ attributes.forEach(attribute => {
+ assert_equals(entry[attribute], 0, `${attribute} should be 0`);
+ });
+}
+
+// Asserts that the given attributes are present in 'entry' and hold a value of
+// 0 or more.
+const assert_not_negative_ = (entry, attributes) => {
+ attributes.forEach(attribute => {
+ assert_greater_than_equal(entry[attribute], 0,
+ `${attribute} should be greater than or equal to 0`);
+ });
+}
+
+// Asserts that the given attributes are present in 'entry' and hold a value
+// greater than 0.
+const assert_positive_ = (entry, attributes) => {
+ attributes.forEach(attribute => {
+ assert_greater_than(entry[attribute], 0,
+ `${attribute} should be greater than 0`);
+ });
+}
+
+const invariants = {
+ // Asserts that attributes of the given PerformanceResourceTiming entry match
+ // what the spec dictates for any resource fetched over HTTP without
+ // redirects but passing the Timing-Allow-Origin checks.
+ assert_tao_pass_no_redirect_http: entry => {
+ assert_ordered_(entry, [
+ "fetchStart",
+ "domainLookupStart",
+ "domainLookupEnd",
+ "connectStart",
+ "connectEnd",
+ "requestStart",
+ "responseStart",
+ "responseEnd",
+ ]);
+
+ assert_zeroed_(entry, [
+ "workerStart",
+ "secureConnectionStart",
+ "redirectStart",
+ "redirectEnd",
+ ]);
+
+ assert_not_negative_(entry, [
+ "duration",
+ ]);
+
+ assert_positive_(entry, [
+ "fetchStart",
+ "transferSize",
+ ]);
+ },
+
+ // Like assert_tao_pass_no_redirect_http but for empty response bodies.
+ assert_tao_pass_no_redirect_http_empty: entry => {
+ assert_ordered_(entry, [
+ "fetchStart",
+ "domainLookupStart",
+ "domainLookupEnd",
+ "connectStart",
+ "connectEnd",
+ "requestStart",
+ "responseStart",
+ "responseEnd",
+ ]);
+
+ assert_zeroed_(entry, [
+ "workerStart",
+ "secureConnectionStart",
+ "redirectStart",
+ "redirectEnd",
+ ]);
+
+ assert_not_negative_(entry, [
+ "duration",
+ ]);
+
+ assert_positive_(entry, [
+ "fetchStart",
+ "transferSize",
+ ]);
+ },
+
+ // Like assert_tao_pass_no_redirect_http but for resources fetched over HTTPS
+ assert_tao_pass_no_redirect_https: entry => {
+ assert_ordered_(entry, [
+ "fetchStart",
+ "domainLookupStart",
+ "domainLookupEnd",
+ "secureConnectionStart",
+ "connectStart",
+ "connectEnd",
+ "requestStart",
+ "responseStart",
+ "responseEnd",
+ ]);
+
+ assert_zeroed_(entry, [
+ "workerStart",
+ "redirectStart",
+ "redirectEnd",
+ ]);
+
+ assert_not_negative_(entry, [
+ "duration",
+ ]);
+
+ assert_positive_(entry, [
+ "fetchStart",
+ "transferSize",
+ ]);
+ },
+
+ // Like assert_tao_pass_no_redirect_https but for resources that did encounter
+ // at least one HTTP redirect.
+ assert_tao_pass_with_redirect_https: entry => {
+ assert_ordered_(entry, [
+ "fetchStart",
+ "redirectStart",
+ "redirectEnd",
+ "domainLookupStart",
+ "domainLookupEnd",
+ "secureConnectionStart",
+ "connectStart",
+ "connectEnd",
+ "requestStart",
+ "responseStart",
+ "responseEnd",
+ ]);
+
+ assert_zeroed_(entry, [
+ "workerStart",
+ ]);
+
+ assert_not_negative_(entry, [
+ "duration",
+ ]);
+
+ assert_positive_(entry, [
+ "fetchStart",
+ "transferSize",
+ ]);
+ },
+
+ // Like assert_tao_pass_no_redirect_http but, since the resource's bytes
+ // won't be retransmitted, the encoded and decoded sizes must be zero.
+ assert_tao_pass_304_not_modified_http: entry => {
+ assert_ordered_(entry, [
+ "fetchStart",
+ "domainLookupStart",
+ "domainLookupEnd",
+ "connectStart",
+ "connectEnd",
+ "requestStart",
+ "responseStart",
+ "responseEnd",
+ ]);
+
+ assert_zeroed_(entry, [
+ "workerStart",
+ "secureConnectionStart",
+ "redirectStart",
+ "redirectEnd",
+ ]);
+
+ assert_not_negative_(entry, [
+ "duration",
+ ]);
+
+ assert_positive_(entry, [
+ "fetchStart",
+ "transferSize",
+ ]);
+ },
+
+ // Like assert_tao_pass_304_not_modified_http but for resources fetched over
+ // HTTPS.
+ assert_tao_pass_304_not_modified_https: entry => {
+ assert_ordered_(entry, [
+ "fetchStart",
+ "domainLookupStart",
+ "domainLookupEnd",
+ "secureConnectionStart",
+ "connectStart",
+ "connectEnd",
+ "requestStart",
+ "responseStart",
+ "responseEnd",
+ ]);
+
+ assert_zeroed_(entry, [
+ "workerStart",
+ "redirectStart",
+ "redirectEnd",
+ ]);
+
+ assert_not_negative_(entry, [
+ "duration",
+ ]);
+
+ assert_positive_(entry, [
+ "fetchStart",
+ "transferSize",
+ ]);
+ },
+
+ // Asserts that attributes of the given PerformanceResourceTiming entry match
+ // what the spec dictates for any resource subsequently fetched over a
+ // persistent connection. When this happens, we expect that certain
+ // attributes describing transport layer behaviour will be equal.
+ assert_connection_reused: entry => {
+ assert_all_equal_(entry, [
+ "fetchStart",
+ "connectStart",
+ "connectEnd",
+ "domainLookupStart",
+ "domainLookupEnd",
+ ]);
+ },
+
+ // Asserts that attributes of the given PerformanceResourceTiming entry match
+ // what the spec dictates for any resource fetched over HTTP through an HTTP
+ // redirect.
+ assert_same_origin_redirected_resource: entry => {
+ assert_positive_(entry, [
+ "redirectStart",
+ ]);
+
+ assert_equals(entry.redirectStart, entry.startTime,
+ "redirectStart should be equal to startTime");
+
+ assert_ordered_(entry, [
+ "redirectStart",
+ "redirectEnd",
+ "fetchStart",
+ "domainLookupStart",
+ "domainLookupEnd",
+ "connectStart",
+ ]);
+ },
+
+ // Asserts that attributes of the given PerformanceResourceTiming entry match
+ // what the spec dictates for any resource fetched over HTTPS through a
+ // cross-origin redirect.
+ // (e.g. GET http://remote.com/foo => 302 Location: https://remote.com/foo)
+ assert_cross_origin_redirected_resource: entry => {
+ assert_zeroed_(entry, [
+ "redirectStart",
+ "redirectEnd",
+ "domainLookupStart",
+ "domainLookupEnd",
+ "connectStart",
+ "connectEnd",
+ "secureConnectionStart",
+ "requestStart",
+ "responseStart",
+ ]);
+
+ assert_positive_(entry, [
+ "fetchStart",
+ "responseEnd",
+ ]);
+
+ assert_ordered_(entry, [
+ "fetchStart",
+ "responseEnd",
+ ]);
+ },
+
+ // Asserts that attributes of the given PerformanceResourceTiming entry match
+ // what the spec dictates when
+ // 1. An HTTP request is made for a same-origin resource.
+ // 2. The response to 1 is an HTTP redirect (like a 302).
+ // 3. The location from 2 is a cross-origin HTTPS URL.
+ // 4. The response to fetching the URL from 3 does not set a matching TAO header.
+ assert_http_to_cross_origin_redirected_resource: entry => {
+ assert_zeroed_(entry, [
+ "redirectStart",
+ "redirectEnd",
+ "domainLookupStart",
+ "domainLookupEnd",
+ "connectStart",
+ "connectEnd",
+ "secureConnectionStart",
+ "requestStart",
+ "responseStart",
+ ]);
+
+ assert_positive_(entry, [
+ "fetchStart",
+ "responseEnd",
+ ]);
+
+ assert_ordered_(entry, [
+ "fetchStart",
+ "responseEnd",
+ ]);
+ },
+
+ // Asserts that attributes of the given PerformanceResourceTiming entry match
+ // what the spec dictates when
+ // 1. An HTTPS request is made for a same-origin resource.
+ // 2. The response to 1 is an HTTP redirect (like a 302).
+ // 3. The location from 2 is a cross-origin HTTPS URL.
+ // 4. The response to fetching the URL from 3 sets a matching TAO header.
+ assert_tao_enabled_cross_origin_redirected_resource: entry => {
+ assert_positive_(entry, [
+ "redirectStart",
+ ]);
+ assert_ordered_(entry, [
+ "redirectStart",
+ "redirectEnd",
+ "fetchStart",
+ "domainLookupStart",
+ "domainLookupEnd",
+ "connectStart",
+ "secureConnectionStart",
+ "connectEnd",
+ "requestStart",
+ "responseStart",
+ "responseEnd",
+ ]);
+ },
+
+ // Asserts that attributes of the given PerformanceResourceTiming entry match
+ // what the spec dictates when
+ // 1. An HTTP request is made for a same-origin resource
+ // 2. The response to 1 is an HTTP redirect (like a 302).
+ // 3. The location from 2 is a cross-origin HTTPS URL.
+ // 4. The response to fetching the URL from 3 sets a matching TAO header.
+ assert_http_to_tao_enabled_cross_origin_https_redirected_resource: entry => {
+ assert_zeroed_(entry, [
+ // Note that, according to the spec, the secureConnectionStart attribute
+ // should describe the connection for the first resource request when
+ // there are redirects. Since the initial request is over HTTP,
+ // secureConnectionStart must be 0.
+ "secureConnectionStart",
+ ]);
+ assert_positive_(entry, [
+ "redirectStart",
+ ]);
+ assert_ordered_(entry, [
+ "redirectStart",
+ "redirectEnd",
+ "fetchStart",
+ "domainLookupStart",
+ "domainLookupEnd",
+ "connectStart",
+ "connectEnd",
+ "requestStart",
+ "responseStart",
+ "responseEnd",
+ ]);
+ },
+
+ assert_same_origin_redirected_from_cross_origin_resource: entry => {
+ assert_zeroed_(entry, [
+ "workerStart",
+ "redirectStart",
+ "redirectEnd",
+ "domainLookupStart",
+ "domainLookupEnd",
+ "connectStart",
+ "connectEnd",
+ "secureConnectionStart",
+ "requestStart",
+ "responseStart",
+ "transferSize",
+ ]);
+
+ assert_ordered_(entry, [
+ "fetchStart",
+ "responseEnd",
+ ]);
+
+ assert_equals(entry.fetchStart, entry.startTime,
+ "fetchStart must equal startTime");
+ },
+
+ assert_tao_failure_resource: entry => {
+ assert_equals(entry.entryType, "resource", "entryType must always be 'resource'");
+
+ assert_positive_(entry, [
+ "startTime",
+ ]);
+
+ assert_not_negative_(entry, [
+ "duration",
+ ]);
+
+ assert_zeroed_(entry, [
+ "redirectStart",
+ "redirectEnd",
+ "domainLookupStart",
+ "domainLookupEnd",
+ "connectStart",
+ "connectEnd",
+ "secureConnectionStart",
+ "requestStart",
+ "responseStart",
+ "transferSize",
+ ]);
+ }
+
+};
+
+const attribute_test_internal = (loader, path, validator, run_test, test_label) => {
+ promise_test(
+ async () => {
+ let loaded_entry = new Promise((resolve, reject) => {
+ new PerformanceObserver((entry_list, self) => {
+ try {
+ const name_matches = entry_list.getEntries().forEach(entry => {
+ if (entry.name.includes(path)) {
+ resolve(entry);
+ }
+ });
+ } catch(e) {
+ // By surfacing exceptions through the Promise interface, tests can
+ // fail fast with a useful message instead of timing out.
+ reject(e);
+ }
+ }).observe({"type": "resource"});
+ });
+
+ await loader(path, validator);
+ const entry = await await_with_timeout(2000,
+ "Timeout was reached before entry fired",
+ loaded_entry);
+ assert_not_equals(entry, null, 'No entry was received');
+ run_test(entry);
+ }, test_label);
+};
+
+// Given a resource-loader, a path (a relative path or absolute URL), and a
+// PerformanceResourceTiming test, applies the loader to the resource path
+// and tests the resulting PerformanceResourceTiming entry.
+const attribute_test = (loader, path, run_test, test_label) => {
+ attribute_test_internal(loader, path, () => {}, run_test, test_label);
+};
+
+// Similar to attribute test, but on top of that, validates the added element,
+// to ensure the test does what it intends to do.
+const attribute_test_with_validator = (loader, path, validator, run_test, test_label) => {
+ attribute_test_internal(loader, path, validator, run_test, test_label);
+};
+
+const network_error_entry_test = (originalURL, args, label) => {
+ const url = new URL(originalURL, location.href);
+ const search = new URLSearchParams(url.search.substr(1));
+ const timeBefore = performance.now();
+ loader = () => new Promise(resolve => fetch(url, args).catch(resolve));
+
+ attribute_test(
+ loader, url,
+ () => {
+ const timeAfter = performance.now();
+ const names = performance.getEntriesByType('resource').filter(e => e.initiatorType === 'fetch').map(e => e.name);
+ const entries = performance.getEntriesByName(url.toString());
+ assert_equals(entries.length, 1, 'resource timing entry for network error');
+ const entry = entries[0]
+ assert_equals(entry.startTime, entry.fetchStart, 'startTime and fetchStart should be equal');
+ assert_greater_than_equal(entry.startTime, timeBefore, 'startTime and fetchStart should be greater than the time before fetching');
+ assert_greater_than_equal(timeAfter, entry.responseEnd, 'endTime should be less than the time right after returning from the fetch');
+ invariants.assert_tao_failure_resource(entry);
+ }, `A ResourceTiming entry should be created for network error of type ${label}`);
+}
diff --git a/testing/web-platform/tests/resource-timing/resources/eventsource.py b/testing/web-platform/tests/resource-timing/resources/eventsource.py
new file mode 100644
index 0000000000..e3a2355730
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/eventsource.py
@@ -0,0 +1,3 @@
+def main(request, response):
+ response.headers.set(b"Content-Type", b"text/event-stream")
+ return u""
diff --git a/testing/web-platform/tests/resource-timing/resources/fake_responses.html b/testing/web-platform/tests/resource-timing/resources/fake_responses.html
new file mode 100644
index 0000000000..52cad6c415
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/fake_responses.html
@@ -0,0 +1,19 @@
+<body>
+<script>
+function request(type) {
+ var client = new XMLHttpRequest,
+ identifier = type == "tag" ? Math.random() : new Date().toGMTString(),
+ url = "fake_responses.py?" + type + "=" + identifier
+ client.open("GET", url, false)
+ client.send(null)
+ client.open("GET", url, false)
+ client.setRequestHeader(type == "tag" ? "If-None-Match" : "If-Modified-Since", identifier)
+ client.send(null)
+}
+
+if(window.parent.setup_iframe) {
+ window.parent.setup_iframe();
+ request("tag");
+}
+</script>
+</body>
diff --git a/testing/web-platform/tests/resource-timing/resources/fake_responses.py b/testing/web-platform/tests/resource-timing/resources/fake_responses.py
new file mode 100644
index 0000000000..cc6aba6efa
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/fake_responses.py
@@ -0,0 +1,42 @@
+# /xhr/resources/conditional.py -- to fake a 304 response
+
+def main(request, response):
+ if request.method == "OPTIONS":
+ # Assume this is a CORS preflight
+ response.headers.set(b"Access-Control-Allow-Headers", "*")
+ response.headers.set(b"Access-Control-Allow-Origin", "*")
+ response.status = (204, "No Content")
+ return b""
+ tag = request.GET.first(b"tag", None)
+ redirect = request.GET.first(b"redirect", None)
+ match = request.headers.get(b"If-None-Match", None)
+ date = request.GET.first(b"date", b"")
+ modified = request.headers.get(b"If-Modified-Since", None)
+ url = request.GET.first(b"url", None)
+ response.headers.set(b"Access-Control-Allow-Origin", b"*")
+ response.headers.set(b"Timing-Allow-Origin", b"*")
+ if tag:
+ response.headers.set(b"ETag", b'"%s"' % tag)
+ elif date:
+ response.headers.set(b"Last-Modified", date)
+ if redirect:
+ response.headers.set(b"Location", redirect)
+ response.status = (302, b"Moved")
+ return b""
+
+ if url:
+ filename = url.decode('utf-8').split("?")[0]
+ filepath = "./resource-timing/resources/{}".format(filename)
+ response.headers.set(b"Content-Type", b"text/javascript")
+ with open(filepath, 'rb') as f:
+ filedata = f.read()
+
+ return filedata
+
+ if ((match is not None and match == tag) or
+ (modified is not None and modified == date)):
+ response.status = (304, b"SUPERCOOL")
+ return b""
+ else:
+ response.headers.set(b"Content-Type", b"text/plain")
+ return b"MAYBE NOT"
diff --git a/testing/web-platform/tests/resource-timing/resources/fake_responses_https.sub.html b/testing/web-platform/tests/resource-timing/resources/fake_responses_https.sub.html
new file mode 100644
index 0000000000..21f1f02a67
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/fake_responses_https.sub.html
@@ -0,0 +1,18 @@
+<body>
+<script>
+function request() {
+ var client = new XMLHttpRequest,
+ baseurl = "https://{{hosts[alt][]}}:{{ports[https][0]}}{{location[pathname]}}",
+ url = new URL("fake_responses.py", baseurl).href;
+ client.open("GET", url, false)
+ client.send(null)
+ client.open("GET", url, false)
+ client.send(null)
+}
+
+if(window.parent.setup_iframe) {
+ window.parent.setup_iframe();
+ request();
+}
+</script>
+</body>
diff --git a/testing/web-platform/tests/resource-timing/resources/fake_responses_https_redirect.sub.html b/testing/web-platform/tests/resource-timing/resources/fake_responses_https_redirect.sub.html
new file mode 100644
index 0000000000..2ee92b2a55
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/fake_responses_https_redirect.sub.html
@@ -0,0 +1,20 @@
+<body>
+<script>
+function request() {
+ var client = new XMLHttpRequest,
+ baseurl = "http://{{hosts[alt][]}}:{{ports[http][0]}}{{location[pathname]}}",
+ subresource = "fake_responses.py",
+ redirecturl = new URL(subresource, "https://{{hosts[][www]}}:{{ports[https][0]}}{{location[pathname]}}").href,
+ url = new URL(subresource + "?redirect=" + redirecturl, baseurl).href;
+ client.open("GET", url, false)
+ client.send(null)
+ client.open("GET", url, false)
+ client.send(null)
+}
+
+if(window.parent.setup_iframe) {
+ window.parent.setup_iframe();
+ request();
+}
+</script>
+</body>
diff --git a/testing/web-platform/tests/resource-timing/resources/frame-timing.js b/testing/web-platform/tests/resource-timing/resources/frame-timing.js
new file mode 100644
index 0000000000..019bd424b5
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/frame-timing.js
@@ -0,0 +1,63 @@
+function test_frame_timing_before_load_event(type) {
+ promise_test(async t => {
+ const {document, performance} = type === 'frame' ? window.parent : window;
+ const delay = 500;
+ const frame = document.createElement(type);
+ t.add_cleanup(() => frame.remove());
+ await new Promise(resolve => {
+ frame.addEventListener('load', resolve);
+ frame.src = `/resource-timing/resources/iframe-with-delay.sub.html?delay=${delay}`;
+ (type === 'frame' ? document.querySelector('frameset') : document.body).appendChild(frame);
+ });
+
+ const entries = performance.getEntriesByName(frame.src);
+ const navigationEntry = frame.contentWindow.performance.getEntriesByType('navigation')[0];
+ assert_equals(entries.length, 1);
+ assert_equals(entries[0].initiatorType, type);
+ assert_greater_than(performance.now(), entries[0].responseEnd + delay);
+ const domContentLoadedEventAbsoluteTime =
+ navigationEntry.domContentLoadedEventStart +
+ frame.contentWindow.performance.timeOrigin;
+ const frameResponseEndAbsoluteTime = entries[0].responseEnd + performance.timeOrigin;
+ assert_greater_than(domContentLoadedEventAbsoluteTime, frameResponseEndAbsoluteTime);
+ }, `A ${type} should report its RT entry when the response is done and before it is completely loaded`);
+}
+
+
+function test_frame_timing_change_src(type,
+ origin1 = document.origin,
+ origin2 = document.origin,
+ tao = false, label = '') {
+ const uid = token();
+ promise_test(async t => {
+ const {document, performance} = type === 'frame' ? window.parent : window;
+ const frame = document.createElement(type);
+ t.add_cleanup(() => frame.remove());
+ function createURL(origin) {
+ const url = new URL(`${origin}/resource-timing/resources/green.html`, location.href);
+ url.searchParams.set("uid", uid);
+ if (tao)
+ url.searchParams.set("pipe", "header(Timing-Allow-Origin, *)");
+ return url.toString();
+ }
+
+ await new Promise(resolve => {
+ const done = () => {
+ resolve();
+ frame.removeEventListener('load', done);
+ }
+ frame.addEventListener('load', done);
+ frame.src = createURL(origin1);
+ const root = type === 'frame' ? document.querySelector('frameset') : document.body;
+ root.appendChild(frame);
+ });
+
+ await new Promise(resolve => {
+ frame.addEventListener('load', resolve);
+ frame.src = createURL(origin2);
+ });
+
+ const entries = performance.getEntries().filter(e => e.name.includes(uid));
+ assert_equals(entries.length, 2);
+ }, label || `A ${type} should report separate RT entries if its src changed from the outside`);
+}
diff --git a/testing/web-platform/tests/resource-timing/resources/frameset-timing-frame.html b/testing/web-platform/tests/resource-timing/resources/frameset-timing-frame.html
new file mode 100644
index 0000000000..e260f57526
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/frameset-timing-frame.html
@@ -0,0 +1,8 @@
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./frame-timing.js"></script>
+
+<script>
+ test_frame_timing_before_load_event('frame');
+ test_frame_timing_change_src('frame');
+</script>
diff --git a/testing/web-platform/tests/resource-timing/resources/green-frame.html b/testing/web-platform/tests/resource-timing/resources/green-frame.html
new file mode 100644
index 0000000000..9613240ae3
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/green-frame.html
@@ -0,0 +1,7 @@
+<html>
+ <head>
+ <frameset>
+ <frame src="green.html?id=frame">
+ </frameset>
+ </head>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/resources/green.html b/testing/web-platform/tests/resource-timing/resources/green.html
new file mode 100644
index 0000000000..b8a1947b77
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/green.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
+ <title>Green Test Page</title>
+ </head>
+ <body style="background-color:#00FF00;">
+ <h1>Placeholder</h1>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/resources/green.html.headers b/testing/web-platform/tests/resource-timing/resources/green.html.headers
new file mode 100644
index 0000000000..cb762eff80
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/green.html.headers
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: *
diff --git a/testing/web-platform/tests/resource-timing/resources/gzip_xml.py b/testing/web-platform/tests/resource-timing/resources/gzip_xml.py
new file mode 100644
index 0000000000..7debc9ce3f
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/gzip_xml.py
@@ -0,0 +1,23 @@
+import gzip as gzip_module
+import os
+
+from io import BytesIO
+
+from wptserve.utils import isomorphic_decode
+
+def main(request, response):
+ dir_path = os.path.dirname(os.path.realpath(isomorphic_decode(__file__)))
+ file_path = os.path.join(dir_path, u'resource_timing_test0.xml')
+ f = open(file_path, u'rb')
+ output = f.read()
+
+ out = BytesIO()
+ with gzip_module.GzipFile(fileobj=out, mode="w") as f:
+ f.write(output)
+ output = out.getvalue()
+
+ headers = [(b"Content-type", b"text/plain"),
+ (b"Content-Encoding", b"gzip"),
+ (b"Content-Length", len(output))]
+
+ return headers, output
diff --git a/testing/web-platform/tests/resource-timing/resources/header-delay.h2.py b/testing/web-platform/tests/resource-timing/resources/header-delay.h2.py
new file mode 100644
index 0000000000..27b6fd5bfb
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/header-delay.h2.py
@@ -0,0 +1,25 @@
+from time import sleep
+
+def handle_headers(frame, request, response):
+ delay = int(request.GET.first(b"delay")) / 1000
+
+ if b"with100" in request.GET:
+ sleep(delay)
+ response.writer.write_raw_header_frame(headers=[(b":status", b"103")], end_headers=True)
+
+ if b"with103" in request.GET:
+ sleep(delay)
+ response.writer.write_raw_header_frame(headers=[(b":status", b"103")], end_headers=True)
+
+ sleep(delay)
+ response.status = 200
+
+ if b"tao" in request.GET:
+ response.headers[b"timing-allow-origin"] = "*"
+
+ response.headers[b"content-type"] = "text/plain"
+ response.headers[b"access-control-allow-origin"] = "*"
+ response.write_status_headers()
+
+def main(request, response):
+ response.writer.write_data(item="Hello World", last=True)
diff --git a/testing/web-platform/tests/resource-timing/resources/header-delay.py b/testing/web-platform/tests/resource-timing/resources/header-delay.py
new file mode 100644
index 0000000000..631f7855f8
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/header-delay.py
@@ -0,0 +1,29 @@
+from time import sleep
+
+def main(request, response):
+ delay = int(request.GET.first(b"delay")) / 1000
+
+ # TODO: make this exported from ResponseWriter
+ handler = response.writer._handler
+ if b"with100" in request.GET:
+ sleep(delay)
+ handler.send_response(100)
+ handler.end_headers()
+
+ if b"with103" in request.GET:
+ sleep(delay)
+ handler.send_response(103)
+ handler.send_header("Link", "<resources/empty.js>;rel=preload;as=script")
+ handler.end_headers()
+
+ sleep(delay)
+
+ handler.send_response(200)
+
+ if b"tao" in request.GET:
+ handler.send_header("timing-allow-origin", "*")
+
+ handler.send_header("content-type", "text/plain")
+ handler.send_header("access-control-allow-origin", "*")
+ handler.end_headers()
+ handler.wfile.write(bytes("Hello World", "utf8"))
diff --git a/testing/web-platform/tests/resource-timing/resources/iframe-TAO-crossorigin-port.sub.html b/testing/web-platform/tests/resource-timing/resources/iframe-TAO-crossorigin-port.sub.html
new file mode 100644
index 0000000000..97d77fcc58
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/iframe-TAO-crossorigin-port.sub.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script>
+ const url = '{{location[scheme]}}://{{host}}:{{ports[http][1]}}/resource-timing/resources/TAOResponse.py?tao=origin_port_{{ports[http][1]}}';
+ const observe = (list, observer) => {
+ const entry = list.getEntries()[0];
+ const sum = entry.redirectStart +
+ entry.redirectEnd +
+ entry.domainLookupStart +
+ entry.domainLookupEnd +
+ entry.connectStart +
+ entry.connectEnd +
+ entry.secureConnectionStart +
+ entry.requestStart +
+ entry.responseStart +
+ entry.transferSize +
+ entry.encodedBodySize +
+ entry.decodedBodySize;
+
+ const result = sum == 0 ? 'PASS' : 'FAIL';
+ window.top.postMessage(result, '*');
+ }
+ let observer = new PerformanceObserver(observe);
+ observer.observe({ entryTypes: ["resource"] });
+ fetch(url).then(r => r.text());
+</script>
+</body>
+</html>
+
+
diff --git a/testing/web-platform/tests/resource-timing/resources/iframe-load-from-mem-cache-transfer-size.html b/testing/web-platform/tests/resource-timing/resources/iframe-load-from-mem-cache-transfer-size.html
new file mode 100644
index 0000000000..6f37a33e8c
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/iframe-load-from-mem-cache-transfer-size.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <meta charset="utf-8" />
+ <title>transfer size of resource timing when loaded from memory cache.</title>
+</head>
+
+<body>
+ <script>
+ // This function is called from the test in the parent document.
+ function getScript(url) {
+ const script = document.createElement("script");
+ const loaded = new Promise(resolve => {
+ script.onload = script.onerror = resolve;
+ });
+ script.src = url;
+ document.body.appendChild(script);
+ return loaded;
+ }
+ </script>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/resource-timing/resources/iframe-navigate-back.html b/testing/web-platform/tests/resource-timing/resources/iframe-navigate-back.html
new file mode 100644
index 0000000000..f944b633e2
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/iframe-navigate-back.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing iframe navigate - back button navigation</title>
+</head>
+<body onunload="/*disable bfcache*/">
+<script src="/common/get-host-info.sub.js"></script>
+<script src="nested-contexts.js"></script>
+<script>
+ setup_back_navigation("iframe-navigate-back.html");
+</script>
+<iframe id="target"></iframe>
+<script>
+ target.src = pre_navigate_url;
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/resources/iframe-navigate.html b/testing/web-platform/tests/resource-timing/resources/iframe-navigate.html
new file mode 100644
index 0000000000..0286884021
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/iframe-navigate.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing iframe navigate</title>
+</head>
+<body>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="nested-contexts.js"></script>
+<script>
+ setup_navigate_test();
+</script>
+<iframe id="target"></iframe>
+<script>
+ target.src = pre_navigate_url;
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/resources/iframe-refresh.html b/testing/web-platform/tests/resource-timing/resources/iframe-refresh.html
new file mode 100644
index 0000000000..862b96da9d
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/iframe-refresh.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing iframe refresh</title>
+</head>
+<body>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="nested-contexts.js"></script>
+<script>
+ setup_refresh_test();
+</script>
+<iframe id="target"></iframe>
+<script>
+ target.src = pre_refresh_url;
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/resources/iframe-reload-TAO.html b/testing/web-platform/tests/resource-timing/resources/iframe-reload-TAO.html
new file mode 100644
index 0000000000..461f43bf8e
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/iframe-reload-TAO.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8" />
+<script src="/resources/testharness.js"></script>
+<script src="resource-loaders.js"></script>
+<script src="entry-invariants.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<body>
+<script>
+ const {HTTP_NOTSAMESITE_ORIGIN} = get_host_info();
+ const font_url = `${HTTP_NOTSAMESITE_ORIGIN}/resource-timing/resources/cors-ahem.py`;
+ if (location.hash === '#check') {
+ attribute_test(
+ load.font, font_url,
+ invariants.assert_tao_pass_no_redirect_http,
+ "Test that TAO headers are reused on reloads.");
+ } else {
+ window.onload = () => {
+ location.hash = 'check';
+ location.reload();
+ };
+ }
+</script>
+</body>
diff --git a/testing/web-platform/tests/resource-timing/resources/iframe-setdomain.sub.html b/testing/web-platform/tests/resource-timing/resources/iframe-setdomain.sub.html
new file mode 100644
index 0000000000..4a2f609aa4
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/iframe-setdomain.sub.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>domain: {{domains[]}}</title>
+</head>
+<body>
+ <script>
+ // The purpose of this IFrame is to change the 'document.domain'
+ document.domain = "{{domains[]}}";
+ </script>
+ The resource-timings-level1.sub.html test loads this document into an IFrame to vet that setting
+ 'document.domain' does not effect the timing allowed.
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/resources/iframe-with-delay.sub.html b/testing/web-platform/tests/resource-timing/resources/iframe-with-delay.sub.html
new file mode 100644
index 0000000000..fe50aa7e47
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/iframe-with-delay.sub.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<img src="/xhr/resources/delay.py?ms={{GET[delay]}}" />
diff --git a/testing/web-platform/tests/resource-timing/resources/iframe_TAO_match_origin.html b/testing/web-platform/tests/resource-timing/resources/iframe_TAO_match_origin.html
new file mode 100644
index 0000000000..cf68aade79
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/iframe_TAO_match_origin.html
@@ -0,0 +1,21 @@
+<body>
+<script>
+function dirname(path) {
+ return path.replace(/\/[^\/]*$/, '/');
+}
+
+function request() {
+ var dirName = dirname(location.href);
+ var client = new XMLHttpRequest,
+ // create a cross-origin request
+ url = dirName.replace('://', '://www.') + 'TAOResponse.py?tao=match_origin';
+ client.open("GET", url, false);
+ client.send(null);
+}
+
+if(window.parent.setup_iframe) {
+ window.parent.setup_iframe();
+ request();
+}
+</script>
+</body>
diff --git a/testing/web-platform/tests/resource-timing/resources/import.sub.css b/testing/web-platform/tests/resource-timing/resources/import.sub.css
new file mode 100644
index 0000000000..618c568d2a
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/import.sub.css
@@ -0,0 +1 @@
+@import "delay-css.py?delay={{GET[delay]}}" \ No newline at end of file
diff --git a/testing/web-platform/tests/resource-timing/resources/importer.css b/testing/web-platform/tests/resource-timing/resources/importer.css
new file mode 100644
index 0000000000..771204cdd1
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/importer.css
@@ -0,0 +1 @@
+@import 'empty_style.css?stylesheet-imported' \ No newline at end of file
diff --git a/testing/web-platform/tests/resource-timing/resources/importer.js b/testing/web-platform/tests/resource-timing/resources/importer.js
new file mode 100644
index 0000000000..e73d45da29
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/importer.js
@@ -0,0 +1,2 @@
+import './fake_responses.py?url=empty_script.js?script-head-import-defer';
+import('./fake_responses.py?url=empty_script.js?script-head-import-defer-dynamic').then(module => {}); \ No newline at end of file
diff --git a/testing/web-platform/tests/resource-timing/resources/importer_async.js b/testing/web-platform/tests/resource-timing/resources/importer_async.js
new file mode 100644
index 0000000000..4b1cd4ddae
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/importer_async.js
@@ -0,0 +1,2 @@
+import './fake_responses.py?url=empty_script.js?script-head-import-async';
+import('./fake_responses.py?url=empty_script.js?script-head-import-async-dynamic').then(module => {}); \ No newline at end of file
diff --git a/testing/web-platform/tests/resource-timing/resources/importer_dynamic.css b/testing/web-platform/tests/resource-timing/resources/importer_dynamic.css
new file mode 100644
index 0000000000..f0ba069f0c
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/importer_dynamic.css
@@ -0,0 +1 @@
+@import 'empty_style.css?stylesheet-imported-dynamic' \ No newline at end of file
diff --git a/testing/web-platform/tests/resource-timing/resources/importer_print.css b/testing/web-platform/tests/resource-timing/resources/importer_print.css
new file mode 100644
index 0000000000..aac191635e
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/importer_print.css
@@ -0,0 +1 @@
+@import 'empty_style.css?stylesheet-imported-print' \ No newline at end of file
diff --git a/testing/web-platform/tests/resource-timing/resources/inject_resource_test.html b/testing/web-platform/tests/resource-timing/resources/inject_resource_test.html
new file mode 100644
index 0000000000..44d09675d3
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/inject_resource_test.html
@@ -0,0 +1,7 @@
+<body>
+<script>
+if(window.parent.setup_iframe) {
+ window.parent.setup_iframe();
+}
+</script>
+</body>
diff --git a/testing/web-platform/tests/resource-timing/resources/invalid.jpg b/testing/web-platform/tests/resource-timing/resources/invalid.jpg
new file mode 100644
index 0000000000..81c545efeb
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/invalid.jpg
@@ -0,0 +1 @@
+1234
diff --git a/testing/web-platform/tests/resource-timing/resources/manifest.json b/testing/web-platform/tests/resource-timing/resources/manifest.json
new file mode 100644
index 0000000000..e107c044d5
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/manifest.json
@@ -0,0 +1,4 @@
+{
+ "name": "Dummy manifest",
+ "start_url": "/start.html"
+}
diff --git a/testing/web-platform/tests/resource-timing/resources/multi_redirect.py b/testing/web-platform/tests/resource-timing/resources/multi_redirect.py
new file mode 100644
index 0000000000..a5fc5db4ac
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/multi_redirect.py
@@ -0,0 +1,59 @@
+import urllib.parse
+from wptserve.utils import isomorphic_encode
+
+def main(request, response):
+ """Handler that causes multiple redirections. Redirect chain is as follows:
+ 1. Initial URL containing multi-redirect.py
+ 2. Redirect to cross-origin URL
+ 3. Redirect to same-origin URL
+ 4. Final URL containing the final same-origin resource.
+ Mandatory parameters:
+ page_origin - The page origin, used for redirection and to set TAO. This is a mandatory parameter.
+ cross_origin - The cross origin used to make this a cross-origin redirect. This is a mandatory parameter.
+ final_resource - Path of the final resource, without origin. This is a mandatory parameter.
+ Optional parameters:
+ tao_steps - Number of redirects for which the TAO header will be present (a number 0 - 3 makes the most sense). Default value is 0.
+ tao_value - The value of the TAO header, when present. Default value is "*".
+ Note that |step| is a parameter used internally for the multi-redirect. It's the step we're at in the redirect chain.
+ """
+ step = 1
+ if b"step" in request.GET:
+ try:
+ step = int(request.GET.first(b"step"))
+ except ValueError:
+ pass
+
+ page_origin = request.GET.first(b"page_origin")
+ cross_origin = request.GET.first(b"cross_origin")
+ final_resource = request.GET.first(b"final_resource")
+
+ tao_value = b"*"
+ if b"tao_value" in request.GET:
+ tao_value = request.GET.first(b"tao_value")
+ tao_steps = 0
+ if b"tao_steps" in request.GET:
+ tao_steps = int(request.GET.first(b"tao_steps"))
+
+ next_tao_steps = tao_steps - 1
+ redirect_url_path = b"/resource-timing/resources/multi_redirect.py?"
+ redirect_url_path += b"page_origin=" + page_origin
+ redirect_url_path += b"&cross_origin=" + cross_origin
+ redirect_url_path += b"&final_resource=" + urllib.parse.quote(final_resource).encode('ascii')
+ redirect_url_path += b"&tao_value=" + tao_value
+ redirect_url_path += b"&tao_steps=" + isomorphic_encode(str(next_tao_steps))
+ redirect_url_path += b"&step="
+ if tao_steps > 0:
+ response.headers.set(b"timing-allow-origin", tao_value)
+
+ if step == 1:
+ # On the first request, redirect to a cross origin URL
+ redirect_url = cross_origin + redirect_url_path + b"2"
+ elif step == 2:
+ # On the second request, redirect to a same origin URL
+ redirect_url = page_origin + redirect_url_path + b"3"
+ else:
+ # On the third request, redirect to a static response
+ redirect_url = page_origin + final_resource
+
+ response.status = 302
+ response.headers.set(b"Location", redirect_url)
diff --git a/testing/web-platform/tests/resource-timing/resources/navigate_back.html b/testing/web-platform/tests/resource-timing/resources/navigate_back.html
new file mode 100644
index 0000000000..345eee1fcc
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/navigate_back.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<script>
+window.onload = function() {history.back();}
+</script>
diff --git a/testing/web-platform/tests/resource-timing/resources/nested-contexts.js b/testing/web-platform/tests/resource-timing/resources/nested-contexts.js
new file mode 100644
index 0000000000..31337ae5da
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/nested-contexts.js
@@ -0,0 +1,87 @@
+let destination = location;
+
+if (location.search == "?cross-site") {
+ const https = destination.protocol.startsWith("https");
+ destination = get_host_info()[https ? 'HTTPS_NOTSAMESITE_ORIGIN' : 'HTTP_NOTSAMESITE_ORIGIN'];
+} else if (location.search == "?crossorigin") {
+ destination = get_host_info().REMOTE_ORIGIN;
+}
+
+const pre_navigate_url =
+ new URL("/resource-timing/resources/document-that-navigates.html",
+ destination).href;
+const post_navigate_url =
+ new URL("/resource-timing/resources/document-navigated.html",
+ destination).href;
+const pre_refresh_url =
+ new URL("/resource-timing/resources/document-that-refreshes.html",
+ destination).href;
+const post_refresh_url =
+ new URL("/resource-timing/resources/document-refreshed.html",
+ destination).href;
+
+const setup_navigate_or_refresh = (type, pre, post) => {
+ const verify_document_navigate_not_observable = () => {
+ if (performance.getEntriesByName(post).length) {
+ opener.postMessage(`FAIL - ${type} document should not be observable`,
+ `*`);
+
+ }
+
+ opener.postMessage("PASS", "*");
+ }
+ window.addEventListener("message", e => {
+ if (e.data == type) {
+ verify_document_navigate_not_observable();
+ }
+ });
+}
+
+const setup_navigate_test = () => {
+ setup_navigate_or_refresh("navigated", pre_navigate_url, post_navigate_url);
+}
+
+const setup_refresh_test = () => {
+ setup_navigate_or_refresh("refreshed", pre_refresh_url, post_refresh_url);
+}
+
+const setup_back_navigation = pushed_url => {
+ const verify_document_navigate_not_observable = navigated_back => {
+ if (performance.getEntriesByName(post_navigate_url).length) {
+ opener.postMessage("FAIL - navigated document exposed", "*");
+ }
+ if (navigated_back) {
+ opener.postMessage("PASS", "*");
+ }
+ }
+ window.addEventListener("message", e => {
+ if (e.data == "navigated") {
+ verify_document_navigate_not_observable(sessionStorage.navigated);
+ if (sessionStorage.navigated) {
+ delete sessionStorage.navigated;
+ } else {
+ sessionStorage.navigated = true;
+ setTimeout(() => {
+ history.pushState({}, "", pushed_url);
+ location.href="navigate_back.html";
+ }, 0);
+ }
+ }
+ });
+}
+
+const open_test_window = (url, message) => {
+ promise_test(() => {
+ return new Promise((resolve, reject) => {
+ const openee = window.open(url);
+ addEventListener("message", e => {
+ openee.close();
+ if (e.data == "PASS") {
+ resolve();
+ } else {
+ reject(e.data);
+ }
+ });
+ });
+ }, message);
+}
diff --git a/testing/web-platform/tests/resource-timing/resources/nested.css b/testing/web-platform/tests/resource-timing/resources/nested.css
new file mode 100644
index 0000000000..90d61b04ac
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/nested.css
@@ -0,0 +1,10 @@
+@import "resource_timing_test0.css?id=n1";
+
+@font-face {
+ font-family: remoteFont;
+ src: url('/fonts/Ahem.ttf?id=n1');
+}
+ol {
+ font-family: remoteFont;
+ list-style-image: url('blue.png?id=n1');
+}
diff --git a/testing/web-platform/tests/resource-timing/resources/no-entries-for-cross-origin-css-fetched-memory-cache-iframe.sub.html b/testing/web-platform/tests/resource-timing/resources/no-entries-for-cross-origin-css-fetched-memory-cache-iframe.sub.html
new file mode 100644
index 0000000000..f47913468b
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/no-entries-for-cross-origin-css-fetched-memory-cache-iframe.sub.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<link rel="stylesheet" href="http://{{hosts[][www1]}}:{{ports[http][1]}}/resource-timing/resources/nested.css">
+</head>
+<body>
+<ol>Some content</ol>
+</body>
diff --git a/testing/web-platform/tests/resource-timing/resources/notify_parent.html b/testing/web-platform/tests/resource-timing/resources/notify_parent.html
new file mode 100644
index 0000000000..c104f3c8f0
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/notify_parent.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<script>
+window.parent.subFrameLoaded();
+</script>
diff --git a/testing/web-platform/tests/resource-timing/resources/object-navigate-back.html b/testing/web-platform/tests/resource-timing/resources/object-navigate-back.html
new file mode 100644
index 0000000000..a746947818
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/object-navigate-back.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing object navigate - back button navigation</title>
+</head>
+<body onunload="/*disable bfcache*/">
+<script src="/common/get-host-info.sub.js"></script>
+<script src="nested-contexts.js"></script>
+<script>
+ setup_back_navigation("object-navigate-back.html");
+</script>
+<object id="target" type="text/html"></object>
+<script>
+ target.data = pre_navigate_url;
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/resources/object-navigate.html b/testing/web-platform/tests/resource-timing/resources/object-navigate.html
new file mode 100644
index 0000000000..6b4bb3128e
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/object-navigate.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing object navigate</title>
+</head>
+<body>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="nested-contexts.js"></script>
+<script>
+ setup_navigate_test();
+</script>
+<object id="target" type="text/html"></object>
+<script>
+ target.data = pre_navigate_url;
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/resources/object-refresh.html b/testing/web-platform/tests/resource-timing/resources/object-refresh.html
new file mode 100644
index 0000000000..5c5f60fb06
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/object-refresh.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing object refresh</title>
+</head>
+<body>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="nested-contexts.js"></script>
+<script>
+ setup_refresh_test();
+</script>
+<object id="target" type="text/html"></object>
+<script>
+ target.data = pre_refresh_url;
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/resources/observe-entry.js b/testing/web-platform/tests/resource-timing/resources/observe-entry.js
new file mode 100644
index 0000000000..260b5929a5
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/observe-entry.js
@@ -0,0 +1,25 @@
+// Given a resource name, returns a promise that will resolve to the
+// corresponding PerformanceResourceTiming entry. The promise will reject,
+// however, if the PerformanceResourceTiming entry isn't observed within ~2
+// seconds (scaled according to WPT timeout scaling).
+const observe_entry = entry_name => {
+ const entry = new Promise(resolve => {
+ new PerformanceObserver((entry_list, observer) => {
+ for (const entry of entry_list.getEntries()) {
+ if (entry.name.endsWith(entry_name)) {
+ resolve(entry);
+ observer.disconnect();
+ return;
+ }
+ }
+ }).observe({"type": "resource", "buffered": true});
+ });
+ const timeout = new Promise((resolve, reject) => {
+ step_timeout(() => {
+ reject(new Error("observe_entry: timeout"));
+ }, 2000);
+ });
+ // If the entry isn't observed within 2 seconds, assume it will never show
+ // up.
+ return Promise.race([entry, timeout]);
+};
diff --git a/testing/web-platform/tests/resource-timing/resources/preflight.py b/testing/web-platform/tests/resource-timing/resources/preflight.py
new file mode 100644
index 0000000000..f0f6017b47
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/preflight.py
@@ -0,0 +1,9 @@
+def main(request, response):
+ response.headers.set(b"Access-Control-Allow-Origin", b"*")
+ response.headers.set(b"Access-Control-Max-Age", b"0")
+ response.headers.set(b"Timing-Allow-Origin", b"*")
+ # If this script is accessed with the header X-Require-Preflight then the
+ # browser will send a preflight request. Otherwise it won't.
+ if request.method == u'OPTIONS':
+ response.headers.set(b"Access-Control-Allow-Headers",
+ b"X-Require-Preflight")
diff --git a/testing/web-platform/tests/resource-timing/resources/redirect-cors.py b/testing/web-platform/tests/resource-timing/resources/redirect-cors.py
new file mode 100644
index 0000000000..655b862fcd
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/redirect-cors.py
@@ -0,0 +1,22 @@
+def main(request, response):
+ if b"allow_origin" in request.GET:
+ response.headers.set(b"Access-Control-Allow-Origin",
+ request.GET.first(b"allow_origin"))
+
+ if b"timing_allow_origin" in request.GET:
+ response.headers.set(b"Timing-Allow-Origin",
+ request.GET.first(b"timing_allow_origin"))
+
+ # Handle CORS-preflights of non-simple requests.
+ if request.method == 'OPTIONS':
+ response.status = 204
+ requested_method = request.headers.get(b"Access-Control-Request-Method")
+ if requested_method:
+ response.headers.set(b"Access-Control-Allow-Methods", requested_method)
+ requested_headers = request.headers.get(b"Access-Control-Request-Headers")
+ if requested_headers:
+ response.headers.set(b"Access-Control-Allow-Headers", requested_headers)
+ else:
+ location = request.GET.first(b"location")
+ response.status = 302
+ response.headers.set(b"Location", location)
diff --git a/testing/web-platform/tests/resource-timing/resources/redirect-without-location.py b/testing/web-platform/tests/resource-timing/resources/redirect-without-location.py
new file mode 100644
index 0000000000..eef1df858b
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/redirect-without-location.py
@@ -0,0 +1,2 @@
+def main(request, response):
+ response.status = 302
diff --git a/testing/web-platform/tests/resource-timing/resources/resource-loaders.js b/testing/web-platform/tests/resource-timing/resources/resource-loaders.js
new file mode 100644
index 0000000000..37fea16b17
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/resource-loaders.js
@@ -0,0 +1,174 @@
+const load = {
+
+ cache_bust: path => {
+ let url = new URL(path, location.origin);
+ url.href += (url.href.includes("?")) ? '&' : '?';
+ // The `Number` type in Javascript, when interpreted as an integer, can only
+ // safely represent [-2^53 + 1, 2^53 - 1] without the loss of precision [1].
+ // We do not generate a global value and increment from it, as the increment
+ // might not have enough precision to be reflected.
+ //
+ // [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number
+ url.href += "unique=" + Math.random().toString().substring(2);
+ return url.href;
+ },
+
+ image_with_attrs: async (path, attribute_map) => {
+ return new Promise(resolve => {
+ const img = new Image();
+ for (const key in attribute_map)
+ img[key] = attribute_map[key];
+ img.onload = img.onerror = resolve;
+ img.src = load.cache_bust(path);
+ });
+ },
+
+ // Returns a promise that settles once the given path has been fetched as an
+ // image resource.
+ image: path => {
+ return load.image_with_attrs(path, undefined);
+ },
+
+ // Returns a promise that settles once the given path has been fetched as an
+ // image resource.
+ image_cors: path => load.image_with_attrs(path, {crossOrigin: "anonymous"}),
+
+ // Returns a promise that settles once the given path has been fetched as a
+ // font resource.
+ font: path => {
+ const div = document.createElement('div');
+ div.innerHTML = `
+ <style>
+ @font-face {
+ font-family: ahem;
+ src: url('${load.cache_bust(path)}');
+ }
+ </style>
+ <div style="font-family: ahem;">This fetches ahem font.</div>
+ `;
+ document.body.appendChild(div);
+ return document.fonts.ready.then(() => {
+ document.body.removeChild(div);
+ });
+ },
+
+ stylesheet_with_attrs: async (path, attribute_map) => {
+ const link = document.createElement("link");
+ if (attribute_map instanceof Object) {
+ for (const [key, value] of Object.entries(attribute_map)) {
+ link[key] = value;
+ }
+ }
+ link.rel = "stylesheet";
+ link.type = "text/css";
+ link.href = load.cache_bust(path);
+
+ const loaded = new Promise(resolve => {
+ link.onload = link.onerror = resolve;
+ });
+
+ document.head.appendChild(link);
+ await loaded;
+ document.head.removeChild(link);
+ },
+
+ // Returns a promise that settles once the given path has been fetched as a
+ // stylesheet resource.
+ stylesheet: async path => {
+ return load.stylesheet_with_attrs(path, undefined);
+ },
+
+ iframe_with_attrs: async (path, attribute_map, validator, skip_wait_for_navigation) => {
+ const frame = document.createElement("iframe");
+ if (attribute_map instanceof Object) {
+ for (const [key, value] of Object.entries(attribute_map)) {
+ frame[key] = value;
+ }
+ }
+ const loaded = new Promise(resolve => {
+ frame.onload = frame.onerror = resolve;
+ });
+ frame.src = load.cache_bust(path);
+ document.body.appendChild(frame);
+ if ( !skip_wait_for_navigation ) {
+ await loaded;
+ }
+ if (validator instanceof Function) {
+ validator(frame);
+ }
+ // since we skipped the wait for load animation, we cannot
+ // remove the iframe here since the request could get cancelled
+ if ( !skip_wait_for_navigation ) {
+ document.body.removeChild(frame);
+ }
+ },
+
+ // Returns a promise that settles once the given path has been fetched as an
+ // iframe.
+ iframe: async (path, validator) => {
+ return load.iframe_with_attrs(path, undefined, validator);
+ },
+
+ script_with_attrs: async (path, attribute_map) => {
+ const script = document.createElement("script");
+ if (attribute_map instanceof Object) {
+ for (const [key, value] of Object.entries(attribute_map)) {
+ script[key] = value;
+ }
+ }
+ const loaded = new Promise(resolve => {
+ script.onload = script.onerror = resolve;
+ });
+ script.src = load.cache_bust(path);
+ document.body.appendChild(script);
+ await loaded;
+ document.body.removeChild(script);
+ },
+
+ // Returns a promise that settles once the given path has been fetched as a
+ // script.
+ script: async path => {
+ return load.script_with_attrs(path, undefined);
+ },
+
+ // Returns a promise that settles once the given path has been fetched as an
+ // object.
+ object: async (path, type) => {
+ const object = document.createElement("object");
+ const object_load_settled = new Promise(resolve => {
+ object.onload = object.onerror = resolve;
+ });
+ object.data = load.cache_bust(path);
+ if (type) {
+ object.type = type;
+ }
+ document.body.appendChild(object);
+ await await_with_timeout(2000,
+ "Timeout was reached before load or error events fired",
+ object_load_settled,
+ () => { document.body.removeChild(object) }
+ );
+ },
+
+ // Returns a promise that settles once the given path has been fetched
+ // through a synchronous XMLHttpRequest.
+ xhr_sync: async (path, headers) => {
+ const xhr = new XMLHttpRequest;
+ xhr.open("GET", path, /* async = */ false);
+ if (headers instanceof Object) {
+ for (const [key, value] of Object.entries(headers)) {
+ xhr.setRequestHeader(key, value);
+ }
+ }
+ xhr.send();
+ },
+
+ xhr_async: path => {
+ const xhr = new XMLHttpRequest();
+ xhr.open("GET", path)
+ xhr.send();
+ return new Promise(resolve => {
+ xhr.onload = xhr.onerror = resolve;
+ });
+ }
+};
diff --git a/testing/web-platform/tests/resource-timing/resources/resource-timing-content-length.py b/testing/web-platform/tests/resource-timing/resources/resource-timing-content-length.py
new file mode 100644
index 0000000000..687689e272
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/resource-timing-content-length.py
@@ -0,0 +1,20 @@
+def main(request, response):
+ content = request.GET.first(b"content")
+ length = request.GET.first(b"length").decode("ascii")
+ response.add_required_headers = False
+
+ output = b"HTTP/1.1 200 OK\r\n"
+ output += b"Content-Type: text/plain;charset=UTF-8\r\n"
+ output += b"Connection: close\r\n"
+ if length == b"auto" :
+ output += b"Content-Length:"
+ output += "{0}".format(len(content)).encode("ascii")
+ output += b"\r\n"
+ elif length != b"none" :
+ output += b"Content-Length:"
+ output += "{0}".format(length).encode("ascii")
+ output += b"\r\n"
+ output += b"\r\n"
+ output += content
+ response.writer.write(output)
+ response.close_connection = True
diff --git a/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.css b/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.css
new file mode 100644
index 0000000000..8bc8326ba6
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.css
@@ -0,0 +1,4 @@
+div#resource_link_css
+{
+ color:hotpink;
+} \ No newline at end of file
diff --git a/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.html b/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.html
new file mode 100644
index 0000000000..167c65c5c4
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>
+ Child Frame
+ </title>
+ </head>
+ <body style="background-color: #C0C0C0">
+
+ <h1>
+ Child Document
+ </h1>
+
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.js b/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.js
new file mode 100644
index 0000000000..cf1c1df392
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.js
@@ -0,0 +1,3 @@
+// This is a test script for purposes of testing the
+// script initiator type in the Resource Timing feature
+var testDummyValue = 0;
diff --git a/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.js.headers b/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.js.headers
new file mode 100644
index 0000000000..308bee94d0
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.js.headers
@@ -0,0 +1,2 @@
+Content-Type: text/javascript
+Cache-Control: max-age=36000
diff --git a/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.png b/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.png
new file mode 100644
index 0000000000..be211bc377
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.png
Binary files differ
diff --git a/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.xml b/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.xml
new file mode 100644
index 0000000000..91cd676be6
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<TESTDATA>
+ <ITEM>
+ <DATA>Test XML Data</DATA>
+ </ITEM>
+</TESTDATA>
diff --git a/testing/web-platform/tests/resource-timing/resources/self_navigation.html b/testing/web-platform/tests/resource-timing/resources/self_navigation.html
new file mode 100644
index 0000000000..beb12f5da5
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/self_navigation.html
@@ -0,0 +1 @@
+<meta http-equiv="refresh" content="0;url=notify_parent.html?redirected">
diff --git a/testing/web-platform/tests/resource-timing/resources/shared-worker.js b/testing/web-platform/tests/resource-timing/resources/shared-worker.js
new file mode 100644
index 0000000000..f3ef3feb96
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/shared-worker.js
@@ -0,0 +1,3 @@
+self.onconnect = e => {
+ e.ports[0].postMessage(performance.timeOrigin);
+} \ No newline at end of file
diff --git a/testing/web-platform/tests/resource-timing/resources/sizes-helper.js b/testing/web-platform/tests/resource-timing/resources/sizes-helper.js
new file mode 100644
index 0000000000..86336686b3
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/sizes-helper.js
@@ -0,0 +1,16 @@
+// Header size is a fixed constant.
+// https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-transfersize
+const headerSize = 300;
+
+const cacheBustUrl = url => {
+ return url + '&unique=' + Math.random().toString().substring(2);
+}
+
+const checkSizeFields = (entry, bodySize, transferSize) => {
+ assert_equals(entry.decodedBodySize, bodySize,
+ 'decodedBodySize');
+ assert_equals(entry.encodedBodySize, bodySize,
+ 'encodedBodySize');
+ assert_equals(entry.transferSize, transferSize,
+ 'transferSize');
+}
diff --git a/testing/web-platform/tests/resource-timing/resources/status-code.py b/testing/web-platform/tests/resource-timing/resources/status-code.py
new file mode 100644
index 0000000000..1c6a021633
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/status-code.py
@@ -0,0 +1,8 @@
+def main(request, response):
+ status = request.GET.first(b'status')
+ response.status = (status, b"");
+ if b'tao_value' in request.GET:
+ response.headers.set(b'timing-allow-origin', request.GET.first(b'tao_value'))
+ if b'allow_origin' in request.GET:
+ response.headers.set(b'access-control-allow-origin', request.GET.first(b'allow_origin'))
+
diff --git a/testing/web-platform/tests/resource-timing/resources/sw-install.html b/testing/web-platform/tests/resource-timing/resources/sw-install.html
new file mode 100644
index 0000000000..3d1407e831
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/sw-install.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<script>
+const service_worker_unregister = async scope => {
+ const absoluteScope = new URL(scope, window.location).href;
+ const registration = await navigator.serviceWorker.getRegistration(scope);
+ if (registration && registration.scope === absoluteScope) {
+ return registration.unregister();
+ }
+};
+
+const service_worker_reregister = async (url, scope) => {
+ if (!scope || scope.length == 0) {
+ return Promise.reject(new Error('tests must define a scope'));
+ }
+
+ await service_worker_unregister(scope);
+ return navigator.serviceWorker.register(url, {'scope': scope});
+};
+
+const wait_for_state_activated = worker => {
+ if (worker.state === 'activated')
+ return Promise.resolve();
+
+ if (worker.state === 'redundant') {
+ return Promise.reject(new Error('worker is redundant'));
+ }
+
+ return new Promise(function(resolve) {
+ worker.addEventListener('statechange', () => {
+ if (worker.state === 'activated') {
+ resolve();
+ }
+ });
+ });
+};
+
+(async () => {
+ const script = '/resource-timing/resources/sw.js';
+ const scope = '/resource-timing/resources/';
+ const registration = await service_worker_reregister(script, scope);
+ await wait_for_state_activated(registration.installing);
+
+ const opener = window.opener;
+ if (!opener) {
+ return;
+ }
+
+ opener.postMessage("installed", "*");
+ window.addEventListener("message", async e => {
+ if (e.data === "unregister") {
+ await registration.unregister();
+ opener.postMessage("unregistered", "*");
+ }
+ });
+
+})();
+
+</script>
diff --git a/testing/web-platform/tests/resource-timing/resources/sw.js b/testing/web-platform/tests/resource-timing/resources/sw.js
new file mode 100644
index 0000000000..4e4fe1e1f0
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/sw.js
@@ -0,0 +1,3 @@
+self.addEventListener('fetch', function(event) {
+ event.respondWith(fetch(event.request));
+});
diff --git a/testing/web-platform/tests/resource-timing/resources/tao-response.js b/testing/web-platform/tests/resource-timing/resources/tao-response.js
new file mode 100644
index 0000000000..2194c5d43a
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/tao-response.js
@@ -0,0 +1,13 @@
+const tao_response = (tao_value, base_url) => {
+ const payload = {
+ 'headers': {
+ 'Timing-Allow-Origin': tao_value
+ }
+ };
+ return custom_cors_response(payload, base_url);
+};
+
+const remote_tao_response = tao_value => {
+ const {REMOTE_ORIGIN} = get_host_info();
+ return tao_response(tao_value, REMOTE_ORIGIN);
+};
diff --git a/testing/web-platform/tests/resource-timing/resources/webperftestharnessextension.js b/testing/web-platform/tests/resource-timing/resources/webperftestharnessextension.js
new file mode 100644
index 0000000000..dc02c075b3
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/webperftestharnessextension.js
@@ -0,0 +1,188 @@
+//
+// Helper functions for Resource Timing tests
+//
+
+var mark_names = [
+ '',
+ '1',
+ 'abc',
+];
+
+var measures = [
+ [''],
+ ['2', 1],
+ ['aaa', 'navigationStart', ''],
+];
+
+function test_method_exists(method, method_name, properties)
+{
+ var msg;
+ if (typeof method === 'function')
+ msg = 'performance.' + method.name + ' is supported!';
+ else
+ msg = 'performance.' + method_name + ' is supported!';
+ wp_test(function() { assert_equals(typeof method, 'function', msg); }, msg, properties);
+}
+
+function test_noless_than(value, greater_than, msg, properties)
+{
+ wp_test(function () { assert_true(value >= greater_than, msg); }, msg, properties);
+}
+
+function test_fail(msg, properties)
+{
+ wp_test(function() { assert_unreached(); }, msg, properties);
+}
+
+function test_resource_entries(entries, expected_entries)
+{
+ test(function() {
+ // This is slightly convoluted so that we can sort the output.
+ var actual_entries = {};
+ var origin = window.location.protocol + "//" + window.location.host;
+
+ for (var i = 0; i < entries.length; ++i) {
+ var entry = entries[i];
+ var found = false;
+ for (var expected_entry in expected_entries) {
+ if (entry.name == origin + expected_entry) {
+ found = true;
+ if (expected_entry in actual_entries) {
+ assert_unreached(expected_entry + ' is not expected to have duplicate entries');
+ }
+ actual_entries[expected_entry] = entry;
+ break;
+ }
+ }
+ if (!found) {
+ assert_unreached(entries[i].name + ' is not expected to be in the Resource Timing buffer');
+ }
+ }
+
+ sorted_urls = [];
+ for (var i in actual_entries) {
+ sorted_urls.push(i);
+ }
+ sorted_urls.sort();
+ for (var i in sorted_urls) {
+ var url = sorted_urls[i];
+ assert_equals(actual_entries[url].initiatorType,
+ expected_entries[url],
+ origin + url + ' is expected to have initiatorType ' + expected_entries[url]);
+ }
+ for (var j in expected_entries) {
+ if (!(j in actual_entries)) {
+ assert_unreached(origin + j + ' is expected to be in the Resource Timing buffer');
+ }
+ }
+ }, "Testing resource entries");
+}
+
+function performance_entrylist_checker(type)
+{
+ var entryType = type;
+
+ function entry_check(entry, expectedNames)
+ {
+ var msg = 'Entry \"' + entry.name + '\" should be one that we have set.';
+ wp_test(function() { assert_in_array(entry.name, expectedNames, msg); }, msg);
+ test_equals(entry.entryType, entryType, 'entryType should be \"' + entryType + '\".');
+ if (type === "measure") {
+ test_true(isFinite(entry.startTime), 'startTime should be a number.');
+ test_true(isFinite(entry.duration), 'duration should be a number.');
+ } else if (type === "mark") {
+ test_greater_than(entry.startTime, 0, 'startTime should greater than 0.');
+ test_equals(entry.duration, 0, 'duration of mark should be 0.');
+ }
+ }
+
+ function entrylist_order_check(entryList)
+ {
+ var inOrder = true;
+ for (var i = 0; i < entryList.length - 1; ++i)
+ {
+ if (entryList[i + 1].startTime < entryList[i].startTime) {
+ inOrder = false;
+ break;
+ }
+ }
+ return inOrder;
+ }
+
+ function entrylist_check(entryList, expectedLength, expectedNames)
+ {
+ test_equals(entryList.length, expectedLength, 'There should be ' + expectedLength + ' entries.');
+ test_true(entrylist_order_check(entryList), 'Entries in entrylist should be in order.');
+ for (var i = 0; i < entryList.length; ++i)
+ {
+ entry_check(entryList[i], expectedNames);
+ }
+ }
+
+ return{"entrylist_check":entrylist_check};
+}
+
+function PerformanceContext(context)
+{
+ this.performanceContext = context;
+}
+
+PerformanceContext.prototype = {
+ initialMeasures: function(item, index, array)
+ {
+ this.performanceContext.measure.apply(this.performanceContext, item);
+ },
+
+ mark: function()
+ {
+ this.performanceContext.mark.apply(this.performanceContext, arguments);
+ },
+
+ measure: function()
+ {
+ this.performanceContext.measure.apply(this.performanceContext, arguments);
+ },
+
+ clearMarks: function()
+ {
+ this.performanceContext.clearMarks.apply(this.performanceContext, arguments);
+
+ },
+
+ clearMeasures: function()
+ {
+ this.performanceContext.clearMeasures.apply(this.performanceContext, arguments);
+
+ },
+
+ getEntries: function()
+ {
+ return this.performanceContext.getEntries.apply(this.performanceContext, arguments);
+ },
+
+ getEntriesByType: function()
+ {
+ return this.performanceContext.getEntriesByType.apply(this.performanceContext, arguments);
+ },
+
+ getEntriesByName: function()
+ {
+ return this.performanceContext.getEntriesByName.apply(this.performanceContext, arguments);
+ },
+
+ setResourceTimingBufferSize: function()
+ {
+ return this.performanceContext.setResourceTimingBufferSize.apply(this.performanceContext, arguments);
+ },
+
+ registerResourceTimingBufferFullCallback: function(func)
+ {
+ this.performanceContext.onresourcetimingbufferfull = func;
+ },
+
+ clearResourceTimings: function()
+ {
+ this.performanceContext.clearResourceTimings.apply(this.performanceContext, arguments);
+ }
+
+};
diff --git a/testing/web-platform/tests/resource-timing/resources/worker_with_images.js b/testing/web-platform/tests/resource-timing/resources/worker_with_images.js
new file mode 100644
index 0000000000..1fa4893201
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/worker_with_images.js
@@ -0,0 +1,22 @@
+let numComplete = 0;
+
+function checkDone() {
+ ++numComplete;
+ if (numComplete == 2) {
+ const numEntries = performance.getEntries().length;
+ postMessage(numEntries);
+ }
+}
+
+function makeRequest(request) {
+ var xhr = new XMLHttpRequest;
+ xhr.open('get', request, true);
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ checkDone();
+ }
+ }
+ xhr.send();
+}
+makeRequest('blue.png');
+makeRequest('resource_timing_test0.png');
diff --git a/testing/web-platform/tests/resource-timing/response-status-code.html b/testing/web-platform/tests/resource-timing/response-status-code.html
new file mode 100644
index 0000000000..3a184c6f01
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/response-status-code.html
@@ -0,0 +1,165 @@
+<!DOCTYPE html>
+<head>
+<meta charset="utf-8" />
+<meta name="timeout" content="long">
+<title>This test validates the response status of resources.</title>
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/entry-invariants.js"></script>
+<script src="resources/resource-loaders.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+</head>
+<body>
+<script>
+const {ORIGIN, REMOTE_ORIGIN} = get_host_info();
+const SAME_ORIGIN = location.origin;
+const status_codes = [
+ 200, 203,
+ 400, 401, 403, 404,
+ 500, 501, 502, 503,
+];
+
+const load_image_object = async path => {
+ return load.object(path, "image/png");
+}
+
+const load_frame_object = async path => {
+ return load.object(path, "text/html");
+}
+
+const load_null_object = async path => {
+ return load.object(path, null);
+}
+
+// Response status for same origin resources is exposed.
+for(const loader of [
+ load.font,
+ load.image,
+ load.script,
+ load.stylesheet,
+ load.xhr_sync,
+ load.xhr_async,
+ load.iframe,
+ load_image_object,
+ load_frame_object,
+ load_null_object
+]) {
+ for(const status of status_codes) {
+ let path = (loader == load.font) ? '/fonts/pass.woff' :
+ '/resource-timing/resources/empty.js';
+ path += `?pipe=status(${status})`;
+ attribute_test(
+ loader, new URL(path, ORIGIN),
+ entry => {
+ assert_equals(entry.responseStatus, status,
+ `response status for ${entry.name} should be ${status}`);
+ }
+ );
+ }
+}
+
+// Response status is exposed for CORS requests for cross-origin resources.
+for(const loader of [
+ load.image_with_attrs,
+ load.script_with_attrs,
+ load.stylesheet_with_attrs
+]) {
+ for(const status of status_codes) {
+ const path = `/resource-timing/resources/empty.js?pipe=status(${status})`
+ + `|header(access-control-allow-origin, ${ORIGIN})`;
+ loader_with_crossOrigin_attr = async url => {
+ return loader(url, {"crossOrigin": "anonymous"});
+ }
+ attribute_test(
+ loader_with_crossOrigin_attr, new URL(path, REMOTE_ORIGIN),
+ entry => {
+ assert_equals(entry.responseStatus, status,
+ `response status for ${entry.name} should be ${status}`);
+ }
+ );
+ }
+}
+
+// Response status is 0 when a no-cors request is made for cross origin
+// fonts, images, scripts, stylesheets.
+// Response status is 0 when request's mode is "navigate" and response's
+// URL's origin is not same origin with request's origin. So response
+// status is not exposed for cross origin iframes.
+for(const loader of [
+ load.font,
+ load.image,
+ load.script,
+ load.stylesheet,
+ load.iframe,
+ load_image_object,
+ load_frame_object,
+ load_null_object
+]) {
+ for(const tao of [false, true]) {
+ for(const status of status_codes) {
+ let path = (loader == load.font) ? '/fonts/pass.woff' :
+ '/resource-timing/resources/empty.js';
+ path += `?pipe=status(${status})`;
+ if (tao) {
+ path += `|header(timing-allow-origin, *)`;
+ }
+ attribute_test(
+ loader, new URL(path, REMOTE_ORIGIN),
+ entry => {
+ assert_equals(entry.responseStatus, 0,
+ `response status for ${entry.name} should be 0`);
+ }
+ );
+ }
+ }
+}
+
+// Response status for iframes is 0 when cross origin redirects are present
+// Same-Origin => Cross-Origin => Same-Origin => Same-Origin redirect chain
+for(const loader of [
+ load.iframe,
+ load_frame_object,
+ load_null_object
+]) {
+ for(const status of status_codes) {
+ const destUrl =
+ `${SAME_ORIGIN}/resource-timing/resources/multi_redirect.py` +
+ `?page_origin=${SAME_ORIGIN}` +
+ `&cross_origin=${REMOTE_ORIGIN}` +
+ `&final_resource=` +
+ `/resource-timing/resources/empty.js?pipe=status(${status})`;
+ attribute_test(
+ loader, new URL(destUrl),
+ entry => {
+ assert_equals(entry.responseStatus, 0,
+ `response status should be 0 for iframes having cross origin`
+ + ` redirects`);
+ }
+ );
+ }
+}
+
+// Response status for iframes is exposed for same origin redirects
+for(const loader of [
+ load.iframe,
+ load_frame_object,
+ load_null_object
+]) {
+ for(const status of status_codes) {
+ const destUrl = `${SAME_ORIGIN}/resource-timing/resources/redirect-cors.py`
+ + `?location=${SAME_ORIGIN}/resource-timing/resources/empty.js`
+ + `?pipe=status(${status})`;
+ attribute_test(
+ loader, new URL(destUrl),
+ entry => {
+ assert_equals(entry.responseStatus, status,
+ `response status should be exposed for iframes having only same`
+ + ` origin redirects`);
+ }
+ );
+ }
+};
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/same-origin-from-cross-origin-redirect.html b/testing/web-platform/tests/resource-timing/same-origin-from-cross-origin-redirect.html
new file mode 100644
index 0000000000..8740b81b13
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/same-origin-from-cross-origin-redirect.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>This test validates resource timing information for a same-origin
+resource fetched through a cross-origin redirect chain.</title>
+<link rel="author" title="Google" href="https://google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/resource-loaders.js"></script>
+<script src="resources/entry-invariants.js"></script>
+</head>
+<body>
+<script>
+const {HTTP_REMOTE_ORIGIN} = get_host_info();
+let destUrl = `${HTTP_REMOTE_ORIGIN}/resource-timing/resources/multi_redirect.py?`;
+destUrl += `page_origin=http://${document.location.host}`;
+destUrl += `&cross_origin=${HTTP_REMOTE_ORIGIN}`;
+destUrl += `&final_resource=/resource-timing/resources/blank-with-tao.html`;
+
+attribute_test(
+ load.iframe, destUrl,
+ invariants.assert_same_origin_redirected_from_cross_origin_resource,
+ "Verify that cross origin resources' timings are exposed when the final " +
+ "resource at the end of an HTTP redirect chain is same-origin.");
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/script-rt-entries.html b/testing/web-platform/tests/resource-timing/script-rt-entries.html
new file mode 100644
index 0000000000..cdd72bd1e2
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/script-rt-entries.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing Entry Sequence of Events for Scripts</title>
+<link rel="help" href="https://w3c.github.io/resource-timing/"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+ async_test(t => {
+ const script = document.createElement("script");
+ const url = new URL('resources/empty.js', location.href).toString();
+ script.addEventListener('load', t.step_func(() => {
+ assert_equals(performance.getEntriesByName(url).length, 1);
+ t.done();
+ }));
+ script.src = url;
+ document.body.appendChild(script);
+ t.add_cleanup(() => script.remove());
+ }, "The RT entry for script should be available when the script 'load' event fires");
+
+ async_test(t => {
+ const script = document.createElement("script");
+ const url = new URL('resources/non-existent.js', location.href).toString();
+ script.addEventListener('error', t.step_func(() => {
+ assert_equals(performance.getEntriesByName(url).length, 1);
+ t.done();
+ }));
+ script.src = url;
+ document.body.appendChild(script);
+ t.add_cleanup(() => script.remove());
+ }, "The RT entry for a non-existent script should be available when the script 'error' event fires");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/secure-iframe-in-insecure-context.html b/testing/web-platform/tests/resource-timing/secure-iframe-in-insecure-context.html
new file mode 100644
index 0000000000..87f4711146
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/secure-iframe-in-insecure-context.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/observe-entry.js"></script>
+
+<body>
+</body>
+<script>
+ const { HTTPS_ORIGIN } = get_host_info();
+
+ promise_test(async t => {
+ const iframe = document.createElement('iframe');
+ iframe.src = `${HTTPS_ORIGIN}/resource-timing/resources/200.https.asis?1`;
+ document.body.appendChild(iframe);
+ t.add_cleanup(() => iframe.remove());
+ const entry = await observe_entry(iframe.src);
+ assert_not_equals(entry.secureConnectionStart, 0);
+ }, 'secureConnectionStart is reported for iframes loaded over https in http context');
+
+ promise_test(async t => {
+ const object = document.createElement('object');
+ object.data = `${HTTPS_ORIGIN}/resource-timing/resources/200.https.asis?2`;
+ document.body.appendChild(object);
+ t.add_cleanup(() => object.remove());
+ const entry = await observe_entry(object.data);
+ assert_not_equals(entry.secureConnectionStart, 0);
+ }, 'secureConnectionStart is reported for object documents loaded over https in http context');
+</script>
+
+</html>
diff --git a/testing/web-platform/tests/resource-timing/shared-worker-rt-entry.html b/testing/web-platform/tests/resource-timing/shared-worker-rt-entry.html
new file mode 100644
index 0000000000..194500a095
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/shared-worker-rt-entry.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing Entry for Shared Workers</title>
+<link rel="help" href="https://w3c.github.io/resource-timing/"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+</head>
+<body>
+<script>
+ promise_test(async () => {
+ const url = new URL(`resources/shared-worker.js?${token()}`, location.href).toString();
+ const worker = new SharedWorker(url, 'name');
+ const {data} = await new Promise(resolve => {
+ worker.port.onmessage = resolve;
+ });
+
+ const timeOrigin = data;
+ const entries = performance.getEntriesByName(url);
+ assert_equals(entries.length, 0, "SharedWorker should not create a ResourceTiming entry");
+ }, "Shared workers should not generate Resource Timing Entries");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/sizes-cache.any.js b/testing/web-platform/tests/resource-timing/sizes-cache.any.js
new file mode 100644
index 0000000000..af70e5a6de
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/sizes-cache.any.js
@@ -0,0 +1,55 @@
+// META: global=window,worker
+// META: script=/resource-timing/resources/sizes-helper.js
+// META: script=/resource-timing/resources/resource-loaders.js
+
+let url = new URL(
+ '/resource-timing/resources/cacheable-and-validated.py' +
+ '?content=loremipsumblablabla',
+ location.href).href;
+const bodySize = 19;
+
+const accumulateEntries = () => {
+ return new Promise(resolve => {
+ const po = new PerformanceObserver(list => {
+ resolve(list);
+ });
+ po.observe({type: "resource", buffered: true});
+ });
+};
+
+const checkResourceSizes = list => {
+ const entries = list.getEntriesByName(url);
+ assert_equals(entries.length, 3, 'Wrong number of entries');
+ let seenCount = 0;
+ for (let entry of entries) {
+ if (seenCount === 0) {
+ // 200 response
+ checkSizeFields(entry, bodySize, bodySize + headerSize);
+ } else if (seenCount === 1) {
+ // from cache
+ checkSizeFields(entry, bodySize, 0);
+ } else if (seenCount === 2) {
+ // 304 response
+ checkSizeFields(entry, bodySize, headerSize);
+ } else {
+ assert_unreached('Too many matching entries');
+ }
+ ++seenCount;
+ }
+};
+
+promise_test(() => {
+ // Use a different URL every time so that the cache behaviour does not
+ // depend on execution order.
+ url = load.cache_bust(url);
+ const eatBody = response => response.arrayBuffer();
+ const mustRevalidate = {headers: {'Cache-Control': 'max-age=0'}};
+ return fetch(url)
+ .then(eatBody)
+ .then(() => fetch(url))
+ .then(eatBody)
+ .then(() => fetch(url, mustRevalidate))
+ .then(eatBody)
+ .then(accumulateEntries)
+ .then(checkResourceSizes);
+}, 'PerformanceResourceTiming sizes caching test');
diff --git a/testing/web-platform/tests/resource-timing/sizes-redirect-img.html b/testing/web-platform/tests/resource-timing/sizes-redirect-img.html
new file mode 100644
index 0000000000..e440029782
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/sizes-redirect-img.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/sizes-helper.js"></script>
+<script src="resources/resource-loaders.js"></script>
+<script src="resources/entry-invariants.js"></script>
+<script>
+// Redirects for fetch() always apply CORS rules, whereas normal resources
+// don't, so this test covers extra code paths beyond those covered by
+// resource-timing-sizes-redirect.html.
+
+const baseUrl = new URL('/resource-timing/resources/TAOResponse.py?tao=wildcard&img=true', location.href).href;
+
+const expectedSize = 1010;
+
+const hostInfo = get_host_info();
+
+const redirectUrl = (redirectSourceOrigin, targetUrl) => {
+ return redirectSourceOrigin +
+ '/resource-timing/resources/redirect-cors.py?allow_origin=*&timing_allow_origin=*' +
+ '&location=' + encodeURIComponent(targetUrl);
+};
+
+const verify_entry = entry => {
+ checkSizeFields(entry, expectedSize, expectedSize + headerSize);
+};
+
+attribute_test(load.image, baseUrl,
+ verify_entry,
+ "PerformanceResourceTiming sizes redirect image - direct URL");
+
+attribute_test(load.image,
+ redirectUrl(hostInfo.HTTP_ORIGIN, baseUrl),
+ verify_entry,
+ "PerformanceResourceTiming sizes redirect image - same origin redirect");
+
+attribute_test(load.image_cors,
+ redirectUrl(hostInfo.HTTP_REMOTE_ORIGIN, baseUrl),
+ verify_entry,
+ "PerformanceResourceTiming sizes redirect image - cross origin redirect");
+
+attribute_test(load.image_cors,
+ redirectUrl(hostInfo.HTTP_REMOTE_ORIGIN,
+ redirectUrl(hostInfo.HTTP_ORIGIN, baseUrl)),
+ verify_entry,
+ "PerformanceResourceTiming sizes redirect image - cross origin to same origin redirect");
+
+attribute_test(load.image_cors,
+ redirectUrl(hostInfo.HTTP_ORIGIN,
+ redirectUrl(hostInfo.HTTP_REMOTE_ORIGIN,
+ redirectUrl(hostInfo.HTTP_ORIGIN,
+ baseUrl))),
+ verify_entry,
+ "PerformanceResourceTiming sizes redirect image - same origin to remote " +
+ "origin to same origin redirect");
+</script>
diff --git a/testing/web-platform/tests/resource-timing/sizes-redirect.any.js b/testing/web-platform/tests/resource-timing/sizes-redirect.any.js
new file mode 100644
index 0000000000..e483a4d409
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/sizes-redirect.any.js
@@ -0,0 +1,62 @@
+// META: global=window,worker
+// META: script=/common/get-host-info.sub.js
+// META: script=/resource-timing/resources/sizes-helper.js
+
+const baseUrl =
+ new URL('/resource-timing/resources/TAOResponse.py?tao=wildcard', location.href).href;
+const expectedSize = 4;
+
+const hostInfo = get_host_info();
+performance.clearResourceTimings();
+
+const accumulateEntry = () => {
+ return new Promise(resolve => {
+ const po = new PerformanceObserver(list => {
+ resolve(list);
+ });
+ po.observe({type: "resource", buffered: true});
+ });
+};
+
+const checkResourceSizes = () => {
+ const entries = performance.getEntriesByType('resource');
+ for (let entry of entries) {
+ checkSizeFields(entry, expectedSize, expectedSize + headerSize);
+ }
+}
+
+const redirectUrl = (redirectSourceOrigin, allowOrigin, targetUrl) => {
+ return redirectSourceOrigin +
+ '/resource-timing/resources/redirect-cors.py?allow_origin=' +
+ encodeURIComponent(allowOrigin) +
+ '&timing_allow_origin=*' +
+ '&location=' + encodeURIComponent(targetUrl);
+}
+
+promise_test(() => {
+ // Use a different URL every time so that the cache behaviour does not
+ // depend on execution order.
+ const directUrl = cacheBustUrl(baseUrl);
+ const sameOriginRedirect = redirectUrl(hostInfo.ORIGIN, '*', directUrl);
+ const crossOriginRedirect = redirectUrl(hostInfo.REMOTE_ORIGIN,
+ hostInfo.ORIGIN, directUrl);
+ const mixedRedirect = redirectUrl(hostInfo.REMOTE_ORIGIN,
+ hostInfo.ORIGIN, sameOriginRedirect);
+ const complexRedirect = redirectUrl(hostInfo.ORIGIN,
+ hostInfo.REMOTE_ORIGIN, mixedRedirect);
+ let eatBody = response => response.arrayBuffer();
+ return fetch(directUrl)
+ .then(eatBody)
+ .then(() => fetch(sameOriginRedirect))
+ .then(eatBody)
+ .then(() => fetch(crossOriginRedirect))
+ .then(eatBody)
+ .then(() => fetch(mixedRedirect))
+ .then(eatBody)
+ .then(() => fetch(complexRedirect))
+ .then(eatBody)
+ .then(accumulateEntry)
+ .then(checkResourceSizes);
+}, 'PerformanceResourceTiming sizes Fetch with redirect test');
+
+done();
diff --git a/testing/web-platform/tests/resource-timing/sleep.py b/testing/web-platform/tests/resource-timing/sleep.py
new file mode 100644
index 0000000000..2e803b1b26
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/sleep.py
@@ -0,0 +1,13 @@
+import time
+import timeit
+
+# sleep can be lower than requested value in some platforms: https://bugs.python.org/issue31539
+# We add padding here to compensate for that.
+sleep_padding = 15.0
+
+def sleep_at_least(sleep_in_ms):
+ sleep_until = timeit.default_timer() + (sleep_in_ms / 1E3)
+ time.sleep((sleep_in_ms + sleep_padding) / 1E3)
+ # Check if the padding was sufficient; if not, sleep again.
+ while timeit.default_timer() < sleep_until:
+ time.sleep(sleep_padding / 1E3)
diff --git a/testing/web-platform/tests/resource-timing/status-codes-create-entry.html b/testing/web-platform/tests/resource-timing/status-codes-create-entry.html
new file mode 100644
index 0000000000..cc0cd8ccb8
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/status-codes-create-entry.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<img src="resources/status-code.py?status=200">
+<img src="resources/status-code.py?status=307">
+<img src="resources/status-code.py?status=404">
+<img src="resources/status-code.py?status=502">
+<script src="resources/status-code.py?status=200&script=1"></script>
+<script src="resources/status-code.py?status=307&script=1"></script>
+<script src="resources/status-code.py?status=404&script=1"></script>
+<script src="resources/status-code.py?status=502&script=1"></script>
+<script>
+async_test(t => {
+ window.addEventListener("load", t.step_func_done(() => {
+ const images = document.getElementsByTagName("img");
+ for (let img of images) {
+ const entries = performance.getEntriesByName(img.src);
+ assert_greater_than(entries.length, 0, img.src);
+ assert_greater_than(entries[0].duration, 0, img.src);
+ }
+ const scripts = document.getElementsByTagName("script");
+ let noSrcScriptFound = false;
+ for (let script of scripts) {
+ if (script.src) {
+ const entries = performance.getEntriesByName(script.src);
+ assert_greater_than(entries.length, 0, script.src);
+ assert_greater_than(entries[0].duration, 0, script.src);
+ } else {
+ // Ignore this script, which has no src value. There should only be one such script.
+ assert_false(noSrcScriptFound);
+ noSrcScriptFound = true;
+ }
+ }
+ }));
+}, "Make sure all status codes are reported");
+</script>
diff --git a/testing/web-platform/tests/resource-timing/supported_resource_type.any.js b/testing/web-platform/tests/resource-timing/supported_resource_type.any.js
new file mode 100644
index 0000000000..31e40096dd
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/supported_resource_type.any.js
@@ -0,0 +1,24 @@
+test(() => {
+ if (typeof PerformanceObserver.supportedEntryTypes === "undefined")
+ assert_unreached("supportedEntryTypes is not supported.");
+ assert_true(PerformanceObserver.supportedEntryTypes.includes("resource"),
+ "There should be an entry 'resource' in PerformanceObserver.supportedEntryTypes");
+}, "supportedEntryTypes contains 'resource'.");
+
+if (typeof PerformanceObserver.supportedEntryTypes !== "undefined") {
+ const entryType = "resource";
+ if (PerformanceObserver.supportedEntryTypes.includes(entryType)) {
+ promise_test(async() => {
+ await new Promise((resolve) => {
+ new PerformanceObserver(function (list, observer) {
+ observer.disconnect();
+ resolve();
+ }).observe({entryTypes: [entryType]});
+
+ // Force the PerformanceEntry.
+ // Use `self` for Workers.
+ fetch(self.location.href + "?" + Math.random());
+ })
+ }, `'${entryType}' entries should be observable.`)
+ }
+}
diff --git a/testing/web-platform/tests/resource-timing/test_resource_timing.html b/testing/web-platform/tests/resource-timing/test_resource_timing.html
new file mode 100644
index 0000000000..f4e851abb2
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/test_resource_timing.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="UTF-8" />
+ <title>window.performance Resource Timing Entries exist</title>
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="https://w3c.github.io/web-performance/specs/ResourceTiming/Overview.html"/>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="test_resource_timing.js"></script>
+ </head>
+ <body>
+ <h1>Description</h1>
+ <p>
+ NOTE: Due to caching behavior in the browser, it is possible that when revisiting this page, some resources
+ may not have to be fetched from the network. As a result, the performance timeline will not contain entries
+ for these resources. This test will fail if any entries are missing to ensure that all resources are fetched
+ from the network and entries for these resources exist in the Performance Timeline. If revisiting this page,
+ please either perform a full reload of the page or clear the cache between visits.
+ </p>
+
+ <div id="log"></div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/test_resource_timing.https.html b/testing/web-platform/tests/resource-timing/test_resource_timing.https.html
new file mode 100644
index 0000000000..f4e851abb2
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/test_resource_timing.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="UTF-8" />
+ <title>window.performance Resource Timing Entries exist</title>
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="https://w3c.github.io/web-performance/specs/ResourceTiming/Overview.html"/>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="test_resource_timing.js"></script>
+ </head>
+ <body>
+ <h1>Description</h1>
+ <p>
+ NOTE: Due to caching behavior in the browser, it is possible that when revisiting this page, some resources
+ may not have to be fetched from the network. As a result, the performance timeline will not contain entries
+ for these resources. This test will fail if any entries are missing to ensure that all resources are fetched
+ from the network and entries for these resources exist in the Performance Timeline. If revisiting this page,
+ please either perform a full reload of the page or clear the cache between visits.
+ </p>
+
+ <div id="log"></div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/test_resource_timing.js b/testing/web-platform/tests/resource-timing/test_resource_timing.js
new file mode 100644
index 0000000000..598a727bf8
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/test_resource_timing.js
@@ -0,0 +1,228 @@
+var TEST_ALLOWED_TIMING_DELTA = 20;
+
+var waitTimer;
+var expectedEntries = {};
+
+var initiatorTypes = ["iframe", "img", "link", "script", "xmlhttprequest"];
+
+var tests = {};
+setup(function() {
+ for (var i in initiatorTypes) {
+ var type = initiatorTypes[i];
+ tests[type] = {
+ "entry": async_test("window.performance.getEntriesByName() and window.performance.getEntriesByNameType() return same data (" + type + ")"),
+ "simple_attrs": async_test("PerformanceEntry has correct name, initiatorType, startTime, and duration (" + type + ")"),
+ "timing_attrs": async_test("PerformanceEntry has correct order of timing attributes (" + type + ")"),
+ "network_attrs": async_test("PerformanceEntry has correct network transfer attributes (" + type + ")"),
+ "protocol": async_test("PerformanceEntry has correct protocol attribute (" + type + ")")
+ };
+ }
+});
+
+function resolve(path) {
+ var a = document.createElement("a");
+ a.href = path;
+ return a.href;
+}
+
+onload = function()
+{
+ // check that the Performance Timeline API exists
+ test(function() {
+ assert_idl_attribute(window.performance, "getEntriesByName",
+ "window.performance.getEntriesByName() is defined");
+ });
+ test(function() {
+ assert_idl_attribute(window.performance, "getEntriesByType",
+ "window.performance.getEntriesByType() is defined");
+ });
+ test(function() {
+ assert_idl_attribute(window.performance, "getEntries",
+ "window.performance.getEntries() is defined");
+ });
+
+ var expected_entry;
+ var url;
+ var type;
+ var startTime;
+ var element;
+ var encodedBodySize;
+ var decodedBodySize;
+ for (var i in initiatorTypes) {
+ startTime = window.performance.now();
+ type = initiatorTypes[i];
+ if (type != "xmlhttprequest") {
+ element = document.createElement(type);
+ } else {
+ element = null;
+ }
+ switch (type) {
+ case "iframe":
+ url = resolve("resources/resource_timing_test0.html");
+ element.src = url;
+ encodedBodySize = 215;
+ decodedBodySize = 215;
+ break;
+ case "img":
+ url = resolve("resources/resource_timing_test0.png");
+ element.src = url;
+ encodedBodySize = 249;
+ decodedBodySize = 249;
+ break;
+ case "link":
+ element.rel = "stylesheet";
+ url = resolve("resources/resource_timing_test0.css");
+ element.href = url;
+ encodedBodySize = 44;
+ decodedBodySize = 44;
+ break;
+ case "script":
+ element.type = "text/javascript";
+ url = resolve("resources/resource_timing_test0.js");
+ element.src = url;
+ encodedBodySize = 133;
+ decodedBodySize = 133;
+ break;
+ case "xmlhttprequest":
+ var xmlhttp = new XMLHttpRequest();
+ url = resolve("resources/gzip_xml.py");
+ xmlhttp.open('GET', url, true);
+ xmlhttp.send();
+ encodedBodySize = 112;
+ decodedBodySize = 125;
+ break;
+ }
+
+ expected_entry = {name:url,
+ startTime: startTime,
+ initiatorType: type,
+ encodedBodySize: encodedBodySize,
+ decodedBodySize: decodedBodySize
+ };
+
+ switch (type) {
+ case "link":
+ poll_for_stylesheet_load(expected_entry);
+ document.body.appendChild(element);
+ break;
+ case "xmlhttprequest":
+ xmlhttp.onload = (function(entry) {
+ return function (event) {
+ resource_load(entry);
+ };
+ })(expected_entry);
+ break;
+ default:
+ element.onload = (function(entry) {
+ return function (event) {
+ resource_load(entry);
+ };
+ })(expected_entry);
+ document.body.appendChild(element);
+ }
+
+ }
+};
+
+function poll_for_stylesheet_load(expected_entry) {
+ var t = tests[expected_entry.initiatorType];
+
+ function inner() {
+ for(var i=0; i<document.styleSheets.length; i++) {
+ var sheet = document.styleSheets[i];
+ if (sheet.href === expected_entry.name) {
+ try {
+ // try/catch avoids throwing if sheet object exists before it is loaded,
+ // which is a bug, but not what we are trying to test here.
+ var hasRules = sheet.cssRules.length > 0;
+ } catch(e) {
+ hasRules = false;
+ }
+ if (hasRules) {
+ t["entry"].step_timeout(function() {
+ resource_load(expected_entry);
+ }, 200);
+ return;
+ }
+ }
+ }
+ t["entry"].step_timeout(inner, 100);
+ }
+ inner();
+}
+
+function resource_load(expected)
+{
+ var t = tests[expected.initiatorType];
+
+ t["entry"].step(function() {
+ var entries_by_name = window.performance.getEntriesByName(expected.name);
+ assert_equals(entries_by_name.length, 1, "should have a single entry for each resource (without type)");
+ var entries_by_name_type = window.performance.getEntriesByName(expected.name, "resource");
+ assert_equals(entries_by_name_type.length, 1, "should have a single entry for each resource (with type)");
+ assert_not_equals(entries_by_name, entries_by_name_type, "values should be copies");
+ for (p in entries_by_name[0]) {
+ var assertMethod = assert_equals
+ if (Array.isArray(entries_by_name[0][p]) && Array.isArray(entries_by_name_type[0][p])) {
+ assertMethod = assert_array_equals
+ }
+ assertMethod(entries_by_name[0][p], entries_by_name_type[0][p], "Property " + p + " should match");
+ }
+ this.done();
+ });
+
+ t["simple_attrs"].step(function() {
+ var actual = window.performance.getEntriesByName(expected.name)[0];
+ var expected_type = expected.initiatorType;
+ assert_equals(actual.name, expected.name);
+ assert_equals(actual.initiatorType, expected_type);
+ assert_equals(actual.entryType, "resource");
+ assert_greater_than_equal(actual.startTime, expected.startTime, "startTime is after the script to initiate the load ran");
+ assert_equals(actual.duration, (actual.responseEnd - actual.startTime));
+ this.done();
+ });
+
+ t["timing_attrs"].step(function test() {
+ const entries = window.performance.getEntriesByName(expected.name);
+ assert_equals(entries.length, 1, 'There should be a single matching entry');
+ const actual = entries[0];
+ if (window.location.protocol == "http:") {
+ assert_equals(actual.secureConnectionStart, 0, 'secureConnectionStart should be 0 in http');
+ } else {
+ assert_greater_than(actual.secureConnectionStart, 0, 'secureConnectionStart should not be 0 in https');
+ }
+
+ assert_equals(actual.redirectStart, 0, 'redirectStart should be 0');
+ assert_equals(actual.redirectEnd, 0, 'redirectEnd should be 0');
+ assert_equals(actual.fetchStart, actual.startTime, 'fetchStart is equal to startTime');
+ assert_greater_than_equal(actual.domainLookupStart, actual.fetchStart, 'domainLookupStart after fetchStart');
+ assert_greater_than_equal(actual.domainLookupEnd, actual.domainLookupStart, 'domainLookupEnd after domainLookupStart');
+ assert_greater_than_equal(actual.connectStart, actual.domainLookupEnd, 'connectStart after domainLookupEnd');
+ assert_greater_than_equal(actual.connectEnd, actual.connectStart, 'connectEnd after connectStart');
+ assert_true(actual.secureConnectionStart == 0 || actual.secureConnectionStart <= actual.requestStart,
+ "secureConnectionStart should be either 0 or smaller than/equals to requestStart")
+ assert_greater_than_equal(actual.requestStart, actual.connectEnd, 'requestStart after connectEnd');
+ assert_greater_than_equal(actual.responseStart, actual.requestStart, 'responseStart after requestStart');
+ assert_greater_than_equal(actual.responseEnd, actual.responseStart, 'responseEnd after responseStart');
+ this.done();
+ });
+
+ t["network_attrs"].step(function test() {
+ var actual = window.performance.getEntriesByName(expected.name)[0];
+ assert_equals(actual.encodedBodySize, expected.encodedBodySize, "encodedBodySize size");
+ assert_equals(actual.decodedBodySize, expected.decodedBodySize, "decodedBodySize size");
+
+ // Transfer size will vary from browser to browser based on default headers, etc. This
+ // test verifies that transferSize for uncached resources is greater than on-the-wire
+ // body size.
+ assert_greater_than(actual.transferSize, actual.encodedBodySize, "transferSize size");
+ this.done();
+ });
+
+ t["protocol"].step(function() {
+ var actual = window.performance.getEntriesByName(expected.name)[0];
+ assert_equals(actual.nextHopProtocol, "http/1.1", "expected protocol");
+ this.done();
+ });
+
+}
diff --git a/testing/web-platform/tests/resource-timing/tojson.html b/testing/web-platform/tests/resource-timing/tojson.html
new file mode 100644
index 0000000000..2564b855df
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/tojson.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>This test validates that PerformanceResourceTiming's toJSON method
+ contains all of the entry's attributes.</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help"
+ href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-tojson">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+promise_test(() => {
+ return new Promise((resolve, reject) => {
+ const po = new PerformanceObserver(list => {
+ const entries = list.getEntries();
+ if (entries.length < 1) {
+ throw new Error(`No entries to run the test with.`);
+ }
+ assert_greater_than_equal(entries.length, 1);
+ const entry = entries[0];
+ assert_equals(typeof(entry.toJSON), 'function');
+ const json = entry.toJSON();
+ assert_equals(typeof(json), 'object');
+
+ const performanceResourceTimingKeys = [
+ 'name',
+ 'entryType',
+ 'startTime',
+ 'duration',
+ 'initiatorType',
+ 'nextHopProtocol',
+ 'workerStart',
+ 'redirectStart',
+ 'redirectEnd',
+ 'fetchStart',
+ 'domainLookupStart',
+ 'domainLookupEnd',
+ 'connectStart',
+ 'connectEnd',
+ 'secureConnectionStart',
+ 'requestStart',
+ 'responseStart',
+ 'responseEnd',
+ 'transferSize',
+ 'encodedBodySize',
+ 'decodedBodySize',
+ 'renderBlockingStatus',
+ 'responseStatus',
+ 'contentType',
+ ];
+ for (const key of performanceResourceTimingKeys) {
+ try {
+ assert_false(json[key] === undefined,
+ `entry.toJSON().${key} is undefined`);
+ assert_equals(json[key], entry[key],
+ `entry.toJSON().${key} should match entry.${key}`);
+ } catch(e) {
+ reject(e);
+ }
+ }
+ resolve();
+ });
+ po.observe({type: "resource", buffered: true});
+ });
+}, 'Test toJSON() in PerformanceResourceTiming');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/workerStart-tao-protected.https.html b/testing/web-platform/tests/resource-timing/workerStart-tao-protected.https.html
new file mode 100644
index 0000000000..f54c0f2756
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/workerStart-tao-protected.https.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing - Check that workerStart is TAO protected</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help"
+ href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/entry-invariants.js"></script>
+<script src="resources/resource-loaders.js"></script>
+</head>
+<body>
+<script>
+
+const {HTTPS_REMOTE_ORIGIN} = get_host_info();
+
+const worker_has_unregistered = new Promise(resolve => {
+ addEventListener("message", e => {
+ if (e.data === "unregistered") {
+ resolve();
+ }
+ });
+});
+
+// Open window to remote origin with a SW install.
+let openee;
+const service_worker_has_installed = new Promise(resolve => {
+ addEventListener("message", e => {
+ if (e.data === 'installed') {
+ resolve();
+ }
+ });
+ openee = window.open(HTTPS_REMOTE_ORIGIN +
+ "/resource-timing/resources/sw-install.html");
+});
+
+const load_after_sw_install = async path => {
+ await service_worker_has_installed;
+ return load.iframe(path);
+}
+
+attribute_test(load_after_sw_install,
+ `${HTTPS_REMOTE_ORIGIN}/resource-timing/resources/green.html`,
+ entry => {
+ assert_equals(entry.workerStart, 0, "workerStart must be zero");
+ invariants.assert_tao_failure_resource(entry);
+ },
+ "A resource from a cross-origin service worker must not expose workerStart " +
+ "when there is a TAO failure");
+
+attribute_test(load_after_sw_install,
+ `${HTTPS_REMOTE_ORIGIN}/resource-timing/resources/blank-with-tao.html`,
+ entry => {
+ assert_greater_than(entry.workerStart, 0,
+ "workerStart must be greater than zero");
+ // TODO(crbug.com/925239): need to add coverage for transferSize,
+ // encoded/decodedBodySize but that's broken right now.
+ // TODO(crbug.com/1171767): need to add coverage for SW-handled resources
+ // that redirect. Should workerStart be non-zero if any piece of the
+ // redirect chain is handled by a SW?
+ },
+ "A resource from a cross-origin service worker must expose workerStart " +
+ "when there is a TAO match");
+
+promise_test(async () => {
+ openee.postMessage("unregister", "*");
+ await worker_has_unregistered;
+ openee.close();
+}, "Not a test - needed to unregister the SW and close its embedder");
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/worklet-rt-entries.https.html b/testing/web-platform/tests/resource-timing/worklet-rt-entries.https.html
new file mode 100644
index 0000000000..8ed280be17
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/worklet-rt-entries.https.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing Entry Sequence of Events for Worklets</title>
+<link rel="help" href="https://w3c.github.io/resource-timing/"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+ promise_test(async() => {
+ const url = new URL("/worklets/resources/empty-worklet-script.js", location.href).toString();
+ await CSS.paintWorklet.addModule(url);
+ assert_equals(performance.getEntriesByName(url).length, 1);
+ }, "Worklets should generate Resource Timing Entries");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resource-timing/xhr-resource-timing.html b/testing/web-platform/tests/resource-timing/xhr-resource-timing.html
new file mode 100644
index 0000000000..6f8f333186
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/xhr-resource-timing.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>This test validates that a failed cross-origin fetch creates an opaque network timing entry.
+</title>
+<link rel="author" title="Noam Rosenthal" href="noam@webkit.org">
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/resource-loaders.js"></script>
+<script src="resources/entry-invariants.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+</head>
+<body>
+<script>
+const {REMOTE_ORIGIN} = get_host_info();
+
+attribute_test(
+ load.xhr_async, "/common/dummy.xml",
+ invariants.assert_tao_pass_no_redirect_http,
+ "Verify resource timing entry creation for successful XHR");
+
+attribute_test(
+ load.xhr_async, `${REMOTE_ORIGIN}/common/dummy.xml`,
+ invariants.assert_tao_failure_resource,
+ "Verify resource timing entry creation for XHR failing with CORS");
+</script>
+</body> \ No newline at end of file