Skip to content

Commit

Permalink
[Resource Timing] Align transferSize to spec
Browse files Browse the repository at this point in the history
Aligning transferSize to the spec means that response header sizes will
no longer be directly exposed, even for same origin resources or
resources with Timing-Allow-Origin headers.
This is because it seems unsafe[1] to expose header sizes directly.

It also means that redirect chains won't count towards the size exposed
in `transferSize`. While we could specify and accumulate redirect body
sizes, it seems like we'd be better off reporting redirects directly[2]
in the future, in case it becomes a priority.

PSA: https://groups.google.com/a/chromium.org/g/blink-dev/c/Sny4FO5_y5E

[1] w3c/resource-timing#238
[2] https://w3c.github.io/web-performance/meetings/2021/2021-03-18/index.html

Bug: 1185801
Change-Id: Iba071c6bb74c5522e3f1ed2586f082f11a7a68a0
  • Loading branch information
Yoav Weiss authored and chromium-wpt-export-bot committed May 14, 2021
1 parent 2474396 commit 717b855
Show file tree
Hide file tree
Showing 9 changed files with 229 additions and 5 deletions.
1 change: 1 addition & 0 deletions lint.ignore
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,7 @@ SET TIMEOUT: css/CSS2/selectors/dom-hover-002.xht
SET TIMEOUT: css/CSS2/tables/tables-102.xht
SET TIMEOUT: css/mediaqueries/min-width-tables-001.html
SET TIMEOUT: css/css-text/crashtests/rendering-rtl-bidi-override-crash.html
SET TIMEOUT: resource-timing/resources/run-async-tasks-promise.js

## Build system stuff
CSS-COLLIDING-SUPPORT-NAME: css/*/README
Expand Down
18 changes: 14 additions & 4 deletions resource-timing/resources/TAOResponse.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import os

def main(request, response):
origin = request.headers[b'origin']
response.headers.set(b'Access-Control-Allow-Origin', origin)
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
Expand Down Expand Up @@ -50,5 +54,11 @@ def main(request, response):
else:
pass
response.status = 200
response.headers.set(b"Content-Type", b"text/plain")
response.content = "TEST"
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"
11 changes: 11 additions & 0 deletions resource-timing/resources/cacheable-and-validated.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
def main(request, response):
revalidation = 'Cache-Control' in request.headers
content = request.GET.first(b'content')
response.headers.set(b'Cache-Control', b'max-age=60')
response.headers.set(b'ETag', b'assdfsdfe')
if revalidation:
response.status = (304, b'NotModified')
else:
response.status = (200, b'OK');
response.write_status_headers()
response.writer.write(content);
10 changes: 10 additions & 0 deletions resource-timing/resources/redirect-cors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
def main(request, response):
location = request.GET.first(b"location")
response.status = 302
response.headers.set(b"Location", location)

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"))
4 changes: 3 additions & 1 deletion resource-timing/resources/resource-loaders.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
const load = {
_cache_bust_value: Math.random().toString().substr(2),

cache_bust: path => {
let url = new URL(path, location.origin);
url.hash += `cache_bust=${load._cache_bust_value++}`;
url.href += (url.href.includes("?")) ? '&' : '?';
url.href += "unique=" + load._cache_bust_value++
return url.href;
},

Expand Down
16 changes: 16 additions & 0 deletions resource-timing/resources/sizes-helper.js
Original file line number Diff line number Diff line change
@@ -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');
}
55 changes: 55 additions & 0 deletions resource-timing/sizes-cache.any.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// META: global=window,worker
// META: script=/resource-timing/resources/sizes-helper.js
// META: script=/resource-timing/resources/resource-loaders.js

let url = new URL(
'/resource-timing/resources/cacheable-and-validated.py' +
'?content=loremipsumblablabla',
location.href).href;
const bodySize = 19;

const accumulateEntry = () => {
return new Promise(resolve => {
const po = new PerformanceObserver(list => {
resolve(list);
});
po.observe({type: "resource", buffered: true});
});
};

const checkResourceSizes = list => {
const entries = list.getEntriesByName(url);
assert_equals(entries.length, 3, 'Wrong number of entries');
let seenCount = 0;
for (let entry of entries) {
if (seenCount === 0) {
// 200 response
checkSizeFields(entry, bodySize, bodySize + headerSize);
} else if (seenCount === 1) {
// from cache
checkSizeFields(entry, bodySize, 0);
} else if (seenCount === 2) {
// 304 response
checkSizeFields(entry, bodySize, headerSize);
} else {
assert_unreached('Too many matching entries');
}
++seenCount;
}
};

promise_test(() => {
// Use a different URL every time so that the cache behaviour does not
// depend on execution order.
url = load.cache_bust(url);
const eatBody = response => response.arrayBuffer();
const mustRevalidate = {headers: {'Cache-Control': 'max-age=0'}};
return fetch(url)
.then(eatBody)
.then(() => fetch(url))
.then(eatBody)
.then(() => fetch(url, mustRevalidate))
.then(eatBody)
.then(accumulateEntry)
.then(checkResourceSizes);
}, 'PerformanceResourceTiming sizes caching test');
57 changes: 57 additions & 0 deletions resource-timing/sizes-redirect-img.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="resources/sizes-helper.js"></script>
<script src="resources/resource-loaders.js"></script>
<script src="resources/entry-invariants.js"></script>
<script>
// Redirects for fetch() always apply CORS rules, whereas normal resources
// don't, so this test covers extra code paths beyond those covered by
// resource-timing-sizes-redirect.html.

const baseUrl = new URL('/resource-timing/resources/TAOResponse.py?tao=wildcard&img=true', location.href).href;

const expectedSize = 1010;

const hostInfo = get_host_info();

const redirectUrl = (redirectSourceOrigin, targetUrl) => {
return redirectSourceOrigin +
'/resource-timing/resources/redirect-cors.py?timing_allow_origin=*' +
'&location=' + encodeURIComponent(targetUrl);
};

const verify_entry = entry => {
assert_equals(entry.transferSize, expectedSize + headerSize);
};

attribute_test(load.image, baseUrl,
verify_entry,
"PerformanceResourceTiming sizes redirect image - direct URL");

attribute_test(load.image,
redirectUrl(hostInfo.HTTP_ORIGIN, baseUrl),
verify_entry,
"PerformanceResourceTiming sizes redirect image - same origin redirect");

attribute_test(load.image,
redirectUrl(hostInfo.HTTP_REMOTE_ORIGIN, baseUrl),
verify_entry,
"PerformanceResourceTiming sizes redirect image - cross origin redirect");

attribute_test(load.image,
redirectUrl(hostInfo.HTTP_REMOTE_ORIGIN,
redirectUrl(hostInfo.HTTP_ORIGIN, baseUrl)),
verify_entry,
"PerformanceResourceTiming sizes redirect image - cross origin to same origin redirect");

attribute_test(load.image,
redirectUrl(hostInfo.HTTP_ORIGIN,
redirectUrl(hostInfo.HTTP_REMOTE_ORIGIN,
redirectUrl(hostInfo.HTTP_ORIGIN,
baseUrl))),
verify_entry,
"PerformanceResourceTiming sizes redirect image - same origin to remote " +
"origin to same origin redirect");
</script>
62 changes: 62 additions & 0 deletions resource-timing/sizes-redirect.any.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// META: global=window,worker
// META: script=/common/get-host-info.sub.js
// META: script=/resource-timing/resources/sizes-helper.js

const baseUrl =
new URL('/resource-timing/resources/TAOResponse.py?tao=wildcard', location.href).href;
const expectedSize = 4;

const hostInfo = get_host_info();
performance.clearResourceTimings();

const accumulateEntry = () => {
return new Promise(resolve => {
const po = new PerformanceObserver(list => {
resolve(list);
});
po.observe({type: "resource", buffered: true});
});
};

const checkResourceSizes = () => {
const entries = performance.getEntriesByType('resource');
for (let entry of entries) {
checkSizeFields(entry, expectedSize, expectedSize + headerSize);
}
}

const redirectUrl = (redirectSourceOrigin, allowOrigin, targetUrl) => {
return redirectSourceOrigin +
'/resource-timing/resources/redirect-cors.py?allow_origin=' +
encodeURIComponent(allowOrigin) +
'&timing_allow_origin=*' +
'&location=' + encodeURIComponent(targetUrl);
}

promise_test(() => {
// Use a different URL every time so that the cache behaviour does not
// depend on execution order.
const directUrl = cacheBustUrl(baseUrl);
const sameOriginRedirect = redirectUrl(hostInfo.ORIGIN, '*', directUrl);
const crossOriginRedirect = redirectUrl(hostInfo.REMOTE_ORIGIN,
hostInfo.ORIGIN, directUrl);
const mixedRedirect = redirectUrl(hostInfo.REMOTE_ORIGIN,
hostInfo.ORIGIN, sameOriginRedirect);
const complexRedirect = redirectUrl(hostInfo.ORIGIN,
hostInfo.REMOTE_ORIGIN, mixedRedirect);
let eatBody = response => response.arrayBuffer();
return fetch(directUrl)
.then(eatBody)
.then(() => fetch(sameOriginRedirect))
.then(eatBody)
.then(() => fetch(crossOriginRedirect))
.then(eatBody)
.then(() => fetch(mixedRedirect))
.then(eatBody)
.then(() => fetch(complexRedirect))
.then(eatBody)
.then(accumulateEntry)
.then(checkResourceSizes);
}, 'PerformanceResourceTiming sizes Fetch with redirect test');

done();

0 comments on commit 717b855

Please sign in to comment.