Skip to content

Commit f1844fa

Browse files
committed
Add HTTP Header speculation rules
1 parent 57829a0 commit f1844fa

File tree

1 file changed

+52
-10
lines changed

1 file changed

+52
-10
lines changed

dist/performance.js

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
const response_bodies = $WPT_BODIES;
44
const script_response_bodies = $WPT_BODIES.filter(body => body.type === 'Script');
55

6+
function fetchWithTimeout(url) {
7+
var controller = new AbortController();
8+
setTimeout(() => {controller.abort()}, 5000);
9+
return fetch(url, {signal: controller.signal});
10+
}
11+
612
function getRawHtmlDocument() {
713
let rawHtml;
814
if (response_bodies.length > 0) {
@@ -324,17 +330,53 @@ function getLcpResponseObject(lcpUrl) {
324330
return responseObject;
325331
}
326332

327-
function getSpeculationRules() {
328-
return Array.from(document.querySelectorAll('script[type=speculationrules]')).map(script => {
329-
try {
330-
return JSON.parse(script.innerHTML);
331-
} catch (error) {
332-
return null;
333-
}
334-
});
333+
function getParameterCaseInsensitive(object, key) {
334+
return object[Object.keys(object).find(k => k.toLowerCase() === key.toLowerCase())];
335+
}
336+
337+
async function getSpeculationRules() {
338+
// Get rules from the HTML
339+
const htmlRules = Array.from(document.querySelectorAll('script[type=speculationrules]')).map(script => {
340+
try {
341+
return JSON.parse(script.innerHTML);
342+
} catch (error) {
343+
return null;
344+
}
345+
});
346+
347+
// Get rules from the HTTP requests
348+
const documentRequests = [$WPT_REQUESTS.find( req => req.url === document.location.href)] || [];
349+
const httpRules = await Promise.all(documentRequests.map(async request => {
350+
try {
351+
const speculationRulesHeaders = getParameterCaseInsensitive(request.response_headers, 'Speculation-Rules');
352+
if (speculationRulesHeaders) {
353+
return await Promise.all(speculationRulesHeaders.split(',').map(async (speculationRuleLocation) => {
354+
try {
355+
let url = decodeURI(speculationRuleLocation).slice(1, -1);
356+
if (url.startsWith('/')) {
357+
url = document.location.origin + url;
358+
} else if (!url.startsWith('http')) {
359+
url = document.location.href + url;
360+
}
361+
const response = await fetchWithTimeout(url);
362+
const body = await response.text();
363+
return {url: speculationRuleLocation, rule: JSON.parse(body)};
364+
} catch(error) {
365+
return {error};
366+
}
367+
}));
368+
} else {
369+
return [];
370+
}
371+
} catch(error) {
372+
return {error};
373+
};
374+
}));
375+
376+
return {htmlRules: htmlRules, httpHeaderRules: httpRules};
335377
}
336378

337-
return Promise.all([getLcpElement()]).then(([lcp_elem_stats]) => {
379+
return Promise.all([getLcpElement(), getSpeculationRules()]).then(([lcp_elem_stats, speculation_rules]) => {
338380
const lcpUrl = lcp_elem_stats.url;
339381
const rawDoc = getRawHtmlDocument();
340382
// Start out with true, only if LCP element is an external resource will we eval & potentially set to false.
@@ -366,7 +408,7 @@ return Promise.all([getLcpElement()]).then(([lcp_elem_stats]) => {
366408
lcp_preload: lcpPreload,
367409
web_vitals_js: getWebVitalsJS(),
368410
gaming_metrics: gamingMetrics,
369-
speculation_rules: getSpeculationRules(),
411+
speculation_rules: speculation_rules,
370412
};
371413
}).catch(error => {
372414
return {error};

0 commit comments

Comments
 (0)