Files
ycode/scripts/rea/css-capture-regression.mjs

184 lines
9.5 KiB
JavaScript

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)
}