diff options
Diffstat (limited to 'testing/web-platform/tests/resource-timing/resources')
95 files changed, 2228 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 Binary files differnew file mode 100644 index 0000000000..820f8cace2 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/blue-with-tao.png 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 Binary files differnew file mode 100644 index 0000000000..820f8cace2 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/blue.png diff --git a/testing/web-platform/tests/resource-timing/resources/buffer-full-utilities.js b/testing/web-platform/tests/resource-timing/resources/buffer-full-utilities.js new file mode 100644 index 0000000000..6cb1753b2e --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/buffer-full-utilities.js @@ -0,0 +1,75 @@ +// This script relies on resources/resource-loaders.js. Include it before in order for the below +// methods to work properly. + +// The resources used to trigger new entries. +const scriptResources = [ + 'resources/empty.js', + 'resources/empty_script.js', + 'resources/empty.js?id' +]; + +const waitForNextTask = () => { + return new Promise(resolve => { + step_timeout(resolve, 0); + }); +}; + +const clearBufferAndSetSize = size => { + performance.clearResourceTimings(); + performance.setResourceTimingBufferSize(size); +} + +const forceBufferFullEvent = async () => { + clearBufferAndSetSize(1); + return new Promise(async resolve => { + performance.addEventListener('resourcetimingbufferfull', resolve); + // Load 2 resources to ensure onresourcetimingbufferfull is fired. + // Load them in order in order to get the entries in that order! + await load.script(scriptResources[0]); + await load.script(scriptResources[1]); + }); +}; + +const fillUpTheBufferWithSingleResource = async (src = scriptResources[0]) => { + clearBufferAndSetSize(1); + await load.script(src); +}; + +const fillUpTheBufferWithTwoResources = async () => { + clearBufferAndSetSize(2); + // Load them in order in order to get the entries in that order! + await load.script(scriptResources[0]); + await load.script(scriptResources[1]); +}; + +const addAssertUnreachedBufferFull = t => { + performance.addEventListener('resourcetimingbufferfull', t.step_func(() => { + assert_unreached("resourcetimingbufferfull should not fire") + })); +}; + +const checkEntries = numEntries => { + const entries = performance.getEntriesByType('resource'); + assert_equals(entries.length, numEntries, + 'Number of entries does not match the expected value.'); + assert_true(entries[0].name.includes(scriptResources[0]), + scriptResources[0] + " is in the entries buffer"); + if (entries.length > 1) { + assert_true(entries[1].name.includes(scriptResources[1]), + scriptResources[1] + " is in the entries buffer"); + } + if (entries.length > 2) { + assert_true(entries[2].name.includes(scriptResources[2]), + scriptResources[2] + " is in the entries buffer"); + } +} + +const bufferFullFirePromise = new Promise(resolve => { + performance.addEventListener('resourcetimingbufferfull', async () => { + // Wait for the next task just to ensure that all bufferfull events have fired, and to ensure + // that the secondary buffer is copied (as this is an event, there may be microtask checkpoints + // right after running an event handler). + await waitForNextTask(); + resolve(); + }); +}); diff --git a/testing/web-platform/tests/resource-timing/resources/cacheable-and-validated.py b/testing/web-platform/tests/resource-timing/resources/cacheable-and-validated.py new file mode 100644 index 0000000000..97de866277 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/cacheable-and-validated.py @@ -0,0 +1,30 @@ +def main(request, response): + # Headers need to be set before `response.writer` writes out the response. + tao = request.GET.get(b'timing_allow_origin') + if tao: + response.headers.set(b"Timing-Allow-Origin", tao) + + if b'origin' in request.headers: + origin = request.headers[b'origin'] + response.headers.set(b'Access-Control-Allow-Origin', origin) + + content = request.GET.first(b'content') + response.headers.set(b'Cache-Control', b'max-age=60') + response.headers.set(b'ETag', b'assdfsdfe') + + # Handle CORS-preflights of non-simple requests. + if request.method == 'OPTIONS': + response.status = 204 + requested_method = request.headers.get(b"Access-Control-Request-Method") + if requested_method: + response.headers.set(b"Access-Control-Allow-Methods", requested_method) + requested_headers = request.headers.get(b"Access-Control-Request-Headers") + if requested_headers: + response.headers.set(b"Access-Control-Allow-Headers", requested_headers) + else: + if 'Cache-Control' in request.headers: + response.status = (304, b'NotModified') + else: + response.status = (200, b'OK') + response.write_status_headers() + response.writer.write(content) diff --git a/testing/web-platform/tests/resource-timing/resources/close.html b/testing/web-platform/tests/resource-timing/resources/close.html new file mode 100644 index 0000000000..02c275f37b --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/close.html @@ -0,0 +1 @@ +<script>window.close()</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/resource-timing/resources/connection-reuse-test.js b/testing/web-platform/tests/resource-timing/resources/connection-reuse-test.js new file mode 100644 index 0000000000..453fbd3405 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/connection-reuse-test.js @@ -0,0 +1,63 @@ +// This script is loaded in HTTP and HTTPS contexts to validate +// PerformanceResourceTiming entries' attributes when reusing connections. +// +// Note: to ensure that we reuse the connection to fetch multiple resources, we +// use the same XMLHttpRequest object throughout an individual test. Although +// it doesn't seem to be specified, each browser tested by WPT will reuse the +// underlying TCP connection with this approach. Pre-establishing the XHR's +// connection helps us to test connection reuse also in browsers that may key +// their connections on the related request's credentials mode. + +const connection_reuse_test = (path, follow_on_assertions, test_label) => { + const {on_200, on_304} = follow_on_assertions; + + // Make the first request before calling 'attribute_test' so that only the + // second request's PerformanceResourceTiming entry will be interrogated. We + // don't check the first request's PerformanceResourceTiming entry because + // that's not what this test is trying to validate. + const client = new XMLHttpRequest(); + const identifier = Math.random(); + path = `${path}?tag=${identifier}`; + client.open("GET", path, false); + client.send(); + + attribute_test( + async () => { + client.open("GET", path + "&same_resource=false", false); + client.send(); + + // We expect to get a 200 Ok response because we've requested a different + // resource than previous requests. + if (client.status != 200) { + throw new Error(`Got something other than a 200 response. ` + + `client.status: ${client.status}`); + } + }, path, entry => { + invariants.assert_connection_reused(entry); + on_200(entry); + }, + `PerformanceResrouceTiming entries need to conform to the spec when a ` + + `distinct resource is fetched over a persistent connection ` + + `(${test_label})`); + + attribute_test( + async () => { + client.open("GET", path, false); + client.setRequestHeader("If-None-Match", identifier); + client.send(); + + // We expect to get a 304 Not Modified response because we've used a + // matching 'identifier' for the If-None-Match header. + if (client.status != 304) { + throw new Error(`Got something other than a 304 response. ` + + `client.status: ${client.status}, response: ` + + `'${client.responseText}'`); + } + }, path, entry => { + invariants.assert_connection_reused(entry); + on_304(entry); + }, + `PerformanceResrouceTiming entries need to conform to the spec when the ` + + `resource is cache-revalidated over a persistent connection ` + + `(${test_label})`); +} diff --git a/testing/web-platform/tests/resource-timing/resources/content-type.py b/testing/web-platform/tests/resource-timing/resources/content-type.py new file mode 100644 index 0000000000..23a4f0dbc6 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/content-type.py @@ -0,0 +1,5 @@ +def main(request, response): + if b'content_type' in request.GET: + response.headers.set(b'content-type', request.GET.first(b'content_type')) + if b'allow_origin' in request.GET: + response.headers.set(b'access-control-allow-origin', request.GET.first(b'allow_origin'))
\ No newline at end of file diff --git a/testing/web-platform/tests/resource-timing/resources/cors-ahem.py b/testing/web-platform/tests/resource-timing/resources/cors-ahem.py new file mode 100644 index 0000000000..dee5b62f00 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/cors-ahem.py @@ -0,0 +1,19 @@ +import os.path + +from wptserve.utils import isomorphic_decode + +def main(request, response): + etag = b"123abc" + if etag == request.headers.get(b"If-None-Match", None): + response.headers.set(b"X-HTTP-STATUS", 304) + response.status = (304, b"Not Modified") + return u"" + + response.headers.set(b"Cache-Control", b"public, max-age=86400") + response.headers.set(b"Content-Type", b"font/truetype") + response.headers.set(b"Access-Control-Allow-Origin", b"*") + response.headers.set(b"Timing-Allow-Origin", b"*") + response.headers.set(b"ETag", etag) + font = u"../../fonts/Ahem.ttf" + path = os.path.join(os.path.dirname(isomorphic_decode(__file__)), font) + response.content = open(path, u"rb").read() diff --git a/testing/web-platform/tests/resource-timing/resources/csp-default-none.html b/testing/web-platform/tests/resource-timing/resources/csp-default-none.html new file mode 100644 index 0000000000..1f59d8c225 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/csp-default-none.html @@ -0,0 +1,3 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>empty page</title> diff --git a/testing/web-platform/tests/resource-timing/resources/csp-default-none.html.headers b/testing/web-platform/tests/resource-timing/resources/csp-default-none.html.headers new file mode 100644 index 0000000000..d66f886dd2 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/csp-default-none.html.headers @@ -0,0 +1,2 @@ +Content-Security-Policy: default-src 'none' + diff --git a/testing/web-platform/tests/resource-timing/resources/delay-css.py b/testing/web-platform/tests/resource-timing/resources/delay-css.py new file mode 100644 index 0000000000..9a905960ee --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/delay-css.py @@ -0,0 +1,6 @@ +import time + +def main(request, response): + time.sleep(float(request.GET.first(b"delay", 1000)) / 1000) + response.headers.set('Content-Type', 'text/css') + return "/* */" diff --git a/testing/web-platform/tests/resource-timing/resources/delay-load.html b/testing/web-platform/tests/resource-timing/resources/delay-load.html new file mode 100644 index 0000000000..4898c1be8e --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/delay-load.html @@ -0,0 +1,4 @@ +<!DOCTYPE html> +<body> +<img src="/images/blue.png?pipe=trickle(d1)"> +</body> diff --git a/testing/web-platform/tests/resource-timing/resources/document-domain-no-impact.html b/testing/web-platform/tests/resource-timing/resources/document-domain-no-impact.html new file mode 100644 index 0000000000..64cdd8a870 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/document-domain-no-impact.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="resource-loaders.js"></script> +<script src="entry-invariants.js"></script> +<body> +<script> +const path = location.origin + + "/resource-timing/resources/iframe-setdomain.sub.html"; +attribute_test_with_validator(load.iframe, path, + el => { + try { + el.contentWindow.document; + throw new Error("iframe document.domain was not set"); + } catch(error) { + if (error.name != "SecurityError") { + throw(error); + } + } + }, + invariants.assert_tao_pass_no_redirect_http, + "test that document.domain being set doesn't have an impact on the " + + "resource timing entry." +); +</script> + diff --git a/testing/web-platform/tests/resource-timing/resources/document-navigated.html b/testing/web-platform/tests/resource-timing/resources/document-navigated.html new file mode 100644 index 0000000000..bedae77082 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/document-navigated.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8" /> +</head> +<body> + Navigated document! +<script> + top.postMessage("navigated", "*"); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/resource-timing/resources/document-refreshed.html b/testing/web-platform/tests/resource-timing/resources/document-refreshed.html new file mode 100644 index 0000000000..568f7f27c7 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/document-refreshed.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8" /> +</head> +<body> + Refreshed document! +<script> + top.postMessage("refreshed", "*"); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/resource-timing/resources/document-that-navigates.html b/testing/web-platform/tests/resource-timing/resources/document-that-navigates.html new file mode 100644 index 0000000000..a59e9f3ab2 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/document-that-navigates.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8" /> +</head> +<body> +<script> + location.href="document-navigated.html"; +</script> +</body> +</html> diff --git a/testing/web-platform/tests/resource-timing/resources/document-that-refreshes.html b/testing/web-platform/tests/resource-timing/resources/document-that-refreshes.html new file mode 100644 index 0000000000..659513a642 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/document-that-refreshes.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8" /> +<meta http-equiv="refresh" content="0;document-refreshed.html"> +</head> +<body> +</body> +</html> diff --git a/testing/web-platform/tests/resource-timing/resources/download.asis b/testing/web-platform/tests/resource-timing/resources/download.asis new file mode 100644 index 0000000000..167386d7a5 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/download.asis @@ -0,0 +1,6 @@ +HTTP/1.0 200 OK +Content-Type: application/octet-stream + +12312313 + + diff --git a/testing/web-platform/tests/resource-timing/resources/embed-navigate-back.html b/testing/web-platform/tests/resource-timing/resources/embed-navigate-back.html new file mode 100644 index 0000000000..c9c7340f53 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/embed-navigate-back.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8" /> +<title>Resource Timing embed navigate - back button navigation</title> +</head> +<body onunload="/*disable bfcache*/"> +<script src="/common/get-host-info.sub.js"></script> +<script src="nested-contexts.js"></script> +<script> + setup_back_navigation("embed-navigate-back.html"); +</script> +<embed id="target" type="text/html"> +<script> + target.src = pre_navigate_url; +</script> +</body> +</html> diff --git a/testing/web-platform/tests/resource-timing/resources/embed-navigate.html b/testing/web-platform/tests/resource-timing/resources/embed-navigate.html new file mode 100644 index 0000000000..24c9d3c462 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/embed-navigate.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8" /> +<title>Resource Timing embed navigate</title> +</head> +<body> +<script src="/common/get-host-info.sub.js"></script> +<script src="nested-contexts.js"></script> +<script> + setup_navigate_test(); +</script> +<embed id="target" type="text/html"> +<script> + target.src = pre_navigate_url; +</script> +</body> +</html> diff --git a/testing/web-platform/tests/resource-timing/resources/embed-refresh.html b/testing/web-platform/tests/resource-timing/resources/embed-refresh.html new file mode 100644 index 0000000000..bd4b5a14de --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/embed-refresh.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8" /> +<title>Resource Timing embed refresh</title> +</head> +<body> +<script src="/common/get-host-info.sub.js"></script> +<script src="nested-contexts.js"></script> +<script> + setup_refresh_test(); +</script> +<embed id="target" type="text/html"> +<script> + target.src = pre_refresh_url; +</script> +</body> +</html> diff --git a/testing/web-platform/tests/resource-timing/resources/empty.js b/testing/web-platform/tests/resource-timing/resources/empty.js new file mode 100644 index 0000000000..3b44754e30 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/empty.js @@ -0,0 +1 @@ +/* Nothing here */ diff --git a/testing/web-platform/tests/resource-timing/resources/empty.py b/testing/web-platform/tests/resource-timing/resources/empty.py new file mode 100644 index 0000000000..cae83c146e --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/empty.py @@ -0,0 +1,3 @@ +def main(request, response): + response.headers.set(b"Content-Type", b"text/plain") + return u"" diff --git a/testing/web-platform/tests/resource-timing/resources/empty_script.js b/testing/web-platform/tests/resource-timing/resources/empty_script.js new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/empty_script.js diff --git a/testing/web-platform/tests/resource-timing/resources/empty_style.css b/testing/web-platform/tests/resource-timing/resources/empty_style.css new file mode 100644 index 0000000000..eb90b432e8 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/empty_style.css @@ -0,0 +1 @@ +/*Nothing here*/ diff --git a/testing/web-platform/tests/resource-timing/resources/entry-invariants.js b/testing/web-platform/tests/resource-timing/resources/entry-invariants.js new file mode 100644 index 0000000000..bbc913b722 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/entry-invariants.js @@ -0,0 +1,510 @@ +const await_with_timeout = async (delay, message, promise, cleanup = ()=>{}) => { + let timeout_id; + const timeout = new Promise((_, reject) => { + timeout_id = step_timeout(() => + reject(new DOMException(message, "TimeoutError")), delay) + }); + let result = null; + try { + result = await Promise.race([promise, timeout]); + clearTimeout(timeout_id); + } finally { + cleanup(); + } + return result; +}; + +// Asserts that the given attributes are present in 'entry' and hold equal +// values. +const assert_all_equal_ = (entry, attributes) => { + let first = attributes[0]; + attributes.slice(1).forEach(other => { + assert_equals(entry[first], entry[other], + `${first} should be equal to ${other}`); + }); +} + +// Asserts that the given attributes are present in 'entry' and hold values +// that are sorted in the same order as given in 'attributes'. +const assert_ordered_ = (entry, attributes) => { + let before = attributes[0]; + attributes.slice(1).forEach(after => { + assert_greater_than_equal(entry[after], entry[before], + `${after} should be greater than ${before}`); + before = after; + }); +} + +// Asserts that the given attributes are present in 'entry' and hold a value of +// 0. +const assert_zeroed_ = (entry, attributes) => { + attributes.forEach(attribute => { + assert_equals(entry[attribute], 0, `${attribute} should be 0`); + }); +} + +// Asserts that the given attributes are present in 'entry' and hold a value of +// 0 or more. +const assert_not_negative_ = (entry, attributes) => { + attributes.forEach(attribute => { + assert_greater_than_equal(entry[attribute], 0, + `${attribute} should be greater than or equal to 0`); + }); +} + +// Asserts that the given attributes are present in 'entry' and hold a value +// greater than 0. +const assert_positive_ = (entry, attributes) => { + attributes.forEach(attribute => { + assert_greater_than(entry[attribute], 0, + `${attribute} should be greater than 0`); + }); +} + +const invariants = { + // Asserts that attributes of the given PerformanceResourceTiming entry match + // what the spec dictates for any resource fetched over HTTP without + // redirects but passing the Timing-Allow-Origin checks. + assert_tao_pass_no_redirect_http: entry => { + assert_ordered_(entry, [ + "fetchStart", + "domainLookupStart", + "domainLookupEnd", + "connectStart", + "connectEnd", + "requestStart", + "responseStart", + "responseEnd", + ]); + + assert_zeroed_(entry, [ + "workerStart", + "secureConnectionStart", + "redirectStart", + "redirectEnd", + ]); + + assert_not_negative_(entry, [ + "duration", + ]); + + assert_positive_(entry, [ + "fetchStart", + "transferSize", + ]); + }, + + // Like assert_tao_pass_no_redirect_http but for empty response bodies. + assert_tao_pass_no_redirect_http_empty: entry => { + assert_ordered_(entry, [ + "fetchStart", + "domainLookupStart", + "domainLookupEnd", + "connectStart", + "connectEnd", + "requestStart", + "responseStart", + "responseEnd", + ]); + + assert_zeroed_(entry, [ + "workerStart", + "secureConnectionStart", + "redirectStart", + "redirectEnd", + ]); + + assert_not_negative_(entry, [ + "duration", + ]); + + assert_positive_(entry, [ + "fetchStart", + "transferSize", + ]); + }, + + // Like assert_tao_pass_no_redirect_http but for resources fetched over HTTPS + assert_tao_pass_no_redirect_https: entry => { + assert_ordered_(entry, [ + "fetchStart", + "domainLookupStart", + "domainLookupEnd", + "secureConnectionStart", + "connectStart", + "connectEnd", + "requestStart", + "responseStart", + "responseEnd", + ]); + + assert_zeroed_(entry, [ + "workerStart", + "redirectStart", + "redirectEnd", + ]); + + assert_not_negative_(entry, [ + "duration", + ]); + + assert_positive_(entry, [ + "fetchStart", + "transferSize", + ]); + }, + + // Like assert_tao_pass_no_redirect_https but for resources that did encounter + // at least one HTTP redirect. + assert_tao_pass_with_redirect_https: entry => { + assert_ordered_(entry, [ + "fetchStart", + "redirectStart", + "redirectEnd", + "domainLookupStart", + "domainLookupEnd", + "secureConnectionStart", + "connectStart", + "connectEnd", + "requestStart", + "responseStart", + "responseEnd", + ]); + + assert_zeroed_(entry, [ + "workerStart", + ]); + + assert_not_negative_(entry, [ + "duration", + ]); + + assert_positive_(entry, [ + "fetchStart", + "transferSize", + ]); + }, + + // Like assert_tao_pass_no_redirect_http but, since the resource's bytes + // won't be retransmitted, the encoded and decoded sizes must be zero. + assert_tao_pass_304_not_modified_http: entry => { + assert_ordered_(entry, [ + "fetchStart", + "domainLookupStart", + "domainLookupEnd", + "connectStart", + "connectEnd", + "requestStart", + "responseStart", + "responseEnd", + ]); + + assert_zeroed_(entry, [ + "workerStart", + "secureConnectionStart", + "redirectStart", + "redirectEnd", + ]); + + assert_not_negative_(entry, [ + "duration", + ]); + + assert_positive_(entry, [ + "fetchStart", + "transferSize", + ]); + }, + + // Like assert_tao_pass_304_not_modified_http but for resources fetched over + // HTTPS. + assert_tao_pass_304_not_modified_https: entry => { + assert_ordered_(entry, [ + "fetchStart", + "domainLookupStart", + "domainLookupEnd", + "secureConnectionStart", + "connectStart", + "connectEnd", + "requestStart", + "responseStart", + "responseEnd", + ]); + + assert_zeroed_(entry, [ + "workerStart", + "redirectStart", + "redirectEnd", + ]); + + assert_not_negative_(entry, [ + "duration", + ]); + + assert_positive_(entry, [ + "fetchStart", + "transferSize", + ]); + }, + + // Asserts that attributes of the given PerformanceResourceTiming entry match + // what the spec dictates for any resource subsequently fetched over a + // persistent connection. When this happens, we expect that certain + // attributes describing transport layer behaviour will be equal. + assert_connection_reused: entry => { + assert_all_equal_(entry, [ + "fetchStart", + "connectStart", + "connectEnd", + "domainLookupStart", + "domainLookupEnd", + ]); + }, + + // Asserts that attributes of the given PerformanceResourceTiming entry match + // what the spec dictates for any resource fetched over HTTP through an HTTP + // redirect. + assert_same_origin_redirected_resource: entry => { + assert_positive_(entry, [ + "redirectStart", + ]); + + assert_equals(entry.redirectStart, entry.startTime, + "redirectStart should be equal to startTime"); + + assert_ordered_(entry, [ + "redirectStart", + "redirectEnd", + "fetchStart", + "domainLookupStart", + "domainLookupEnd", + "connectStart", + ]); + }, + + // Asserts that attributes of the given PerformanceResourceTiming entry match + // what the spec dictates for any resource fetched over HTTPS through a + // cross-origin redirect. + // (e.g. GET http://remote.com/foo => 302 Location: https://remote.com/foo) + assert_cross_origin_redirected_resource: entry => { + assert_zeroed_(entry, [ + "redirectStart", + "redirectEnd", + "domainLookupStart", + "domainLookupEnd", + "connectStart", + "connectEnd", + "secureConnectionStart", + "requestStart", + "responseStart", + ]); + + assert_positive_(entry, [ + "fetchStart", + "responseEnd", + ]); + + assert_ordered_(entry, [ + "fetchStart", + "responseEnd", + ]); + }, + + // Asserts that attributes of the given PerformanceResourceTiming entry match + // what the spec dictates when + // 1. An HTTP request is made for a same-origin resource. + // 2. The response to 1 is an HTTP redirect (like a 302). + // 3. The location from 2 is a cross-origin HTTPS URL. + // 4. The response to fetching the URL from 3 does not set a matching TAO header. + assert_http_to_cross_origin_redirected_resource: entry => { + assert_zeroed_(entry, [ + "redirectStart", + "redirectEnd", + "domainLookupStart", + "domainLookupEnd", + "connectStart", + "connectEnd", + "secureConnectionStart", + "requestStart", + "responseStart", + ]); + + assert_positive_(entry, [ + "fetchStart", + "responseEnd", + ]); + + assert_ordered_(entry, [ + "fetchStart", + "responseEnd", + ]); + }, + + // Asserts that attributes of the given PerformanceResourceTiming entry match + // what the spec dictates when + // 1. An HTTPS request is made for a same-origin resource. + // 2. The response to 1 is an HTTP redirect (like a 302). + // 3. The location from 2 is a cross-origin HTTPS URL. + // 4. The response to fetching the URL from 3 sets a matching TAO header. + assert_tao_enabled_cross_origin_redirected_resource: entry => { + assert_positive_(entry, [ + "redirectStart", + ]); + assert_ordered_(entry, [ + "redirectStart", + "redirectEnd", + "fetchStart", + "domainLookupStart", + "domainLookupEnd", + "connectStart", + "secureConnectionStart", + "connectEnd", + "requestStart", + "responseStart", + "responseEnd", + ]); + }, + + // Asserts that attributes of the given PerformanceResourceTiming entry match + // what the spec dictates when + // 1. An HTTP request is made for a same-origin resource + // 2. The response to 1 is an HTTP redirect (like a 302). + // 3. The location from 2 is a cross-origin HTTPS URL. + // 4. The response to fetching the URL from 3 sets a matching TAO header. + assert_http_to_tao_enabled_cross_origin_https_redirected_resource: entry => { + assert_zeroed_(entry, [ + // Note that, according to the spec, the secureConnectionStart attribute + // should describe the connection for the first resource request when + // there are redirects. Since the initial request is over HTTP, + // secureConnectionStart must be 0. + "secureConnectionStart", + ]); + assert_positive_(entry, [ + "redirectStart", + ]); + assert_ordered_(entry, [ + "redirectStart", + "redirectEnd", + "fetchStart", + "domainLookupStart", + "domainLookupEnd", + "connectStart", + "connectEnd", + "requestStart", + "responseStart", + "responseEnd", + ]); + }, + + assert_same_origin_redirected_from_cross_origin_resource: entry => { + assert_zeroed_(entry, [ + "workerStart", + "redirectStart", + "redirectEnd", + "domainLookupStart", + "domainLookupEnd", + "connectStart", + "connectEnd", + "secureConnectionStart", + "requestStart", + "responseStart", + "transferSize", + ]); + + assert_ordered_(entry, [ + "fetchStart", + "responseEnd", + ]); + + assert_equals(entry.fetchStart, entry.startTime, + "fetchStart must equal startTime"); + }, + + assert_tao_failure_resource: entry => { + assert_equals(entry.entryType, "resource", "entryType must always be 'resource'"); + + assert_positive_(entry, [ + "startTime", + ]); + + assert_not_negative_(entry, [ + "duration", + ]); + + assert_zeroed_(entry, [ + "redirectStart", + "redirectEnd", + "domainLookupStart", + "domainLookupEnd", + "connectStart", + "connectEnd", + "secureConnectionStart", + "requestStart", + "responseStart", + "transferSize", + ]); + } + +}; + +const attribute_test_internal = (loader, path, validator, run_test, test_label) => { + promise_test( + async () => { + let loaded_entry = new Promise((resolve, reject) => { + new PerformanceObserver((entry_list, self) => { + try { + const name_matches = entry_list.getEntries().forEach(entry => { + if (entry.name.includes(path)) { + resolve(entry); + } + }); + } catch(e) { + // By surfacing exceptions through the Promise interface, tests can + // fail fast with a useful message instead of timing out. + reject(e); + } + }).observe({"type": "resource"}); + }); + + await loader(path, validator); + const entry = await await_with_timeout(2000, + "Timeout was reached before entry fired", + loaded_entry); + assert_not_equals(entry, null, 'No entry was received'); + run_test(entry); + }, test_label); +}; + +// Given a resource-loader, a path (a relative path or absolute URL), and a +// PerformanceResourceTiming test, applies the loader to the resource path +// and tests the resulting PerformanceResourceTiming entry. +const attribute_test = (loader, path, run_test, test_label) => { + attribute_test_internal(loader, path, () => {}, run_test, test_label); +}; + +// Similar to attribute test, but on top of that, validates the added element, +// to ensure the test does what it intends to do. +const attribute_test_with_validator = (loader, path, validator, run_test, test_label) => { + attribute_test_internal(loader, path, validator, run_test, test_label); +}; + +const network_error_entry_test = (originalURL, args, label) => { + const url = new URL(originalURL, location.href); + const search = new URLSearchParams(url.search.substr(1)); + const timeBefore = performance.now(); + loader = () => new Promise(resolve => fetch(url, args).catch(resolve)); + + attribute_test( + loader, url, + () => { + const timeAfter = performance.now(); + const names = performance.getEntriesByType('resource').filter(e => e.initiatorType === 'fetch').map(e => e.name); + const entries = performance.getEntriesByName(url.toString()); + assert_equals(entries.length, 1, 'resource timing entry for network error'); + const entry = entries[0] + assert_equals(entry.startTime, entry.fetchStart, 'startTime and fetchStart should be equal'); + assert_greater_than_equal(entry.startTime, timeBefore, 'startTime and fetchStart should be greater than the time before fetching'); + assert_greater_than_equal(timeAfter, entry.responseEnd, 'endTime should be less than the time right after returning from the fetch'); + invariants.assert_tao_failure_resource(entry); + }, `A ResourceTiming entry should be created for network error of type ${label}`); +} diff --git a/testing/web-platform/tests/resource-timing/resources/eventsource.py b/testing/web-platform/tests/resource-timing/resources/eventsource.py new file mode 100644 index 0000000000..e3a2355730 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/eventsource.py @@ -0,0 +1,3 @@ +def main(request, response): + response.headers.set(b"Content-Type", b"text/event-stream") + return u"" diff --git a/testing/web-platform/tests/resource-timing/resources/fake_responses.html b/testing/web-platform/tests/resource-timing/resources/fake_responses.html new file mode 100644 index 0000000000..52cad6c415 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/fake_responses.html @@ -0,0 +1,19 @@ +<body> +<script> +function request(type) { + var client = new XMLHttpRequest, + identifier = type == "tag" ? Math.random() : new Date().toGMTString(), + url = "fake_responses.py?" + type + "=" + identifier + client.open("GET", url, false) + client.send(null) + client.open("GET", url, false) + client.setRequestHeader(type == "tag" ? "If-None-Match" : "If-Modified-Since", identifier) + client.send(null) +} + +if(window.parent.setup_iframe) { + window.parent.setup_iframe(); + request("tag"); +} +</script> +</body> diff --git a/testing/web-platform/tests/resource-timing/resources/fake_responses.py b/testing/web-platform/tests/resource-timing/resources/fake_responses.py new file mode 100644 index 0000000000..cc6aba6efa --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/fake_responses.py @@ -0,0 +1,42 @@ +# /xhr/resources/conditional.py -- to fake a 304 response + +def main(request, response): + if request.method == "OPTIONS": + # Assume this is a CORS preflight + response.headers.set(b"Access-Control-Allow-Headers", "*") + response.headers.set(b"Access-Control-Allow-Origin", "*") + response.status = (204, "No Content") + return b"" + tag = request.GET.first(b"tag", None) + redirect = request.GET.first(b"redirect", None) + match = request.headers.get(b"If-None-Match", None) + date = request.GET.first(b"date", b"") + modified = request.headers.get(b"If-Modified-Since", None) + url = request.GET.first(b"url", None) + response.headers.set(b"Access-Control-Allow-Origin", b"*") + response.headers.set(b"Timing-Allow-Origin", b"*") + if tag: + response.headers.set(b"ETag", b'"%s"' % tag) + elif date: + response.headers.set(b"Last-Modified", date) + if redirect: + response.headers.set(b"Location", redirect) + response.status = (302, b"Moved") + return b"" + + if url: + filename = url.decode('utf-8').split("?")[0] + filepath = "./resource-timing/resources/{}".format(filename) + response.headers.set(b"Content-Type", b"text/javascript") + with open(filepath, 'rb') as f: + filedata = f.read() + + return filedata + + if ((match is not None and match == tag) or + (modified is not None and modified == date)): + response.status = (304, b"SUPERCOOL") + return b"" + else: + response.headers.set(b"Content-Type", b"text/plain") + return b"MAYBE NOT" diff --git a/testing/web-platform/tests/resource-timing/resources/fake_responses_https.sub.html b/testing/web-platform/tests/resource-timing/resources/fake_responses_https.sub.html new file mode 100644 index 0000000000..21f1f02a67 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/fake_responses_https.sub.html @@ -0,0 +1,18 @@ +<body> +<script> +function request() { + var client = new XMLHttpRequest, + baseurl = "https://{{hosts[alt][]}}:{{ports[https][0]}}{{location[pathname]}}", + url = new URL("fake_responses.py", baseurl).href; + client.open("GET", url, false) + client.send(null) + client.open("GET", url, false) + client.send(null) +} + +if(window.parent.setup_iframe) { + window.parent.setup_iframe(); + request(); +} +</script> +</body> diff --git a/testing/web-platform/tests/resource-timing/resources/fake_responses_https_redirect.sub.html b/testing/web-platform/tests/resource-timing/resources/fake_responses_https_redirect.sub.html new file mode 100644 index 0000000000..2ee92b2a55 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/fake_responses_https_redirect.sub.html @@ -0,0 +1,20 @@ +<body> +<script> +function request() { + var client = new XMLHttpRequest, + baseurl = "http://{{hosts[alt][]}}:{{ports[http][0]}}{{location[pathname]}}", + subresource = "fake_responses.py", + redirecturl = new URL(subresource, "https://{{hosts[][www]}}:{{ports[https][0]}}{{location[pathname]}}").href, + url = new URL(subresource + "?redirect=" + redirecturl, baseurl).href; + client.open("GET", url, false) + client.send(null) + client.open("GET", url, false) + client.send(null) +} + +if(window.parent.setup_iframe) { + window.parent.setup_iframe(); + request(); +} +</script> +</body> diff --git a/testing/web-platform/tests/resource-timing/resources/frame-timing.js b/testing/web-platform/tests/resource-timing/resources/frame-timing.js new file mode 100644 index 0000000000..019bd424b5 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/frame-timing.js @@ -0,0 +1,63 @@ +function test_frame_timing_before_load_event(type) { + promise_test(async t => { + const {document, performance} = type === 'frame' ? window.parent : window; + const delay = 500; + const frame = document.createElement(type); + t.add_cleanup(() => frame.remove()); + await new Promise(resolve => { + frame.addEventListener('load', resolve); + frame.src = `/resource-timing/resources/iframe-with-delay.sub.html?delay=${delay}`; + (type === 'frame' ? document.querySelector('frameset') : document.body).appendChild(frame); + }); + + const entries = performance.getEntriesByName(frame.src); + const navigationEntry = frame.contentWindow.performance.getEntriesByType('navigation')[0]; + assert_equals(entries.length, 1); + assert_equals(entries[0].initiatorType, type); + assert_greater_than(performance.now(), entries[0].responseEnd + delay); + const domContentLoadedEventAbsoluteTime = + navigationEntry.domContentLoadedEventStart + + frame.contentWindow.performance.timeOrigin; + const frameResponseEndAbsoluteTime = entries[0].responseEnd + performance.timeOrigin; + assert_greater_than(domContentLoadedEventAbsoluteTime, frameResponseEndAbsoluteTime); + }, `A ${type} should report its RT entry when the response is done and before it is completely loaded`); +} + + +function test_frame_timing_change_src(type, + origin1 = document.origin, + origin2 = document.origin, + tao = false, label = '') { + const uid = token(); + promise_test(async t => { + const {document, performance} = type === 'frame' ? window.parent : window; + const frame = document.createElement(type); + t.add_cleanup(() => frame.remove()); + function createURL(origin) { + const url = new URL(`${origin}/resource-timing/resources/green.html`, location.href); + url.searchParams.set("uid", uid); + if (tao) + url.searchParams.set("pipe", "header(Timing-Allow-Origin, *)"); + return url.toString(); + } + + await new Promise(resolve => { + const done = () => { + resolve(); + frame.removeEventListener('load', done); + } + frame.addEventListener('load', done); + frame.src = createURL(origin1); + const root = type === 'frame' ? document.querySelector('frameset') : document.body; + root.appendChild(frame); + }); + + await new Promise(resolve => { + frame.addEventListener('load', resolve); + frame.src = createURL(origin2); + }); + + const entries = performance.getEntries().filter(e => e.name.includes(uid)); + assert_equals(entries.length, 2); + }, label || `A ${type} should report separate RT entries if its src changed from the outside`); +} diff --git a/testing/web-platform/tests/resource-timing/resources/frameset-timing-frame.html b/testing/web-platform/tests/resource-timing/resources/frameset-timing-frame.html new file mode 100644 index 0000000000..e260f57526 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/frameset-timing-frame.html @@ -0,0 +1,8 @@ +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="./frame-timing.js"></script> + +<script> + test_frame_timing_before_load_event('frame'); + test_frame_timing_change_src('frame'); +</script> diff --git a/testing/web-platform/tests/resource-timing/resources/green-frame.html b/testing/web-platform/tests/resource-timing/resources/green-frame.html new file mode 100644 index 0000000000..9613240ae3 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/green-frame.html @@ -0,0 +1,7 @@ +<html> + <head> + <frameset> + <frame src="green.html?id=frame"> + </frameset> + </head> +</html> diff --git a/testing/web-platform/tests/resource-timing/resources/green.html b/testing/web-platform/tests/resource-timing/resources/green.html new file mode 100644 index 0000000000..b8a1947b77 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/green.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML> +<html> + <head> + <meta content="text/html; charset=utf-8" http-equiv="Content-Type" /> + <title>Green Test Page</title> + </head> + <body style="background-color:#00FF00;"> + <h1>Placeholder</h1> + </body> +</html> diff --git a/testing/web-platform/tests/resource-timing/resources/green.html.headers b/testing/web-platform/tests/resource-timing/resources/green.html.headers new file mode 100644 index 0000000000..cb762eff80 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/green.html.headers @@ -0,0 +1 @@ +Access-Control-Allow-Origin: * diff --git a/testing/web-platform/tests/resource-timing/resources/gzip_xml.py b/testing/web-platform/tests/resource-timing/resources/gzip_xml.py new file mode 100644 index 0000000000..7debc9ce3f --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/gzip_xml.py @@ -0,0 +1,23 @@ +import gzip as gzip_module +import os + +from io import BytesIO + +from wptserve.utils import isomorphic_decode + +def main(request, response): + dir_path = os.path.dirname(os.path.realpath(isomorphic_decode(__file__))) + file_path = os.path.join(dir_path, u'resource_timing_test0.xml') + f = open(file_path, u'rb') + output = f.read() + + out = BytesIO() + with gzip_module.GzipFile(fileobj=out, mode="w") as f: + f.write(output) + output = out.getvalue() + + headers = [(b"Content-type", b"text/plain"), + (b"Content-Encoding", b"gzip"), + (b"Content-Length", len(output))] + + return headers, output diff --git a/testing/web-platform/tests/resource-timing/resources/header-delay.h2.py b/testing/web-platform/tests/resource-timing/resources/header-delay.h2.py new file mode 100644 index 0000000000..27b6fd5bfb --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/header-delay.h2.py @@ -0,0 +1,25 @@ +from time import sleep + +def handle_headers(frame, request, response): + delay = int(request.GET.first(b"delay")) / 1000 + + if b"with100" in request.GET: + sleep(delay) + response.writer.write_raw_header_frame(headers=[(b":status", b"103")], end_headers=True) + + if b"with103" in request.GET: + sleep(delay) + response.writer.write_raw_header_frame(headers=[(b":status", b"103")], end_headers=True) + + sleep(delay) + response.status = 200 + + if b"tao" in request.GET: + response.headers[b"timing-allow-origin"] = "*" + + response.headers[b"content-type"] = "text/plain" + response.headers[b"access-control-allow-origin"] = "*" + response.write_status_headers() + +def main(request, response): + response.writer.write_data(item="Hello World", last=True) diff --git a/testing/web-platform/tests/resource-timing/resources/header-delay.py b/testing/web-platform/tests/resource-timing/resources/header-delay.py new file mode 100644 index 0000000000..631f7855f8 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/header-delay.py @@ -0,0 +1,29 @@ +from time import sleep + +def main(request, response): + delay = int(request.GET.first(b"delay")) / 1000 + + # TODO: make this exported from ResponseWriter + handler = response.writer._handler + if b"with100" in request.GET: + sleep(delay) + handler.send_response(100) + handler.end_headers() + + if b"with103" in request.GET: + sleep(delay) + handler.send_response(103) + handler.send_header("Link", "<resources/empty.js>;rel=preload;as=script") + handler.end_headers() + + sleep(delay) + + handler.send_response(200) + + if b"tao" in request.GET: + handler.send_header("timing-allow-origin", "*") + + handler.send_header("content-type", "text/plain") + handler.send_header("access-control-allow-origin", "*") + handler.end_headers() + handler.wfile.write(bytes("Hello World", "utf8")) diff --git a/testing/web-platform/tests/resource-timing/resources/iframe-TAO-crossorigin-port.sub.html b/testing/web-platform/tests/resource-timing/resources/iframe-TAO-crossorigin-port.sub.html new file mode 100644 index 0000000000..97d77fcc58 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/iframe-TAO-crossorigin-port.sub.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<html> +<body> +<script> + const url = '{{location[scheme]}}://{{host}}:{{ports[http][1]}}/resource-timing/resources/TAOResponse.py?tao=origin_port_{{ports[http][1]}}'; + const observe = (list, observer) => { + const entry = list.getEntries()[0]; + const sum = entry.redirectStart + + entry.redirectEnd + + entry.domainLookupStart + + entry.domainLookupEnd + + entry.connectStart + + entry.connectEnd + + entry.secureConnectionStart + + entry.requestStart + + entry.responseStart + + entry.transferSize + + entry.encodedBodySize + + entry.decodedBodySize; + + const result = sum == 0 ? 'PASS' : 'FAIL'; + window.top.postMessage(result, '*'); + } + let observer = new PerformanceObserver(observe); + observer.observe({ entryTypes: ["resource"] }); + fetch(url).then(r => r.text()); +</script> +</body> +</html> + + diff --git a/testing/web-platform/tests/resource-timing/resources/iframe-load-from-mem-cache-transfer-size.html b/testing/web-platform/tests/resource-timing/resources/iframe-load-from-mem-cache-transfer-size.html new file mode 100644 index 0000000000..6f37a33e8c --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/iframe-load-from-mem-cache-transfer-size.html @@ -0,0 +1,24 @@ +<!DOCTYPE HTML> +<html> + +<head> + <meta charset="utf-8" /> + <title>transfer size of resource timing when loaded from memory cache.</title> +</head> + +<body> + <script> + // This function is called from the test in the parent document. + function getScript(url) { + const script = document.createElement("script"); + const loaded = new Promise(resolve => { + script.onload = script.onerror = resolve; + }); + script.src = url; + document.body.appendChild(script); + return loaded; + } + </script> +</body> + +</html> diff --git a/testing/web-platform/tests/resource-timing/resources/iframe-navigate-back.html b/testing/web-platform/tests/resource-timing/resources/iframe-navigate-back.html new file mode 100644 index 0000000000..f944b633e2 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/iframe-navigate-back.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8" /> +<title>Resource Timing iframe navigate - back button navigation</title> +</head> +<body onunload="/*disable bfcache*/"> +<script src="/common/get-host-info.sub.js"></script> +<script src="nested-contexts.js"></script> +<script> + setup_back_navigation("iframe-navigate-back.html"); +</script> +<iframe id="target"></iframe> +<script> + target.src = pre_navigate_url; +</script> +</body> +</html> diff --git a/testing/web-platform/tests/resource-timing/resources/iframe-navigate.html b/testing/web-platform/tests/resource-timing/resources/iframe-navigate.html new file mode 100644 index 0000000000..0286884021 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/iframe-navigate.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8" /> +<title>Resource Timing iframe navigate</title> +</head> +<body> +<script src="/common/get-host-info.sub.js"></script> +<script src="nested-contexts.js"></script> +<script> + setup_navigate_test(); +</script> +<iframe id="target"></iframe> +<script> + target.src = pre_navigate_url; +</script> +</body> +</html> diff --git a/testing/web-platform/tests/resource-timing/resources/iframe-refresh.html b/testing/web-platform/tests/resource-timing/resources/iframe-refresh.html new file mode 100644 index 0000000000..862b96da9d --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/iframe-refresh.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8" /> +<title>Resource Timing iframe refresh</title> +</head> +<body> +<script src="/common/get-host-info.sub.js"></script> +<script src="nested-contexts.js"></script> +<script> + setup_refresh_test(); +</script> +<iframe id="target"></iframe> +<script> + target.src = pre_refresh_url; +</script> +</body> +</html> diff --git a/testing/web-platform/tests/resource-timing/resources/iframe-reload-TAO.html b/testing/web-platform/tests/resource-timing/resources/iframe-reload-TAO.html new file mode 100644 index 0000000000..461f43bf8e --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/iframe-reload-TAO.html @@ -0,0 +1,25 @@ +<!doctype html> +<html> +<head> +<meta charset="utf-8" /> +<script src="/resources/testharness.js"></script> +<script src="resource-loaders.js"></script> +<script src="entry-invariants.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<body> +<script> + const {HTTP_NOTSAMESITE_ORIGIN} = get_host_info(); + const font_url = `${HTTP_NOTSAMESITE_ORIGIN}/resource-timing/resources/cors-ahem.py`; + if (location.hash === '#check') { + attribute_test( + load.font, font_url, + invariants.assert_tao_pass_no_redirect_http, + "Test that TAO headers are reused on reloads."); + } else { + window.onload = () => { + location.hash = 'check'; + location.reload(); + }; + } +</script> +</body> diff --git a/testing/web-platform/tests/resource-timing/resources/iframe-setdomain.sub.html b/testing/web-platform/tests/resource-timing/resources/iframe-setdomain.sub.html new file mode 100644 index 0000000000..4a2f609aa4 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/iframe-setdomain.sub.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html> +<head> + <title>domain: {{domains[]}}</title> +</head> +<body> + <script> + // The purpose of this IFrame is to change the 'document.domain' + document.domain = "{{domains[]}}"; + </script> + The resource-timings-level1.sub.html test loads this document into an IFrame to vet that setting + 'document.domain' does not effect the timing allowed. +</body> +</html> diff --git a/testing/web-platform/tests/resource-timing/resources/iframe-with-delay.sub.html b/testing/web-platform/tests/resource-timing/resources/iframe-with-delay.sub.html new file mode 100644 index 0000000000..fe50aa7e47 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/iframe-with-delay.sub.html @@ -0,0 +1,3 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<img src="/xhr/resources/delay.py?ms={{GET[delay]}}" /> diff --git a/testing/web-platform/tests/resource-timing/resources/iframe_TAO_match_origin.html b/testing/web-platform/tests/resource-timing/resources/iframe_TAO_match_origin.html new file mode 100644 index 0000000000..cf68aade79 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/iframe_TAO_match_origin.html @@ -0,0 +1,21 @@ +<body> +<script> +function dirname(path) { + return path.replace(/\/[^\/]*$/, '/'); +} + +function request() { + var dirName = dirname(location.href); + var client = new XMLHttpRequest, + // create a cross-origin request + url = dirName.replace('://', '://www.') + 'TAOResponse.py?tao=match_origin'; + client.open("GET", url, false); + client.send(null); +} + +if(window.parent.setup_iframe) { + window.parent.setup_iframe(); + request(); +} +</script> +</body> diff --git a/testing/web-platform/tests/resource-timing/resources/import.sub.css b/testing/web-platform/tests/resource-timing/resources/import.sub.css new file mode 100644 index 0000000000..618c568d2a --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/import.sub.css @@ -0,0 +1 @@ +@import "delay-css.py?delay={{GET[delay]}}"
\ No newline at end of file diff --git a/testing/web-platform/tests/resource-timing/resources/importer.css b/testing/web-platform/tests/resource-timing/resources/importer.css new file mode 100644 index 0000000000..771204cdd1 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/importer.css @@ -0,0 +1 @@ +@import 'empty_style.css?stylesheet-imported'
\ No newline at end of file diff --git a/testing/web-platform/tests/resource-timing/resources/importer.js b/testing/web-platform/tests/resource-timing/resources/importer.js new file mode 100644 index 0000000000..e73d45da29 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/importer.js @@ -0,0 +1,2 @@ +import './fake_responses.py?url=empty_script.js?script-head-import-defer'; +import('./fake_responses.py?url=empty_script.js?script-head-import-defer-dynamic').then(module => {});
\ No newline at end of file diff --git a/testing/web-platform/tests/resource-timing/resources/importer_async.js b/testing/web-platform/tests/resource-timing/resources/importer_async.js new file mode 100644 index 0000000000..4b1cd4ddae --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/importer_async.js @@ -0,0 +1,2 @@ +import './fake_responses.py?url=empty_script.js?script-head-import-async'; +import('./fake_responses.py?url=empty_script.js?script-head-import-async-dynamic').then(module => {});
\ No newline at end of file diff --git a/testing/web-platform/tests/resource-timing/resources/importer_dynamic.css b/testing/web-platform/tests/resource-timing/resources/importer_dynamic.css new file mode 100644 index 0000000000..f0ba069f0c --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/importer_dynamic.css @@ -0,0 +1 @@ +@import 'empty_style.css?stylesheet-imported-dynamic'
\ No newline at end of file diff --git a/testing/web-platform/tests/resource-timing/resources/importer_print.css b/testing/web-platform/tests/resource-timing/resources/importer_print.css new file mode 100644 index 0000000000..aac191635e --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/importer_print.css @@ -0,0 +1 @@ +@import 'empty_style.css?stylesheet-imported-print'
\ No newline at end of file diff --git a/testing/web-platform/tests/resource-timing/resources/inject_resource_test.html b/testing/web-platform/tests/resource-timing/resources/inject_resource_test.html new file mode 100644 index 0000000000..44d09675d3 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/inject_resource_test.html @@ -0,0 +1,7 @@ +<body> +<script> +if(window.parent.setup_iframe) { + window.parent.setup_iframe(); +} +</script> +</body> diff --git a/testing/web-platform/tests/resource-timing/resources/invalid.jpg b/testing/web-platform/tests/resource-timing/resources/invalid.jpg new file mode 100644 index 0000000000..81c545efeb --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/invalid.jpg @@ -0,0 +1 @@ +1234 diff --git a/testing/web-platform/tests/resource-timing/resources/manifest.json b/testing/web-platform/tests/resource-timing/resources/manifest.json new file mode 100644 index 0000000000..e107c044d5 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/manifest.json @@ -0,0 +1,4 @@ +{ + "name": "Dummy manifest", + "start_url": "/start.html" +} diff --git a/testing/web-platform/tests/resource-timing/resources/multi_redirect.py b/testing/web-platform/tests/resource-timing/resources/multi_redirect.py new file mode 100644 index 0000000000..a5fc5db4ac --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/multi_redirect.py @@ -0,0 +1,59 @@ +import urllib.parse +from wptserve.utils import isomorphic_encode + +def main(request, response): + """Handler that causes multiple redirections. Redirect chain is as follows: + 1. Initial URL containing multi-redirect.py + 2. Redirect to cross-origin URL + 3. Redirect to same-origin URL + 4. Final URL containing the final same-origin resource. + Mandatory parameters: + page_origin - The page origin, used for redirection and to set TAO. This is a mandatory parameter. + cross_origin - The cross origin used to make this a cross-origin redirect. This is a mandatory parameter. + final_resource - Path of the final resource, without origin. This is a mandatory parameter. + Optional parameters: + tao_steps - Number of redirects for which the TAO header will be present (a number 0 - 3 makes the most sense). Default value is 0. + tao_value - The value of the TAO header, when present. Default value is "*". + Note that |step| is a parameter used internally for the multi-redirect. It's the step we're at in the redirect chain. + """ + step = 1 + if b"step" in request.GET: + try: + step = int(request.GET.first(b"step")) + except ValueError: + pass + + page_origin = request.GET.first(b"page_origin") + cross_origin = request.GET.first(b"cross_origin") + final_resource = request.GET.first(b"final_resource") + + tao_value = b"*" + if b"tao_value" in request.GET: + tao_value = request.GET.first(b"tao_value") + tao_steps = 0 + if b"tao_steps" in request.GET: + tao_steps = int(request.GET.first(b"tao_steps")) + + next_tao_steps = tao_steps - 1 + redirect_url_path = b"/resource-timing/resources/multi_redirect.py?" + redirect_url_path += b"page_origin=" + page_origin + redirect_url_path += b"&cross_origin=" + cross_origin + redirect_url_path += b"&final_resource=" + urllib.parse.quote(final_resource).encode('ascii') + redirect_url_path += b"&tao_value=" + tao_value + redirect_url_path += b"&tao_steps=" + isomorphic_encode(str(next_tao_steps)) + redirect_url_path += b"&step=" + if tao_steps > 0: + response.headers.set(b"timing-allow-origin", tao_value) + + if step == 1: + # On the first request, redirect to a cross origin URL + redirect_url = cross_origin + redirect_url_path + b"2" + elif step == 2: + # On the second request, redirect to a same origin URL + redirect_url = page_origin + redirect_url_path + b"3" + else: + # On the third request, redirect to a static response + redirect_url = page_origin + final_resource + + response.status = 302 + response.headers.set(b"Location", redirect_url) diff --git a/testing/web-platform/tests/resource-timing/resources/navigate_back.html b/testing/web-platform/tests/resource-timing/resources/navigate_back.html new file mode 100644 index 0000000000..345eee1fcc --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/navigate_back.html @@ -0,0 +1,7 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8" /> +<script> +window.onload = function() {history.back();} +</script> diff --git a/testing/web-platform/tests/resource-timing/resources/nested-contexts.js b/testing/web-platform/tests/resource-timing/resources/nested-contexts.js new file mode 100644 index 0000000000..31337ae5da --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/nested-contexts.js @@ -0,0 +1,87 @@ +let destination = location; + +if (location.search == "?cross-site") { + const https = destination.protocol.startsWith("https"); + destination = get_host_info()[https ? 'HTTPS_NOTSAMESITE_ORIGIN' : 'HTTP_NOTSAMESITE_ORIGIN']; +} else if (location.search == "?crossorigin") { + destination = get_host_info().REMOTE_ORIGIN; +} + +const pre_navigate_url = + new URL("/resource-timing/resources/document-that-navigates.html", + destination).href; +const post_navigate_url = + new URL("/resource-timing/resources/document-navigated.html", + destination).href; +const pre_refresh_url = + new URL("/resource-timing/resources/document-that-refreshes.html", + destination).href; +const post_refresh_url = + new URL("/resource-timing/resources/document-refreshed.html", + destination).href; + +const setup_navigate_or_refresh = (type, pre, post) => { + const verify_document_navigate_not_observable = () => { + if (performance.getEntriesByName(post).length) { + opener.postMessage(`FAIL - ${type} document should not be observable`, + `*`); + + } + + opener.postMessage("PASS", "*"); + } + window.addEventListener("message", e => { + if (e.data == type) { + verify_document_navigate_not_observable(); + } + }); +} + +const setup_navigate_test = () => { + setup_navigate_or_refresh("navigated", pre_navigate_url, post_navigate_url); +} + +const setup_refresh_test = () => { + setup_navigate_or_refresh("refreshed", pre_refresh_url, post_refresh_url); +} + +const setup_back_navigation = pushed_url => { + const verify_document_navigate_not_observable = navigated_back => { + if (performance.getEntriesByName(post_navigate_url).length) { + opener.postMessage("FAIL - navigated document exposed", "*"); + } + if (navigated_back) { + opener.postMessage("PASS", "*"); + } + } + window.addEventListener("message", e => { + if (e.data == "navigated") { + verify_document_navigate_not_observable(sessionStorage.navigated); + if (sessionStorage.navigated) { + delete sessionStorage.navigated; + } else { + sessionStorage.navigated = true; + setTimeout(() => { + history.pushState({}, "", pushed_url); + location.href="navigate_back.html"; + }, 0); + } + } + }); +} + +const open_test_window = (url, message) => { + promise_test(() => { + return new Promise((resolve, reject) => { + const openee = window.open(url); + addEventListener("message", e => { + openee.close(); + if (e.data == "PASS") { + resolve(); + } else { + reject(e.data); + } + }); + }); + }, message); +} diff --git a/testing/web-platform/tests/resource-timing/resources/nested.css b/testing/web-platform/tests/resource-timing/resources/nested.css new file mode 100644 index 0000000000..90d61b04ac --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/nested.css @@ -0,0 +1,10 @@ +@import "resource_timing_test0.css?id=n1"; + +@font-face { + font-family: remoteFont; + src: url('/fonts/Ahem.ttf?id=n1'); +} +ol { + font-family: remoteFont; + list-style-image: url('blue.png?id=n1'); +} diff --git a/testing/web-platform/tests/resource-timing/resources/no-entries-for-cross-origin-css-fetched-memory-cache-iframe.sub.html b/testing/web-platform/tests/resource-timing/resources/no-entries-for-cross-origin-css-fetched-memory-cache-iframe.sub.html new file mode 100644 index 0000000000..f47913468b --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/no-entries-for-cross-origin-css-fetched-memory-cache-iframe.sub.html @@ -0,0 +1,8 @@ +<!DOCTYPE HTML> +<html> +<head> +<link rel="stylesheet" href="http://{{hosts[][www1]}}:{{ports[http][1]}}/resource-timing/resources/nested.css"> +</head> +<body> +<ol>Some content</ol> +</body> diff --git a/testing/web-platform/tests/resource-timing/resources/notify_parent.html b/testing/web-platform/tests/resource-timing/resources/notify_parent.html new file mode 100644 index 0000000000..c104f3c8f0 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/notify_parent.html @@ -0,0 +1,4 @@ +<!DOCTYPE html> +<script> +window.parent.subFrameLoaded(); +</script> diff --git a/testing/web-platform/tests/resource-timing/resources/object-navigate-back.html b/testing/web-platform/tests/resource-timing/resources/object-navigate-back.html new file mode 100644 index 0000000000..a746947818 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/object-navigate-back.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8" /> +<title>Resource Timing object navigate - back button navigation</title> +</head> +<body onunload="/*disable bfcache*/"> +<script src="/common/get-host-info.sub.js"></script> +<script src="nested-contexts.js"></script> +<script> + setup_back_navigation("object-navigate-back.html"); +</script> +<object id="target" type="text/html"></object> +<script> + target.data = pre_navigate_url; +</script> +</body> +</html> diff --git a/testing/web-platform/tests/resource-timing/resources/object-navigate.html b/testing/web-platform/tests/resource-timing/resources/object-navigate.html new file mode 100644 index 0000000000..6b4bb3128e --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/object-navigate.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8" /> +<title>Resource Timing object navigate</title> +</head> +<body> +<script src="/common/get-host-info.sub.js"></script> +<script src="nested-contexts.js"></script> +<script> + setup_navigate_test(); +</script> +<object id="target" type="text/html"></object> +<script> + target.data = pre_navigate_url; +</script> +</body> +</html> diff --git a/testing/web-platform/tests/resource-timing/resources/object-refresh.html b/testing/web-platform/tests/resource-timing/resources/object-refresh.html new file mode 100644 index 0000000000..5c5f60fb06 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/object-refresh.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8" /> +<title>Resource Timing object refresh</title> +</head> +<body> +<script src="/common/get-host-info.sub.js"></script> +<script src="nested-contexts.js"></script> +<script> + setup_refresh_test(); +</script> +<object id="target" type="text/html"></object> +<script> + target.data = pre_refresh_url; +</script> +</body> +</html> diff --git a/testing/web-platform/tests/resource-timing/resources/observe-entry.js b/testing/web-platform/tests/resource-timing/resources/observe-entry.js new file mode 100644 index 0000000000..260b5929a5 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/observe-entry.js @@ -0,0 +1,25 @@ +// Given a resource name, returns a promise that will resolve to the +// corresponding PerformanceResourceTiming entry. The promise will reject, +// however, if the PerformanceResourceTiming entry isn't observed within ~2 +// seconds (scaled according to WPT timeout scaling). +const observe_entry = entry_name => { + const entry = new Promise(resolve => { + new PerformanceObserver((entry_list, observer) => { + for (const entry of entry_list.getEntries()) { + if (entry.name.endsWith(entry_name)) { + resolve(entry); + observer.disconnect(); + return; + } + } + }).observe({"type": "resource", "buffered": true}); + }); + const timeout = new Promise((resolve, reject) => { + step_timeout(() => { + reject(new Error("observe_entry: timeout")); + }, 2000); + }); + // If the entry isn't observed within 2 seconds, assume it will never show + // up. + return Promise.race([entry, timeout]); +}; diff --git a/testing/web-platform/tests/resource-timing/resources/preflight.py b/testing/web-platform/tests/resource-timing/resources/preflight.py new file mode 100644 index 0000000000..f0f6017b47 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/preflight.py @@ -0,0 +1,9 @@ +def main(request, response): + response.headers.set(b"Access-Control-Allow-Origin", b"*") + response.headers.set(b"Access-Control-Max-Age", b"0") + response.headers.set(b"Timing-Allow-Origin", b"*") + # If this script is accessed with the header X-Require-Preflight then the + # browser will send a preflight request. Otherwise it won't. + if request.method == u'OPTIONS': + response.headers.set(b"Access-Control-Allow-Headers", + b"X-Require-Preflight") diff --git a/testing/web-platform/tests/resource-timing/resources/redirect-cors.py b/testing/web-platform/tests/resource-timing/resources/redirect-cors.py new file mode 100644 index 0000000000..655b862fcd --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/redirect-cors.py @@ -0,0 +1,22 @@ +def main(request, response): + if b"allow_origin" in request.GET: + response.headers.set(b"Access-Control-Allow-Origin", + request.GET.first(b"allow_origin")) + + if b"timing_allow_origin" in request.GET: + response.headers.set(b"Timing-Allow-Origin", + request.GET.first(b"timing_allow_origin")) + + # Handle CORS-preflights of non-simple requests. + if request.method == 'OPTIONS': + response.status = 204 + requested_method = request.headers.get(b"Access-Control-Request-Method") + if requested_method: + response.headers.set(b"Access-Control-Allow-Methods", requested_method) + requested_headers = request.headers.get(b"Access-Control-Request-Headers") + if requested_headers: + response.headers.set(b"Access-Control-Allow-Headers", requested_headers) + else: + location = request.GET.first(b"location") + response.status = 302 + response.headers.set(b"Location", location) diff --git a/testing/web-platform/tests/resource-timing/resources/redirect-without-location.py b/testing/web-platform/tests/resource-timing/resources/redirect-without-location.py new file mode 100644 index 0000000000..eef1df858b --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/redirect-without-location.py @@ -0,0 +1,2 @@ +def main(request, response): + response.status = 302 diff --git a/testing/web-platform/tests/resource-timing/resources/resource-loaders.js b/testing/web-platform/tests/resource-timing/resources/resource-loaders.js new file mode 100644 index 0000000000..37fea16b17 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/resource-loaders.js @@ -0,0 +1,174 @@ +const load = { + + cache_bust: path => { + let url = new URL(path, location.origin); + url.href += (url.href.includes("?")) ? '&' : '?'; + // The `Number` type in Javascript, when interpreted as an integer, can only + // safely represent [-2^53 + 1, 2^53 - 1] without the loss of precision [1]. + // We do not generate a global value and increment from it, as the increment + // might not have enough precision to be reflected. + // + // [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number + url.href += "unique=" + Math.random().toString().substring(2); + return url.href; + }, + + image_with_attrs: async (path, attribute_map) => { + return new Promise(resolve => { + const img = new Image(); + for (const key in attribute_map) + img[key] = attribute_map[key]; + img.onload = img.onerror = resolve; + img.src = load.cache_bust(path); + }); + }, + + // Returns a promise that settles once the given path has been fetched as an + // image resource. + image: path => { + return load.image_with_attrs(path, undefined); + }, + + // Returns a promise that settles once the given path has been fetched as an + // image resource. + image_cors: path => load.image_with_attrs(path, {crossOrigin: "anonymous"}), + + // Returns a promise that settles once the given path has been fetched as a + // font resource. + font: path => { + const div = document.createElement('div'); + div.innerHTML = ` + <style> + @font-face { + font-family: ahem; + src: url('${load.cache_bust(path)}'); + } + </style> + <div style="font-family: ahem;">This fetches ahem font.</div> + `; + document.body.appendChild(div); + return document.fonts.ready.then(() => { + document.body.removeChild(div); + }); + }, + + stylesheet_with_attrs: async (path, attribute_map) => { + const link = document.createElement("link"); + if (attribute_map instanceof Object) { + for (const [key, value] of Object.entries(attribute_map)) { + link[key] = value; + } + } + link.rel = "stylesheet"; + link.type = "text/css"; + link.href = load.cache_bust(path); + + const loaded = new Promise(resolve => { + link.onload = link.onerror = resolve; + }); + + document.head.appendChild(link); + await loaded; + document.head.removeChild(link); + }, + + // Returns a promise that settles once the given path has been fetched as a + // stylesheet resource. + stylesheet: async path => { + return load.stylesheet_with_attrs(path, undefined); + }, + + iframe_with_attrs: async (path, attribute_map, validator, skip_wait_for_navigation) => { + const frame = document.createElement("iframe"); + if (attribute_map instanceof Object) { + for (const [key, value] of Object.entries(attribute_map)) { + frame[key] = value; + } + } + const loaded = new Promise(resolve => { + frame.onload = frame.onerror = resolve; + }); + frame.src = load.cache_bust(path); + document.body.appendChild(frame); + if ( !skip_wait_for_navigation ) { + await loaded; + } + if (validator instanceof Function) { + validator(frame); + } + // since we skipped the wait for load animation, we cannot + // remove the iframe here since the request could get cancelled + if ( !skip_wait_for_navigation ) { + document.body.removeChild(frame); + } + }, + + // Returns a promise that settles once the given path has been fetched as an + // iframe. + iframe: async (path, validator) => { + return load.iframe_with_attrs(path, undefined, validator); + }, + + script_with_attrs: async (path, attribute_map) => { + const script = document.createElement("script"); + if (attribute_map instanceof Object) { + for (const [key, value] of Object.entries(attribute_map)) { + script[key] = value; + } + } + const loaded = new Promise(resolve => { + script.onload = script.onerror = resolve; + }); + script.src = load.cache_bust(path); + document.body.appendChild(script); + await loaded; + document.body.removeChild(script); + }, + + // Returns a promise that settles once the given path has been fetched as a + // script. + script: async path => { + return load.script_with_attrs(path, undefined); + }, + + // Returns a promise that settles once the given path has been fetched as an + // object. + object: async (path, type) => { + const object = document.createElement("object"); + const object_load_settled = new Promise(resolve => { + object.onload = object.onerror = resolve; + }); + object.data = load.cache_bust(path); + if (type) { + object.type = type; + } + document.body.appendChild(object); + await await_with_timeout(2000, + "Timeout was reached before load or error events fired", + object_load_settled, + () => { document.body.removeChild(object) } + ); + }, + + // Returns a promise that settles once the given path has been fetched + // through a synchronous XMLHttpRequest. + xhr_sync: async (path, headers) => { + const xhr = new XMLHttpRequest; + xhr.open("GET", path, /* async = */ false); + if (headers instanceof Object) { + for (const [key, value] of Object.entries(headers)) { + xhr.setRequestHeader(key, value); + } + } + xhr.send(); + }, + + xhr_async: path => { + const xhr = new XMLHttpRequest(); + xhr.open("GET", path) + xhr.send(); + return new Promise(resolve => { + xhr.onload = xhr.onerror = resolve; + }); + } +}; diff --git a/testing/web-platform/tests/resource-timing/resources/resource-timing-content-length.py b/testing/web-platform/tests/resource-timing/resources/resource-timing-content-length.py new file mode 100644 index 0000000000..687689e272 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/resource-timing-content-length.py @@ -0,0 +1,20 @@ +def main(request, response): + content = request.GET.first(b"content") + length = request.GET.first(b"length").decode("ascii") + response.add_required_headers = False + + output = b"HTTP/1.1 200 OK\r\n" + output += b"Content-Type: text/plain;charset=UTF-8\r\n" + output += b"Connection: close\r\n" + if length == b"auto" : + output += b"Content-Length:" + output += "{0}".format(len(content)).encode("ascii") + output += b"\r\n" + elif length != b"none" : + output += b"Content-Length:" + output += "{0}".format(length).encode("ascii") + output += b"\r\n" + output += b"\r\n" + output += content + response.writer.write(output) + response.close_connection = True diff --git a/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.css b/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.css new file mode 100644 index 0000000000..8bc8326ba6 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.css @@ -0,0 +1,4 @@ +div#resource_link_css +{ + color:hotpink; +}
\ No newline at end of file diff --git a/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.html b/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.html new file mode 100644 index 0000000000..167c65c5c4 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html> + <head> + <title> + Child Frame + </title> + </head> + <body style="background-color: #C0C0C0"> + + <h1> + Child Document + </h1> + + </body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.js b/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.js new file mode 100644 index 0000000000..cf1c1df392 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.js @@ -0,0 +1,3 @@ +// This is a test script for purposes of testing the +// script initiator type in the Resource Timing feature +var testDummyValue = 0; diff --git a/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.js.headers b/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.js.headers new file mode 100644 index 0000000000..308bee94d0 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.js.headers @@ -0,0 +1,2 @@ +Content-Type: text/javascript +Cache-Control: max-age=36000 diff --git a/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.png b/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.png Binary files differnew file mode 100644 index 0000000000..be211bc377 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.png diff --git a/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.xml b/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.xml new file mode 100644 index 0000000000..91cd676be6 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/resource_timing_test0.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<TESTDATA> + <ITEM> + <DATA>Test XML Data</DATA> + </ITEM> +</TESTDATA> diff --git a/testing/web-platform/tests/resource-timing/resources/self_navigation.html b/testing/web-platform/tests/resource-timing/resources/self_navigation.html new file mode 100644 index 0000000000..beb12f5da5 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/self_navigation.html @@ -0,0 +1 @@ +<meta http-equiv="refresh" content="0;url=notify_parent.html?redirected"> diff --git a/testing/web-platform/tests/resource-timing/resources/shared-worker.js b/testing/web-platform/tests/resource-timing/resources/shared-worker.js new file mode 100644 index 0000000000..f3ef3feb96 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/shared-worker.js @@ -0,0 +1,3 @@ +self.onconnect = e => { + e.ports[0].postMessage(performance.timeOrigin); +}
\ No newline at end of file diff --git a/testing/web-platform/tests/resource-timing/resources/sizes-helper.js b/testing/web-platform/tests/resource-timing/resources/sizes-helper.js new file mode 100644 index 0000000000..86336686b3 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/sizes-helper.js @@ -0,0 +1,16 @@ +// Header size is a fixed constant. +// https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-transfersize +const headerSize = 300; + +const cacheBustUrl = url => { + return url + '&unique=' + Math.random().toString().substring(2); +} + +const checkSizeFields = (entry, bodySize, transferSize) => { + assert_equals(entry.decodedBodySize, bodySize, + 'decodedBodySize'); + assert_equals(entry.encodedBodySize, bodySize, + 'encodedBodySize'); + assert_equals(entry.transferSize, transferSize, + 'transferSize'); +} diff --git a/testing/web-platform/tests/resource-timing/resources/status-code.py b/testing/web-platform/tests/resource-timing/resources/status-code.py new file mode 100644 index 0000000000..1c6a021633 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/status-code.py @@ -0,0 +1,8 @@ +def main(request, response): + status = request.GET.first(b'status') + response.status = (status, b""); + if b'tao_value' in request.GET: + response.headers.set(b'timing-allow-origin', request.GET.first(b'tao_value')) + if b'allow_origin' in request.GET: + response.headers.set(b'access-control-allow-origin', request.GET.first(b'allow_origin')) + diff --git a/testing/web-platform/tests/resource-timing/resources/sw-install.html b/testing/web-platform/tests/resource-timing/resources/sw-install.html new file mode 100644 index 0000000000..3d1407e831 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/sw-install.html @@ -0,0 +1,58 @@ +<!DOCTYPE html> +<script> +const service_worker_unregister = async scope => { + const absoluteScope = new URL(scope, window.location).href; + const registration = await navigator.serviceWorker.getRegistration(scope); + if (registration && registration.scope === absoluteScope) { + return registration.unregister(); + } +}; + +const service_worker_reregister = async (url, scope) => { + if (!scope || scope.length == 0) { + return Promise.reject(new Error('tests must define a scope')); + } + + await service_worker_unregister(scope); + return navigator.serviceWorker.register(url, {'scope': scope}); +}; + +const wait_for_state_activated = worker => { + if (worker.state === 'activated') + return Promise.resolve(); + + if (worker.state === 'redundant') { + return Promise.reject(new Error('worker is redundant')); + } + + return new Promise(function(resolve) { + worker.addEventListener('statechange', () => { + if (worker.state === 'activated') { + resolve(); + } + }); + }); +}; + +(async () => { + const script = '/resource-timing/resources/sw.js'; + const scope = '/resource-timing/resources/'; + const registration = await service_worker_reregister(script, scope); + await wait_for_state_activated(registration.installing); + + const opener = window.opener; + if (!opener) { + return; + } + + opener.postMessage("installed", "*"); + window.addEventListener("message", async e => { + if (e.data === "unregister") { + await registration.unregister(); + opener.postMessage("unregistered", "*"); + } + }); + +})(); + +</script> diff --git a/testing/web-platform/tests/resource-timing/resources/sw.js b/testing/web-platform/tests/resource-timing/resources/sw.js new file mode 100644 index 0000000000..4e4fe1e1f0 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/sw.js @@ -0,0 +1,3 @@ +self.addEventListener('fetch', function(event) { + event.respondWith(fetch(event.request)); +}); diff --git a/testing/web-platform/tests/resource-timing/resources/tao-response.js b/testing/web-platform/tests/resource-timing/resources/tao-response.js new file mode 100644 index 0000000000..2194c5d43a --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/tao-response.js @@ -0,0 +1,13 @@ +const tao_response = (tao_value, base_url) => { + const payload = { + 'headers': { + 'Timing-Allow-Origin': tao_value + } + }; + return custom_cors_response(payload, base_url); +}; + +const remote_tao_response = tao_value => { + const {REMOTE_ORIGIN} = get_host_info(); + return tao_response(tao_value, REMOTE_ORIGIN); +}; diff --git a/testing/web-platform/tests/resource-timing/resources/webperftestharnessextension.js b/testing/web-platform/tests/resource-timing/resources/webperftestharnessextension.js new file mode 100644 index 0000000000..dc02c075b3 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/webperftestharnessextension.js @@ -0,0 +1,188 @@ +// +// Helper functions for Resource Timing tests +// + +var mark_names = [ + '', + '1', + 'abc', +]; + +var measures = [ + [''], + ['2', 1], + ['aaa', 'navigationStart', ''], +]; + +function test_method_exists(method, method_name, properties) +{ + var msg; + if (typeof method === 'function') + msg = 'performance.' + method.name + ' is supported!'; + else + msg = 'performance.' + method_name + ' is supported!'; + wp_test(function() { assert_equals(typeof method, 'function', msg); }, msg, properties); +} + +function test_noless_than(value, greater_than, msg, properties) +{ + wp_test(function () { assert_true(value >= greater_than, msg); }, msg, properties); +} + +function test_fail(msg, properties) +{ + wp_test(function() { assert_unreached(); }, msg, properties); +} + +function test_resource_entries(entries, expected_entries) +{ + test(function() { + // This is slightly convoluted so that we can sort the output. + var actual_entries = {}; + var origin = window.location.protocol + "//" + window.location.host; + + for (var i = 0; i < entries.length; ++i) { + var entry = entries[i]; + var found = false; + for (var expected_entry in expected_entries) { + if (entry.name == origin + expected_entry) { + found = true; + if (expected_entry in actual_entries) { + assert_unreached(expected_entry + ' is not expected to have duplicate entries'); + } + actual_entries[expected_entry] = entry; + break; + } + } + if (!found) { + assert_unreached(entries[i].name + ' is not expected to be in the Resource Timing buffer'); + } + } + + sorted_urls = []; + for (var i in actual_entries) { + sorted_urls.push(i); + } + sorted_urls.sort(); + for (var i in sorted_urls) { + var url = sorted_urls[i]; + assert_equals(actual_entries[url].initiatorType, + expected_entries[url], + origin + url + ' is expected to have initiatorType ' + expected_entries[url]); + } + for (var j in expected_entries) { + if (!(j in actual_entries)) { + assert_unreached(origin + j + ' is expected to be in the Resource Timing buffer'); + } + } + }, "Testing resource entries"); +} + +function performance_entrylist_checker(type) +{ + var entryType = type; + + function entry_check(entry, expectedNames) + { + var msg = 'Entry \"' + entry.name + '\" should be one that we have set.'; + wp_test(function() { assert_in_array(entry.name, expectedNames, msg); }, msg); + test_equals(entry.entryType, entryType, 'entryType should be \"' + entryType + '\".'); + if (type === "measure") { + test_true(isFinite(entry.startTime), 'startTime should be a number.'); + test_true(isFinite(entry.duration), 'duration should be a number.'); + } else if (type === "mark") { + test_greater_than(entry.startTime, 0, 'startTime should greater than 0.'); + test_equals(entry.duration, 0, 'duration of mark should be 0.'); + } + } + + function entrylist_order_check(entryList) + { + var inOrder = true; + for (var i = 0; i < entryList.length - 1; ++i) + { + if (entryList[i + 1].startTime < entryList[i].startTime) { + inOrder = false; + break; + } + } + return inOrder; + } + + function entrylist_check(entryList, expectedLength, expectedNames) + { + test_equals(entryList.length, expectedLength, 'There should be ' + expectedLength + ' entries.'); + test_true(entrylist_order_check(entryList), 'Entries in entrylist should be in order.'); + for (var i = 0; i < entryList.length; ++i) + { + entry_check(entryList[i], expectedNames); + } + } + + return{"entrylist_check":entrylist_check}; +} + +function PerformanceContext(context) +{ + this.performanceContext = context; +} + +PerformanceContext.prototype = { + initialMeasures: function(item, index, array) + { + this.performanceContext.measure.apply(this.performanceContext, item); + }, + + mark: function() + { + this.performanceContext.mark.apply(this.performanceContext, arguments); + }, + + measure: function() + { + this.performanceContext.measure.apply(this.performanceContext, arguments); + }, + + clearMarks: function() + { + this.performanceContext.clearMarks.apply(this.performanceContext, arguments); + + }, + + clearMeasures: function() + { + this.performanceContext.clearMeasures.apply(this.performanceContext, arguments); + + }, + + getEntries: function() + { + return this.performanceContext.getEntries.apply(this.performanceContext, arguments); + }, + + getEntriesByType: function() + { + return this.performanceContext.getEntriesByType.apply(this.performanceContext, arguments); + }, + + getEntriesByName: function() + { + return this.performanceContext.getEntriesByName.apply(this.performanceContext, arguments); + }, + + setResourceTimingBufferSize: function() + { + return this.performanceContext.setResourceTimingBufferSize.apply(this.performanceContext, arguments); + }, + + registerResourceTimingBufferFullCallback: function(func) + { + this.performanceContext.onresourcetimingbufferfull = func; + }, + + clearResourceTimings: function() + { + this.performanceContext.clearResourceTimings.apply(this.performanceContext, arguments); + } + +}; diff --git a/testing/web-platform/tests/resource-timing/resources/worker_with_images.js b/testing/web-platform/tests/resource-timing/resources/worker_with_images.js new file mode 100644 index 0000000000..1fa4893201 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/worker_with_images.js @@ -0,0 +1,22 @@ +let numComplete = 0; + +function checkDone() { + ++numComplete; + if (numComplete == 2) { + const numEntries = performance.getEntries().length; + postMessage(numEntries); + } +} + +function makeRequest(request) { + var xhr = new XMLHttpRequest; + xhr.open('get', request, true); + xhr.onreadystatechange = function() { + if (xhr.readyState == 4) { + checkDone(); + } + } + xhr.send(); +} +makeRequest('blue.png'); +makeRequest('resource_timing_test0.png'); |