import assert from 'node:assert/strict' import { createHash } from 'node:crypto' import { mkdtemp, realpath, rm, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join, resolve } from 'node:path' import { fileURLToPath, pathToFileURL } from 'node:url' const projectRoot = resolve(fileURLToPath(new URL('../..', import.meta.url))) const runtimeModule = join(projectRoot, '.tmp/rea-css-runtime/dist/browser/CdpElectronStyles.js') const { captureElectronStyles, ingestElectronStyleEvent } = await import(pathToFileURL(runtimeModule)) const authorizedFrameIds = new Set(['frame-main']) const noCompletenessWrites = { exclude() { assert.fail('CSS capture must not write into the old completeness sections') } } const limitations = [] const styles = new Map() const addStyle = (styleSheetId, header) => ingestElectronStyleEvent({ method: 'CSS.styleSheetAdded', params: { header: { styleSheetId, ...header } } }, styles) const removeStyle = styleSheetId => ingestElectronStyleEvent({ method: 'CSS.styleSheetRemoved', params: { styleSheetId } }, styles) const fixtureRoot = await mkdtemp(join(tmpdir(), 'rea-css-fixtures-')) const cssFile = join(fixtureRoot, 'authorized.css') await writeFile(cssFile, 'body { color: red; }\n') const cssUrl = pathToFileURL(cssFile).href addStyle('file-style', { frameId: 'frame-main', sourceURL: cssUrl, origin: 'regular' }) addStyle('inline-style', { frameId: 'frame-main', sourceURL: '', origin: 'regular', isInline: true }) addStyle('constructed-style', { frameId: 'frame-main', sourceURL: '', origin: 'regular', isConstructed: true }) addStyle('remote-style', { frameId: 'frame-main', sourceURL: 'https://example.invalid/site.css', origin: 'regular' }) addStyle('other-frame-style', { frameId: 'frame-other', sourceURL: cssUrl, origin: 'regular' }) addStyle('user-agent-style', { frameId: 'frame-main', sourceURL: cssUrl, origin: 'user-agent' }) addStyle('injected-style', { frameId: 'frame-main', sourceURL: cssUrl, origin: 'injected' }) addStyle('inspector-style', { frameId: 'frame-main', sourceURL: cssUrl, origin: 'inspector' }) addStyle('unknown-origin-style', { frameId: 'frame-main', sourceURL: cssUrl, origin: 'unknown' }) addStyle('unattributed-style', { frameId: 'frame-main', sourceURL: '', origin: 'regular' }) addStyle('removed-style', { frameId: 'frame-main', sourceURL: cssUrl, origin: 'regular' }) removeStyle('removed-style') assert.equal(styles.has('removed-style'), false) removeStyle('missing-style') const bodyTexts = new Map([ ['file-style', 'body { color: red; }\n'], ['inline-style', '.inline { display: block; }'], ['constructed-style', '.constructed { display: grid; }'] ]) const calls = [] const connection = { async send(method, params, sessionId, signal) { calls.push({ method, params, sessionId, signal }) if (method !== 'CSS.getStyleSheetText') throw new Error(`Unexpected command: ${method}`) return { text: bodyTexts.get(params.styleSheetId) } } } const included = await captureElectronStyles({ connection, sessionId: 'private-session-1', signal: undefined, styles, frameIds: authorizedFrameIds, request: { include_style_sources: true }, completeness: noCompletenessWrites, limitations }) assert.equal(included.total, 10) assert.equal(included.excluded, 7) assert.equal(included.source_failures, 0) assert.equal(included.truncated, false) assert.equal(included.items.length, 3) assert.equal(calls.length, 3) assert.ok(calls.every(call => call.method === 'CSS.getStyleSheetText' && call.sessionId === 'private-session-1')) assert.deepEqual(included.limits, { max_sheets: 1024, max_sheet_bytes: 8 * 1024 * 1024, max_total_bytes: 64 * 1024 * 1024 }) const fileItem = included.items.find(item => item.style_sheet_id === 'file-style') assert.equal(fileItem.file_path, await realpath(cssFile)) assert.equal(fileItem.source.included, true) assert.equal(fileItem.source.artifact.media_type, 'text/css') assert.equal(fileItem.source.artifact.text, bodyTexts.get('file-style')) assert.equal(fileItem.source.artifact.bytes, Buffer.byteLength(bodyTexts.get('file-style'), 'utf8')) assert.equal(fileItem.source.artifact.sha256, createHash('sha256').update(bodyTexts.get('file-style')).digest('hex')) assert.match(fileItem.style_sheet_key, /^electron_stylesheet_[a-f0-9]{64}$/) assert.equal(included.items.find(item => item.style_sheet_id === 'inline-style').file_path, null) assert.equal(included.items.find(item => item.style_sheet_id === 'inline-style').is_inline, true) assert.equal(included.items.find(item => item.style_sheet_id === 'constructed-style').is_constructed, true) assert.doesNotMatch(JSON.stringify(included), /example\.invalid/) const unselectedCalls = [] const unselected = await captureElectronStyles({ connection: { async send(...args) { unselectedCalls.push(args); throw new Error('CSS body must not be requested') } }, sessionId: 'private-session-1', styles, frameIds: authorizedFrameIds, request: { include_style_sources: false }, completeness: noCompletenessWrites, limitations: [] }) assert.equal(unselected.total, 10) assert.equal(unselected.items.length, 0) assert.equal(unselected.excluded, 7) assert.equal(unselectedCalls.length, 0) const failingStyles = new Map() addStyleTo(failingStyles, 'throwing-style', { frameId: 'frame-main', sourceURL: cssUrl, origin: 'regular' }) addStyleTo(failingStyles, 'invalid-style', { frameId: 'frame-main', sourceURL: '', origin: 'regular', isInline: true }) addStyleTo(failingStyles, 'surviving-style', { frameId: 'frame-main', sourceURL: '', origin: 'regular', isConstructed: true }) const failures = await captureElectronStyles({ connection: { async send(_method, params) { if (params.styleSheetId === 'throwing-style') throw new Error('stylesheet was removed') if (params.styleSheetId === 'invalid-style') return { text: 3 } return { text: '.still-captured{}' } } }, sessionId: 'private-session-2', styles: failingStyles, frameIds: authorizedFrameIds, request: { include_style_sources: true }, completeness: noCompletenessWrites, limitations: [] }) assert.equal(failures.items.length, 3) assert.equal(failures.source_failures, 2) assert.equal(failures.truncated, false) assert.equal(failures.items.find(item => item.style_sheet_id === 'throwing-style').source.reason, 'CSS.getStyleSheetText failed') assert.equal(failures.items.find(item => item.style_sheet_id === 'invalid-style').source.reason, 'CSS.getStyleSheetText returned invalid text') assert.equal(failures.items.find(item => item.style_sheet_id === 'surviving-style').source.included, true) const oversizedStyles = new Map() addStyleTo(oversizedStyles, 'oversized', { frameId: 'frame-main', sourceURL: '', origin: 'regular', isInline: true }) const oversized = await captureElectronStyles({ connection: { async send() { return { text: 'x'.repeat(8 * 1024 * 1024 + 1) } } }, sessionId: 'private-session-3', styles: oversizedStyles, frameIds: authorizedFrameIds, request: { include_style_sources: true }, completeness: noCompletenessWrites, limitations: [] }) assert.equal(oversized.source_failures, 1) assert.equal(oversized.truncated, true) assert.equal(oversized.items[0].source.included, false) assert.match(oversized.items[0].source.reason, /per-stylesheet byte limit/) const aggregateStyles = new Map() const aggregateText = 'a'.repeat(8 * 1024 * 1024) for (let index = 0; index < 8; index += 1) { addStyleTo(aggregateStyles, `aggregate-${String(index).padStart(2, '0')}`, { frameId: 'frame-main', sourceURL: '', origin: 'regular', isInline: true }) } addStyleTo(aggregateStyles, 'aggregate-overflow', { frameId: 'frame-main', sourceURL: '', origin: 'regular', isInline: true }) const aggregate = await captureElectronStyles({ connection: { async send(_method, params) { return { text: params.styleSheetId === 'aggregate-overflow' ? 'overflow' : aggregateText } } }, sessionId: 'private-session-4', styles: aggregateStyles, frameIds: authorizedFrameIds, request: { include_style_sources: true }, completeness: noCompletenessWrites, limitations: [] }) assert.equal(aggregate.source_failures, 1) assert.equal(aggregate.truncated, true) assert.equal(aggregate.items.filter(item => item.source.included).length, 8) assert.equal(aggregate.items.find(item => item.style_sheet_id === 'aggregate-overflow').source.included, false) assert.match(aggregate.items.find(item => item.style_sheet_id === 'aggregate-overflow').source.reason, /aggregate stylesheet source byte limit/) const countLimitedStyles = new Map() for (let index = 0; index < 1025; index += 1) { addStyleTo(countLimitedStyles, `sheet-${String(index).padStart(4, '0')}`, { frameId: 'frame-main', sourceURL: '', origin: 'regular', isConstructed: true }) } let countRequests = 0 const countLimited = await captureElectronStyles({ connection: { async send() { countRequests += 1; return { text: '' } } }, sessionId: 'private-session-5', styles: countLimitedStyles, frameIds: authorizedFrameIds, request: { include_style_sources: true }, completeness: noCompletenessWrites, limitations: [] }) assert.equal(countLimited.total, 1025) assert.equal(countLimited.items.length, 1025) assert.equal(countLimited.source_failures, 1) assert.equal(countLimited.truncated, true) assert.equal(countRequests, 1024) assert.equal(countLimited.items.filter(item => item.source.included).length, 1024) await rm(fixtureRoot, { recursive: true, force: true }) console.log('REA Electron CSS capture regression checks passed') function addStyleTo(styleMap, styleSheetId, header) { ingestElectronStyleEvent({ method: 'CSS.styleSheetAdded', params: { header: { styleSheetId, ...header } } }, styleMap) }