Skip to content

Commit

Permalink
Bug 1810501 [wpt PR 37984] - Resource Timing: Expose firstInterimResp…
Browse files Browse the repository at this point in the history
…onseStart, a=testonly

Automatic update from web-platform-tests
Resource Timing: Expose firstInterimResponseStart

This adds an entry to PerformanceResourceTiming:
- firstInterimResponseStart: the time of the first early-hints header

It also changes the meaning of responseStart to be the first
non-informational header (non-103).

Implemented for Quic, Spdy and HTTP.

All behind a feature runtime flag (ResourceTimingInterimResponseTimes)

Spec issue: w3c/resource-timing#345

Bug: 1402089
Change-Id: I2f050788515959e3576f3cf2bd8df13ff848090a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4165825
Reviewed-by: Bence Béky <[email protected]>
Commit-Queue: Noam Rosenthal <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1094571}

--

wpt-commits: e75f154bb894b0e2bf78cf1ac04e1cbecedebdc6
wpt-pr: 37984
  • Loading branch information
noamr authored and moz-wptsync-bot committed Feb 1, 2023
1 parent 77ab243 commit 25efc7d
Show file tree
Hide file tree
Showing 7 changed files with 223 additions and 41 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta name="timeout" content="long">
<script src="/common/utils.js"></script>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<script>
promise_test(async t => {
const iframe = document.createElement("iframe");
const params = new URLSearchParams();
const delays = [200, 100, 150];
params.set("delay1", delays[0]);
params.set("delay2", delays[1]);

iframe.src = `resources/early-hints-delay.h2.py?${params.toString()}`;
document.body.appendChild(iframe);
t.add_cleanup(() => iframe.remove());
await new Promise(resolve => iframe.addEventListener("load", resolve));
const [entry] = iframe.contentWindow.performance.getEntriesByType("navigation");
assert_greater_than(entry.firstInterimResponseStart, entry.requestStart + delays[0]);
assert_greater_than(entry.responseStart, entry.firstInterimResponseStart + delays[1]);
}, `Interim response times should correspond to delays (h2)`);
</script>
</body>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import time

def handle_headers(frame, request, response):
early_hints = [
(b":status", b"103"),
(b"link", b"</empty.js>; rel=preload; as=script"),
]

time.sleep(int(request.GET.first(b"delay1")) / 1000)
response.writer.write_raw_header_frame(headers=early_hints,
end_headers=True)

time.sleep(int(request.GET.first(b"delay2")) / 1000)
response.status = 200
response.headers[b"content-type"] = "text/html"
response.write_status_headers()


def main(request, response):
response.writer.write_data(item="Hello", last=True)
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Resource Timing: PerformanceResourceTiming interim resource times</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<script src="/common/utils.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
const {REMOTE_HOST} = get_host_info();
function interim_response_time_test({origin, tao, with103, expected}) {
promise_test(async t => {
const delay = 100;
const url = new URL('/resource-timing/resources/header-delay.h2.py',
origin == "same-origin" ?
location.href :
`${location.protocol}//${REMOTE_HOST}:${location.port}`);
url.searchParams.set("delay", delay);
if (tao)
url.searchParams.set("tao", "*");
if (with103)
url.searchParams.set("with103", "true");
const response = await fetch(url.toString(), {mode: "cors"});
assert_equals(response.status, 200)
await response.text();
const [entry] = performance.getEntriesByName(url.toString());
if (expected) {
assert_greater_than(entry.firstInterimResponseStart,
entry.requestStart + delay * 2,
"firstInterimResponseStart");
assert_greater_than(entry.responseStart,
entry.firstInterimResponseStart + delay,
"responseStart");
} else {
assert_equals(entry.firstInterimResponseStart, 0);
}

assert_equals(entry.toJSON().firstInterimResponseStart,
entry.firstInterimResponseStart);
}, `Fetch from ${origin} ${with103 ? "with" : "without"} early hints, ${
tao ? "with" : "without"} Timing-Allow-Origin should ${
expected ? "expose" : "not expose"} interim response times`);
}

interim_response_time_test(
{origin: "same-origin", tao: false, with103: true, expected: true});

// TAO should protect firstInterimResponseStart
interim_response_time_test(
{origin: "cross-origin", tao: true, with103: true, expected: true});
interim_response_time_test(
{origin: "cross-origin", tao: false, with103: true, expected: false});

// Without early hints, firstInterimResponseStart should be 0 regalrdss of protections.
interim_response_time_test(
{origin: "same-origin", tao: false, with103: false, expected: false});
interim_response_time_test(
{origin: "cross-origin", tao: true, with103: false, expected: false});

</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Resource Timing: PerformanceResourceTiming interim resource times</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<script src="/common/utils.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
const {REMOTE_ORIGIN} = get_host_info();
function interim_response_time_test({origin, tao, with103, expected}) {
promise_test(async t => {
const delay = 100;
const url = new URL('/resource-timing/resources/header-delay.py',
origin == "same-origin" ? location.href : REMOTE_ORIGIN);
url.searchParams.set("delay", delay);
if (tao)
url.searchParams.set("tao", "*");
if (with103)
url.searchParams.set("with103", "true");
const response = await fetch(url.toString(), {mode: "cors"});
assert_equals(response.status, 200)
await response.text();
const [entry] = performance.getEntriesByName(url.toString());
if (expected) {
assert_greater_than(entry.firstInterimResponseStart,
entry.requestStart + delay * 2,
"firstInterimResponseStart");
assert_greater_than(entry.responseStart,
entry.firstInterimResponseStart + delay,
"responseStart");
} else {
assert_equals(entry.firstInterimResponseStart, 0);
}

assert_equals(entry.toJSON().firstInterimResponseStart,
entry.firstInterimResponseStart);
}, `Fetch from ${origin} ${with103 ? "with" : "without"} early hints, ${
tao ? "with" : "without"} Timing-Allow-Origin should ${
expected ? "expose" : "not expose"} interim response times`);
}

interim_response_time_test(
{origin: "same-origin", tao: false, with103: true, expected: true});

// TAO should protect firstInterimResponseStart
interim_response_time_test(
{origin: "cross-origin", tao: true, with103: true, expected: true});
interim_response_time_test(
{origin: "cross-origin", tao: false, with103: true, expected: false});

// Without early hints, firstInterimResponseStart should be 0 regalrdss of protections.
interim_response_time_test(
{origin: "same-origin", tao: false, with103: false, expected: false});
interim_response_time_test(
{origin: "cross-origin", tao: true, with103: false, expected: false});

</script>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -235,47 +235,6 @@ window.onload =
});
});

// Test that responseStart uses the timing of 1XX responses by
// synthesizing a delay between a 100 and 200 status, and verifying that
// this delay is included before responseEnd. If the delay is not
// included, this implies that the 200 status line was (incorrectly) used
// for responseStart timing, despite the 100 response arriving earlier.
//
// Source: "In the case where more than one response is available for a
// request, due to an Informational 1xx response, the reported
// responseStart value is that of the first response to the last
// request."
[
{ initiator: "iframe", response: "(done)", mime: mimeHtml },
{ initiator: "xmlhttprequest", response: "(done)", mime: mimeText },
{ initiator: "script", response: '"";', mime: mimeScript },
{ initiator: "link", response: ".unused{}", mime: mimeCss },
]
.forEach(function (template) {
testCases.push({
description: "'" + template.initiator + " responseStart uses 1XX (first) response timings'",
test: function (test) {
initiateFetch(
test,
template.initiator,
getSyntheticUrl("status:100"
+ "&flush"
+ "&" + serverStepDelay + "ms"
+ "&status:200"
+ "&mime:" + template.mime
+ "&send:" + encodeURIComponent(template.response)),
function (initiator, entry) {
assert_greater_than_equal(
entry.responseEnd,
entry.responseStart + serverStepDelay,
"HTTP/1.1 1XX (first) response should determine 'responseStart' timing.");

test.done();
});
}
});
});

// Function to run the next case in the queue.
var currentTestIndex = -1;
function runNextCase() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from time import sleep

def handle_headers(frame, request, response):
delay = int(request.GET.first(b"delay")) / 1000
sleep(delay)
response.writer.write_raw_header_frame(headers=[(b":status", b"100")], end_headers=True)
sleep(delay)

if b"with103" in request.GET:
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)
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
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
sleep(delay)
handler.send_response(100)
handler.end_headers()
sleep(delay)

if b"with103" in request.GET:
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"))

0 comments on commit 25efc7d

Please sign in to comment.