Files
ycode/scripts/rea/patches/CdpElectronStyles.js

176 lines
7.0 KiB
JavaScript

import { digestCanonicalValue } from "../domain/canonicalDigest.js";
import { createWebTextArtifact } from "../domain/webContentArtifact.js";
import { authorizedElectronFile } from "./ElectronFileScope.js";
import { compareUnicodeCodePoints } from "../domain/unicodeCodePointOrder.js";
const MAX_SHEETS = 1024;
const MAX_SHEET_BYTES = 8 * 1024 * 1024;
const MAX_TOTAL_BYTES = 64 * 1024 * 1024;
/** Retain only stylesheet lifecycle metadata from the selected target session. */
export const ingestElectronStyleEvent = (event, stylesMap) => {
if (event?.method === "CSS.styleSheetRemoved") {
const styleSheetId = stringValue(recordValue(event.params)?.styleSheetId);
if (styleSheetId !== undefined)
stylesMap.delete(styleSheetId);
return;
}
if (event?.method !== "CSS.styleSheetAdded")
return;
const header = recordValue(recordValue(event.params)?.header);
const styleSheetId = stringValue(header?.styleSheetId);
if (styleSheetId === undefined || styleSheetId === "")
return;
stylesMap.set(styleSheetId, {
styleSheetId,
frameId: stringValue(header?.frameId) ?? null,
sourceURL: stringValue(header?.sourceURL) ?? null,
origin: stringValue(header?.origin) ?? null,
isInline: header?.isInline === true,
isConstructed: header?.isConstructed === true,
});
};
/** Capture CSS text only for authorized file frames and explicit source opt-in. */
export const captureElectronStyles = async (input) => {
const styles = [...input.styles.values()];
const items = [];
let excluded = 0;
let sourceFailures = 0;
let truncated = false;
let includedBytes = 0;
let sourceRequests = 0;
const limitationNotes = new Set();
void input.completeness;
limitationNotes.add("Stylesheet inventory includes only CSS.styleSheetAdded events still present at the capture snapshot.");
for (const style of styles) {
const scoped = await authorizedStyle(style, input.frameIds);
if (scoped === undefined) {
excluded += 1;
continue;
}
if (input.request?.include_style_sources !== true)
continue;
const identity = {
style_sheet_id: style.styleSheetId,
frame_id: scoped.frameId,
file_path: scoped.filePath,
origin: style.origin,
is_inline: style.isInline,
is_constructed: style.isConstructed,
};
const item = {
style_sheet_key: `electron_stylesheet_${digestCanonicalValue(identity, "CDP capture")}`,
style_sheet_id: style.styleSheetId,
frame_id: scoped.frameId,
file_path: scoped.filePath,
origin: style.origin,
is_inline: style.isInline,
is_constructed: style.isConstructed,
source: { included: false, reason: "CSS source was not requested" },
};
if (sourceRequests >= MAX_SHEETS) {
sourceFailures += 1;
truncated = true;
item.source = { included: false, reason: "stylesheet source count limit reached" };
limitationNotes.add(`CSS source capture reached the ${String(MAX_SHEETS)} stylesheet limit.`);
items.push(item);
continue;
}
sourceRequests += 1;
let sourceText;
try {
const result = await input.connection.send(
"CSS.getStyleSheetText",
{ styleSheetId: style.styleSheetId },
input.sessionId,
input.signal
);
sourceText = stringValue(recordValue(result)?.text);
}
catch (cause) {
if (input.signal?.aborted === true)
throw cause;
sourceFailures += 1;
item.source = { included: false, reason: "CSS.getStyleSheetText failed" };
limitationNotes.add("CSS text could not be retrieved for one or more authorized stylesheets.");
items.push(item);
continue;
}
if (sourceText === undefined) {
sourceFailures += 1;
item.source = { included: false, reason: "CSS.getStyleSheetText returned invalid text" };
limitationNotes.add("CSS text was invalid or absent for one or more authorized stylesheets.");
items.push(item);
continue;
}
const bytes = Buffer.byteLength(sourceText, "utf8");
if (bytes > MAX_SHEET_BYTES) {
sourceFailures += 1;
truncated = true;
item.source = { included: false, reason: "stylesheet source exceeds the per-stylesheet byte limit" };
limitationNotes.add(`CSS source capture omitted text larger than ${String(MAX_SHEET_BYTES)} bytes per stylesheet.`);
items.push(item);
continue;
}
if (includedBytes + bytes > MAX_TOTAL_BYTES) {
sourceFailures += 1;
truncated = true;
item.source = { included: false, reason: "aggregate stylesheet source byte limit reached" };
limitationNotes.add(`CSS source capture omitted text after reaching the ${String(MAX_TOTAL_BYTES)} byte aggregate limit.`);
items.push(item);
continue;
}
includedBytes += bytes;
item.source = { included: true, artifact: createWebTextArtifact(sourceText, "text/css") };
items.push(item);
}
if (input.request?.include_style_sources !== true)
limitationNotes.add("Stylesheet source capture was not selected; no CSS.getStyleSheetText requests were sent.");
if (excluded > 0)
limitationNotes.add(`${String(excluded)} stylesheet(s) were excluded because their origin or frame was outside the authorized local scope.`);
if (sourceFailures > 0)
limitationNotes.add(`${String(sourceFailures)} stylesheet source(s) failed retrieval or exceeded a source budget.`);
input.limitations.push(...limitationNotes);
return {
total: styles.length,
items: items.sort((left, right) => compareUnicodeCodePoints(left.style_sheet_key, right.style_sheet_key)),
excluded,
source_failures: sourceFailures,
truncated,
limits: {
max_sheets: MAX_SHEETS,
max_sheet_bytes: MAX_SHEET_BYTES,
max_total_bytes: MAX_TOTAL_BYTES,
},
};
};
const authorizedStyle = async (style, frameIds) => {
if (style.frameId === null || !frameIds.has(style.frameId) || style.origin !== "regular")
return undefined;
const sourceURL = style.sourceURL ?? "";
if (sourceURL === "") {
if (!style.isInline && !style.isConstructed)
return undefined;
return { frameId: style.frameId, filePath: null };
}
const filePath = await authorizedElectronFile(sourceURL);
return filePath === undefined ? undefined : { frameId: style.frameId, filePath };
};
const recordValue = (value) => typeof value === "object" && value !== null && !Array.isArray(value)
? value
: undefined;
const stringValue = (value) => typeof value === "string" ? value : undefined;