chrome-devtools-frontend
Version:
Chrome DevTools UI
102 lines (84 loc) • 4.38 kB
text/typescript
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as Common from '../../core/common/common.js';
import type * as SDK from '../../core/sdk/sdk.js';
const resourceTypeToAsAttribute = new Map<Common.ResourceType.ResourceType, string>([
[Common.ResourceType.resourceTypes.Document, 'document'],
[Common.ResourceType.resourceTypes.Stylesheet, 'style'],
[Common.ResourceType.resourceTypes.Image, 'image'],
[Common.ResourceType.resourceTypes.Font, 'font'],
[Common.ResourceType.resourceTypes.Script, 'script'],
[Common.ResourceType.resourceTypes.TextTrack, 'track'],
[Common.ResourceType.resourceTypes.Manifest, 'manifest'],
[Common.ResourceType.resourceTypes.Fetch, 'fetch'],
[Common.ResourceType.resourceTypes.XHR, 'fetch'],
[Common.ResourceType.resourceTypes.Wasm, 'fetch'],
]);
/**
* Escapes HTML special characters.
* Crucial for URLs in href attributes to prevent characters like '&' from being
* interpreted as HTML entities (e.g., '©' becoming '©').
*/
function escapeHTML(str: string): string {
return str.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
function parsePreloadLinkOrigins(request: SDK.NetworkRequest.NetworkRequest):
{requestOrigin: string|null, documentOrigin: string|null} {
const requestOrigin = request.parsedURL.securityOrigin();
const documentURL = request.documentURL;
const documentOrigin =
documentURL ? (Common.ParsedURL.ParsedURL.fromString(documentURL)?.securityOrigin() ?? null) : null;
return {requestOrigin, documentOrigin};
}
function determinePreloadLinkHref(request: SDK.NetworkRequest.NetworkRequest, isSameOrigin: boolean): string {
// For same-origin resources, use root-relative URL for environment portability.
if (isSameOrigin && request.parsedURL.isValid) {
return request.parsedURL.path + (request.parsedURL.queryParams ? '?' + request.parsedURL.queryParams : '');
}
return request.url();
}
function determinePreloadLinkCrossOrigin(request: SDK.NetworkRequest.NetworkRequest, isCrossOrigin: boolean): string {
const resourceType = request.resourceType();
const asValue = resourceTypeToAsAttribute.get(resourceType);
const isFont = resourceType === Common.ResourceType.resourceTypes.Font;
const isFetch = asValue === 'fetch';
const secFetchMode = request.requestHeaderValue('sec-fetch-mode')?.toLowerCase();
const needsCrossOrigin = isFont || isFetch || secFetchMode === 'cors';
if (!needsCrossOrigin) {
return '';
}
const hasCredentials = request.includedRequestCookies().length > 0 ||
Boolean(request.requestHeaderValue('cookie') || request.requestHeaderValue('authorization'));
// Only use credentials mode if it is cross-origin AND has credentials.
// Same-origin CORS (anonymous) automatically sends credentials.
const useCredentials = isCrossOrigin && hasCredentials;
return useCredentials ? ' crossorigin="use-credentials"' : ' crossorigin';
}
/**
* Determines whether a network request can be preloaded.
*/
export function canPreloadRequest(request: SDK.NetworkRequest.NetworkRequest): boolean {
return resourceTypeToAsAttribute.has(request.resourceType());
}
/**
* Generates an HTML `<link rel="preload">` element string for a given network request.
*/
export function generatePreloadLink(request: SDK.NetworkRequest.NetworkRequest): string {
const {requestOrigin, documentOrigin} = parsePreloadLinkOrigins(request);
const isSameOrigin = Boolean(requestOrigin && documentOrigin && requestOrigin === documentOrigin);
const isCrossOrigin = Boolean(requestOrigin && documentOrigin && requestOrigin !== documentOrigin);
const url = determinePreloadLinkHref(request, isSameOrigin);
const escapedUrl = escapeHTML(url);
const resourceType = request.resourceType();
const escapedMimeType = request.mimeType ? escapeHTML(request.mimeType) : '';
const asValue = resourceTypeToAsAttribute.get(resourceType);
const asAttr = `as="${asValue ?? 'fetch'}"`;
const crossoriginAttr = determinePreloadLinkCrossOrigin(request, isCrossOrigin);
const typeAttr = escapedMimeType ? ` type="${escapedMimeType}"` : '';
return `<link rel="preload" href="${escapedUrl}" ${asAttr}${typeAttr}${crossoriginAttr}>`;
}