summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/resource-timing/resources
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/resource-timing/resources')
-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.js512
-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/get-resourceID.js30
-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/loadingResources.js21
-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/test-initiator.js16
-rw-r--r--testing/web-platform/tests/resource-timing/resources/webperftestharness.js166
-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
99 files changed, 2463 insertions, 0 deletions
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..78d43d1b01
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/entry-invariants.js
@@ -0,0 +1,512 @@
+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, loader) => {
+ const url = new URL(originalURL, location.href);
+ const search = new URLSearchParams(url.search.substr(1));
+ const timeBefore = performance.now();
+
+ // Load using `fetch()`, unless we're given a specific loader for this test.
+ 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/get-resourceID.js b/testing/web-platform/tests/resource-timing/resources/get-resourceID.js
new file mode 100644
index 0000000000..3fe499226a
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/get-resourceID.js
@@ -0,0 +1,30 @@
+function getResourceID(resourceName) {
+ return new Promise((resolve) => {
+ const observer = new PerformanceObserver((list) => {
+ const entries = list.getEntriesByType("resource");
+ for (const entry of entries) {
+ if (entry.name.endsWith(resourceName)) {
+ observer.disconnect();
+ resolve(`${entry.name}/${entry.startTime}`);
+ return;
+ }
+ }
+ });
+ observer.observe({ entryTypes: ["resource"] });
+ });
+}
+
+function getDocumentResourceID() {
+ return new Promise((resolve) => {
+ const observer = new PerformanceObserver((list) => {
+ const entries = list.getEntriesByType("navigation");
+ if (entries.length > 0) {
+ observer.disconnect();
+ const [entry] = entries;
+ const { name, startTime } = entry;
+ resolve(`${name}/${startTime}`);
+ }
+ });
+ observer.observe({ entryTypes: ["navigation"] });
+ });
+}
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/loadingResources.js b/testing/web-platform/tests/resource-timing/resources/loadingResources.js
new file mode 100644
index 0000000000..e5d5d71982
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/loadingResources.js
@@ -0,0 +1,21 @@
+//Fetching the Stylesheet
+var link = document.createElement("link");
+link.rel = "stylesheet";
+link.href = "../resources/empty_style.css";
+document.head.appendChild(link);
+
+// Fetching an image
+var img = document.createElement("img");
+img.src = "/images/blue.png";
+img.alt = "Sample Image for testing initiator Attribute";
+document.body.appendChild(img);
+
+//Inserting a html document in an iframe
+var iframe = document.createElement("iframe");
+iframe.src = "../resources/green.html";
+document.body.appendChild(iframe);
+
+// Inserting a script element
+var script = document.createElement("script");
+script.src = "../resources/empty.js";
+document.body.appendChild(script);
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/test-initiator.js b/testing/web-platform/tests/resource-timing/resources/test-initiator.js
new file mode 100644
index 0000000000..f839b463c5
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/test-initiator.js
@@ -0,0 +1,16 @@
+function testResourceInitiator(resourceName, expectedInitiator) {
+ return new Promise(resolve => {
+ const observer = new PerformanceObserver(list => {
+ const entries = list.getEntriesByType('resource');
+ for (const entry of entries) {
+ if (entry.name.endsWith(resourceName)) {
+ observer.disconnect();
+ assert_equals(entry.initiator, expectedInitiator, `Test ${resourceName} initiator`);
+ resolve();
+ return;
+ }
+ }
+ });
+ observer.observe({entryTypes: ['resource']});
+ });
+} \ No newline at end of file
diff --git a/testing/web-platform/tests/resource-timing/resources/webperftestharness.js b/testing/web-platform/tests/resource-timing/resources/webperftestharness.js
new file mode 100644
index 0000000000..869ef3d74f
--- /dev/null
+++ b/testing/web-platform/tests/resource-timing/resources/webperftestharness.js
@@ -0,0 +1,166 @@
+/*
+author: W3C http://www.w3.org/
+help: http://www.w3.org/TR/navigation-timing/#sec-window.performance-attribute
+*/
+//
+// Helper Functions for ResourceTiming W3C tests
+//
+
+var performanceNamespace = window.performance;
+var timingAttributes = [
+ 'connectEnd',
+ 'connectStart',
+ 'domComplete',
+ 'domContentLoadedEventEnd',
+ 'domContentLoadedEventStart',
+ 'domInteractive',
+ 'domLoading',
+ 'domainLookupEnd',
+ 'domainLookupStart',
+ 'fetchStart',
+ 'loadEventEnd',
+ 'loadEventStart',
+ 'navigationStart',
+ 'redirectEnd',
+ 'redirectStart',
+ 'requestStart',
+ 'responseEnd',
+ 'responseStart',
+ 'unloadEventEnd',
+ 'unloadEventStart'
+];
+
+var namespace_check = false;
+
+//
+// All test() functions in the WebPerf test suite should use wp_test() instead.
+//
+// wp_test() validates the window.performance namespace exists prior to running tests and
+// immediately shows a single failure if it does not.
+//
+
+function wp_test(func, msg, properties)
+{
+ // only run the namespace check once
+ if (!namespace_check)
+ {
+ namespace_check = true;
+
+ if (performanceNamespace === undefined || performanceNamespace == null)
+ {
+ // show a single error that window.performance is undefined
+ // The window.performance attribute provides a hosting area for performance related attributes.
+ test(function() { assert_true(performanceNamespace !== undefined && performanceNamespace != null, "window.performance is defined and not null"); }, "window.performance is defined and not null.");
+ }
+ }
+
+ test(func, msg, properties);
+}
+
+function test_namespace(child_name, skip_root)
+{
+ if (skip_root === undefined) {
+ var msg = 'window.performance is defined';
+ // The window.performance attribute provides a hosting area for performance related attributes.
+ wp_test(function () { assert_not_equals(performanceNamespace, undefined, msg); }, msg);
+ }
+
+ if (child_name !== undefined) {
+ var msg2 = 'window.performance.' + child_name + ' is defined';
+ // The window.performance attribute provides a hosting area for performance related attributes.
+ wp_test(function() { assert_not_equals(performanceNamespace[child_name], undefined, msg2); }, msg2);
+ }
+}
+
+function test_attribute_exists(parent_name, attribute_name, properties)
+{
+ var msg = 'window.performance.' + parent_name + '.' + attribute_name + ' is defined.';
+ wp_test(function() { assert_not_equals(performanceNamespace[parent_name][attribute_name], undefined, msg); }, msg, properties);
+}
+
+function test_enum(parent_name, enum_name, value, properties)
+{
+ var msg = 'window.performance.' + parent_name + '.' + enum_name + ' is defined.';
+ wp_test(function() { assert_not_equals(performanceNamespace[parent_name][enum_name], undefined, msg); }, msg, properties);
+
+ msg = 'window.performance.' + parent_name + '.' + enum_name + ' = ' + value;
+ wp_test(function() { assert_equals(performanceNamespace[parent_name][enum_name], value, msg); }, msg, properties);
+}
+
+function test_timing_order(attribute_name, greater_than_attribute, properties)
+{
+ // ensure it's not 0 first
+ var msg = "window.performance.timing." + attribute_name + " > 0";
+ wp_test(function() { assert_true(performanceNamespace.timing[attribute_name] > 0, msg); }, msg, properties);
+
+ // ensure it's in the right order
+ msg = "window.performance.timing." + attribute_name + " >= window.performance.timing." + greater_than_attribute;
+ wp_test(function() { assert_true(performanceNamespace.timing[attribute_name] >= performanceNamespace.timing[greater_than_attribute], msg); }, msg, properties);
+}
+
+function test_timing_greater_than(attribute_name, greater_than, properties)
+{
+ var msg = "window.performance.timing." + attribute_name + " > " + greater_than;
+ test_greater_than(performanceNamespace.timing[attribute_name], greater_than, msg, properties);
+}
+
+function test_timing_equals(attribute_name, equals, msg, properties)
+{
+ var test_msg = msg || "window.performance.timing." + attribute_name + " == " + equals;
+ test_equals(performanceNamespace.timing[attribute_name], equals, test_msg, properties);
+}
+
+//
+// Non-test related helper functions
+//
+
+function sleep_milliseconds(n)
+{
+ var start = new Date().getTime();
+ while (true) {
+ if ((new Date().getTime() - start) >= n) break;
+ }
+}
+
+//
+// Common helper functions
+//
+
+function test_true(value, msg, properties)
+{
+ wp_test(function () { assert_true(value, msg); }, msg, properties);
+}
+
+function test_equals(value, equals, msg, properties)
+{
+ wp_test(function () { assert_equals(value, equals, msg); }, msg, properties);
+}
+
+function test_greater_than(value, greater_than, msg, properties)
+{
+ wp_test(function () { assert_true(value > greater_than, msg); }, msg, properties);
+}
+
+function test_greater_or_equals(value, greater_than, msg, properties)
+{
+ wp_test(function () { assert_true(value >= greater_than, msg); }, msg, properties);
+}
+
+function test_not_equals(value, notequals, msg, properties)
+{
+ wp_test(function() { assert_not_equals(value, notequals, msg); }, msg, properties);
+}
+
+function test_tao_pass(entry) {
+ test_greater_than(entry.redirectStart, 0, 'redirectStart > 0 in cross-origin redirect with Timing-Allow-Origin.');
+ test_greater_than(entry.redirectEnd, 0, 'redirectEnd > 0 in cross-origin redirect with Timing-Allow-Origin.');
+ test_greater_than(entry.fetchStart, 0, 'fetchStart > 0 in cross-origin redirect with Timing-Allow-Origin.');
+ test_greater_than(entry.fetchStart, entry.startTime, 'startTime < fetchStart in cross-origin redirect with Timing-Allow-Origin.');
+}
+
+function test_tao_fail(entry) {
+ test_equals(entry.redirectStart, 0, 'redirectStart == 0 in cross-origin redirect with failing Timing-Allow-Origin.');
+ test_equals(entry.redirectEnd, 0, 'redirectEnd == 0 in cross-origin redirect with failing Timing-Allow-Origin.');
+ test_greater_than(entry.fetchStart, 0, 'fetchStart > 0 in cross-origin redirect with failing Timing-Allow-Origin.');
+ test_equals(entry.fetchStart, entry.startTime, 'startTime == fetchStart in cross-origin redirect with failing Timing-Allow-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');