Add content-type to PerformanceResourceTiming
This CL introduces a contentType field to Performance Resource
Timing object. This field is behind a Runtime Enabled Flag.
Resource Timing PR : https://github.com/w3c/resource-timing/pull/341
Fetch PR : https://github.com/whatwg/fetch/pull/1481
Bug: 1366706
Change-Id: If4c92ef96d74e3ddbb71420ac61dbea4bfa1163b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3916841
Reviewed-by: Yoav Weiss <yoavweiss@chromium.org>
Reviewed-by: Mike West <mkwst@chromium.org>
Commit-Queue: Abin Paul <abin.paul1@gmail.com>
Cr-Commit-Position: refs/heads/main@{#1063289}
diff --git a/resource-timing/content-type.html b/resource-timing/content-type.html
new file mode 100644
index 0000000..f6b1db7
--- /dev/null
+++ b/resource-timing/content-type.html
@@ -0,0 +1,117 @@
+<!DOCTYPE html>
+<head>
+<meta charset="utf-8" />
+<title>This test validates the content-type of resources.</title>
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/entry-invariants.js"></script>
+<script src="resources/resource-loaders.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+</head>
+<body>
+<script>
+const {ORIGIN, REMOTE_ORIGIN} = get_host_info();
+const SAME_ORIGIN = location.origin;
+
+
+// Content-type for same origin resources is exposed.
+const run_test = (loader, contentType) => {
+ let path = `/resource-timing/resources/content-type.py?content_type=${contentType}`;
+ const url = new URL(path, ORIGIN);
+ attribute_test(
+ loader, url,
+ entry => {
+ assert_equals(entry.contentType, contentType,
+ `content-type for ${entry.name} should be ${contentType}`);
+ });
+}
+
+// Content-type is empty string when a no-cors request is made for cross
+// origin resource.
+// Content-type is empty for cross origin iframes.
+const run_test_cross_origin = (loader, contentType) => {
+ let path = `/resource-timing/resources/content-type.py?content_type=${contentType}`;
+ const url = new URL(path, REMOTE_ORIGIN);
+ attribute_test(
+ loader, url,
+ entry => {
+ assert_equals(entry.contentType, "",
+ `content-type for ${entry.name} should be ""`);
+ });
+}
+
+const resource_loaders_and_types = [
+ [load.font, ["font/woff", "font/otf"]],
+ [load.image, ["image/png", "image/jpg"]],
+ [load.script, ["application/javascript", "text/javascript"]],
+ [load.stylesheet, ["text/css"]],
+ [load.xhr_async, ["application/x-gzip", "application/pdf"]],
+ [load.iframe, ["text/html"]]
+];
+
+resource_loaders_and_types.forEach(resource => {
+ let loader = resource[0];
+ let content_types = resource[1];
+ content_types.forEach(type => {
+ run_test(loader, type);
+ run_test_cross_origin(loader, type);
+ })
+});
+
+
+// Content-type is exposed for cors request for cross-origin resources.
+const run_test_cross_origin_allow_origin = (loader_with_attr,contentType) => {
+ let path = `/resource-timing/resources/content-type.py?content_type=${contentType}&allow_origin=${ORIGIN}`;
+ const url = new URL(path, REMOTE_ORIGIN);
+ loader_with_crossOrigin_attr = async url => {
+ return loader_with_attr(url, {"crossOrigin": "anonymous"});
+ }
+ attribute_test(
+ loader_with_crossOrigin_attr, url,
+ entry => {
+ assert_equals(entry.contentType, contentType,
+ `content-type for ${entry.name} should be ${contentType}`);
+ });
+}
+
+const resource_loaders_with_attrs_and_types = [
+ [load.image_with_attrs, ["image/gif", "image/jpeg"]],
+ [load.script_with_attrs, ["application/javascript", "text/javascript"]],
+ [load.stylesheet_with_attrs, ["text/css"]],
+]
+
+resource_loaders_with_attrs_and_types.forEach(resource => {
+ let loader = resource[0];
+ let content_types = resource[1];
+ content_types.forEach(type => {
+ run_test_cross_origin_allow_origin(loader, type);
+ })
+});
+
+// Content-type for iframes is empty when cross origin redirects are present.
+var destUrl = `${SAME_ORIGIN}/resource-timing/resources/multi_redirect.py?`;
+destUrl += `page_origin=${SAME_ORIGIN}`;
+destUrl += `&cross_origin=${REMOTE_ORIGIN}`;
+destUrl += `&final_resource=/resource-timing/resources/content-type.py?content_type=text/html`;
+attribute_test(
+ load.iframe, new URL(destUrl),
+ entry => {
+ assert_equals(entry.contentType, "",
+ `content-type should be empty for iframes having cross origin redirects`);
+});
+
+
+// Content-type for iframes is exposed for same origin redirects.
+var destUrl = `${SAME_ORIGIN}/resource-timing/resources/redirect-cors.py`;
+destUrl += `?location=${SAME_ORIGIN}/resource-timing/resources/content-type.py?content_type=text/html`;
+attribute_test(
+ load.iframe, new URL(destUrl),
+ entry => {
+ assert_equals(entry.contentType, "text/html",
+ `content-type should be exposed for iframes having only same origin redirects`);
+});
+
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/resource-timing/resources/content-type.py b/resource-timing/resources/content-type.py
new file mode 100644
index 0000000..23a4f0d
--- /dev/null
+++ b/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/resource-timing/tojson.html b/resource-timing/tojson.html
index 19da9c0..2564b85 100644
--- a/resource-timing/tojson.html
+++ b/resource-timing/tojson.html
@@ -49,6 +49,7 @@
'decodedBodySize',
'renderBlockingStatus',
'responseStatus',
+ 'contentType',
];
for (const key of performanceResourceTimingKeys) {
try {