// The WebSocket shim tag, put into guest app documents so it runs before any app // script: as the first child of
, or before the first tag when there is no // tag (the parser then opens head for it). Comments, doctypes and the contents // of script and style elements are skipped while looking. The document's bytes are // otherwise untouched; the scan reads them as latin1, one character per byte, so any // ASCII-compatible charset survives. import { WS_SHIM_PATH } from "../shared/vurls"; const TAG = new TextEncoder().encode(``); const RAW_TEXT = new Set(["script", "style"]); /** Where the shim tag goes in `html`. */ export function shimOffset(html: string): number { let firstTag = -1; let at = 0; while ((at = html.indexOf("<", at)) !== -1) { if (html.startsWith("", at + 4); if (end === -1) break; at = end + 3; continue; } const m = /^<\/?([A-Za-z][^\s/>]*)/.exec(html.slice(at, at + 64)); if (!m) { // , ...>, or a stray "<" in text. at += 1; continue; } const close = html.indexOf(">", at); if (close === -1) break; if (firstTag === -1) firstTag = at; const name = m[1]!.toLowerCase(); const isEnd = html[at + 1] === "/"; if (!isEnd && name === "head") return close + 1; if (!isEnd && RAW_TEXT.has(name)) { const end = html.toLowerCase().indexOf(`${name}`, close); at = end === -1 ? html.length : end; continue; } at = close + 1; } return firstTag === -1 ? html.length : firstTag; } export function injectShim(body: Uint8Array): Uint8Array { const at = shimOffset(new TextDecoder("latin1").decode(body)); const out = new Uint8Array(body.length + TAG.length); out.set(body.subarray(0, at)); out.set(TAG, at); out.set(body.subarray(at), at + TAG.length); return out; } /** The HTML patterns of the MIME Sniffing standard's rules for an unknown type: a body * with no content-type that starts like this is rendered as HTML. */ const SNIFFED_HTML = /^[\t\n\f\r ]*<(?:!doctype html|html|head|script|iframe|h1|div|font|table|a|style|title|b|body|br|p|!--)[ >]/i; /** Only a document runs scripts, and only an uncompressed HTML body can be edited. */ export function wantsShim(mode: RequestMode, headers: Headers, body: Uint8Array): boolean { const encoding = headers.get("content-encoding"); if (mode !== "navigate" || (encoding !== null && encoding.toLowerCase() !== "identity")) return false; const type = headers.get("content-type"); if (type !== null) return /^text\/html\b/i.test(type); return SNIFFED_HTML.test(new TextDecoder("latin1").decode(body.subarray(0, 512))); }