Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/domformat/conformance/viewer/mount.js
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,7 @@ export async function mountConformanceDom(result, host, options = {}) {
pagedState = createPolycssPagedState(document, mounted, DEFAULT_LIMITS, loadStatePage, {
boundTargets,
onLateFailure: cleanup,
diagnostics: options.diagnostics,
});
await pagedState?.prepareInitial(options.signal);
compositorTiming = createPolycssCompositorTiming(document.state, document.bindings, materialized, mounted, { boundTargets });
Expand All @@ -549,6 +550,7 @@ export async function mountConformanceDom(result, host, options = {}) {
pagedState,
assertPagedFrameReady: (frame) => pagedState?.assertFrameReady(frame),
compositorTiming,
diagnostics: options.diagnostics,
});
effects = interpreters.has("polycss-effects@0")
? createPolycssEffects(materialized, document.bindings, mounted, { boundTargets })
Expand Down
2 changes: 1 addition & 1 deletion packages/domformat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"test": "npm run check && npm run build && node --import tsx --test test/*.test.js",
"test:coverage": "npm run check && npm run build && node --import tsx --experimental-test-coverage --test-coverage-include=\"src/**/*.ts\" --test-coverage-lines=90 --test-coverage-branches=85 --test-coverage-functions=90 --test test/*.test.js",
"test:browser": "npm run build && node scripts/run-browser-check.js",
"test:page-preparation": "node --import tsx scripts/check-page-preparation.js",
"test:page-preparation": "node --import tsx scripts/check-page-preparation.js && node --import tsx scripts/check-publication-performance.js",
"check": "npm run typecheck && node --check conformance/viewer/*.js && node --check scripts/*.js && node --check viewer/*.js",
"check:nversion": "node --check conformance/nversion/*.js && node --check test/nversion-viewer.js",
"conformance": "python3 -B conformance/run_corpus.py && python3 -B conformance/check_canonical.py && python3 -B conformance/check_css.py",
Expand Down
712 changes: 712 additions & 0 deletions packages/domformat/scripts/check-publication-performance.js

Large diffs are not rendered by default.

200 changes: 200 additions & 0 deletions packages/domformat/scripts/publication-allocation-guard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import ts from "typescript";

const FORBIDDEN_SCOPE_NAMES = Object.freeze([
"playbackSparseStage",
"stagePlayback:sequential",
"stageVariants:sequential",
"applyPlaybackStage:range",
"applyVariantStage:range",
"publishVariantTarget",
"installActiveStage",
"applyStage:range",
"publishStageShapeVisibility",
"publishSurfaceTarget",
"publishSurfaceRangeWithForced",
"applySurface",
"stageProfileVisibility",
"recoverSurface",
"recoverPendingTransforms",
"publishProfileVisibility",
"publishRecoveredShapeVisibility",
]);

function optionalNamedFunction(sourceFile, name) {
let match;
const visit = (node) => {
if (match) return;
if (ts.isFunctionDeclaration(node) && node.name?.text === name) match = node;
else if (ts.isVariableDeclaration(node)
&& ts.isIdentifier(node.name)
&& node.name.text === name
&& node.initializer
&& (ts.isArrowFunction(node.initializer) || ts.isFunctionExpression(node.initializer))) match = node.initializer;
if (!match) ts.forEachChild(node, visit);
};
visit(sourceFile);
return match;
}

function namedFunction(sourceFile, name) {
const match = optionalNamedFunction(sourceFile, name);
if (!match) throw new Error(`Publication allocation guard could not find ${name}.`);
return match;
}

function optionalBranchWithCondition(sourceFile, functionNode, pattern) {
let branch;
const visit = (node) => {
if (branch) return;
if (ts.isIfStatement(node) && pattern.test(node.expression.getText(sourceFile))) branch = node.thenStatement;
if (!branch) ts.forEachChild(node, visit);
};
visit(functionNode.body);
return branch;
}

function branchWithCondition(sourceFile, functionNode, pattern, label) {
const branch = optionalBranchWithCondition(sourceFile, functionNode, pattern);
if (!branch) throw new Error(`Publication allocation guard could not find ${label}.`);
return branch;
}

function forbiddenOperation(sourceFile, node) {
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
const owner = node.expression.expression.getText(sourceFile);
const method = node.expression.name.text;
if (method === "slice") return "slice-copy";
if (method === "from" && /(?:^|\.)(?:Array|(?:Uint|Int|Float|BigInt|BigUint)\d*Array)$/u.test(owner)) return "array-from-copy";
if (method === "sort" || method === "toSorted") return "sort-call";
}
if (ts.isNewExpression(node)) {
const constructor = node.expression.getText(sourceFile);
if (/(?:^|\.)(?:Array|(?:Uint|Int|Float|BigInt|BigUint)\d*Array)$/u.test(constructor)) return "array-constructor";
if (/(?:^|\.)(?:Set|Map)$/u.test(constructor)) return "set-map-constructor";
}
if (ts.isArrayLiteralExpression(node)) return node.elements.some(ts.isSpreadElement) ? "spread-array-clone" : "array-literal";
if (ts.isArrowFunction(node) || ts.isFunctionExpression(node) || ts.isFunctionDeclaration(node)) return "nested-closure";
return null;
}

function within(node, ancestor) {
for (let current = node; current; current = current.parent) if (current === ancestor) return true;
return false;
}

function auditNode(sourceFile, node, scope, excluded = []) {
const violations = [];
const visit = (candidate) => {
if (excluded.some((branch) => within(candidate, branch))) return;
const operation = forbiddenOperation(sourceFile, candidate);
if (operation) {
const position = sourceFile.getLineAndCharacterOfPosition(candidate.getStart(sourceFile));
violations.push(Object.freeze({
scope,
operation,
line: position.line + 1,
column: position.character + 1,
expression: candidate.getText(sourceFile).replace(/\s+/gu, " ").slice(0, 160),
}));
}
ts.forEachChild(candidate, visit);
};
visit(node);
return violations;
}

function completeBranches(sourceFile, functionNode) {
const branches = [];
const visit = (node) => {
if (ts.isIfStatement(node) && /stage\.(?:kind\s*===\s*["']complete["']|complete)/u.test(node.expression.getText(sourceFile))) branches.push(node.thenStatement);
ts.forEachChild(node, visit);
};
visit(functionNode.body);
return branches;
}

function pagedDispatchGuard(sourceFile) {
const stageFrame = namedFunction(sourceFile, "stageFrame");
const first = ts.isBlock(stageFrame.body) ? stageFrame.body.statements[0] : undefined;
const text = first?.getText(sourceFile).replace(/\s+/gu, " ") ?? "";
return Boolean(first
&& ts.isIfStatement(first)
&& /packet\.kind\s*===\s*["']paged["']/u.test(first.expression.getText(sourceFile))
&& /return\s+options\.pagedState!?\.stage\(frame,\s*true\)/u.test(text));
}

export function auditSequentialPagedPublicationSources({ pagedSource, polycssSource, statePagesSource, pagedFile = "src/state/paged-state.ts", polycssFile = "src/state/polycss.ts", statePagesFile = "src/state-pages.ts" }) {
const paged = ts.createSourceFile(pagedFile, pagedSource, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
const polycss = ts.createSourceFile(polycssFile, polycssSource, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
const statePages = ts.createSourceFile(statePagesFile, statePagesSource, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
const playbackSparseStage = namedFunction(paged, "playbackSparseStage");
const stagePlayback = namedFunction(paged, "stagePlayback");
const stageVariants = namedFunction(paged, "stageVariants");
const applyPlaybackStage = namedFunction(paged, "applyPlaybackStage");
const applyVariantStage = namedFunction(paged, "applyVariantStage");
const publishVariantTarget = optionalNamedFunction(paged, "publishVariantTarget");
const installActiveStage = optionalNamedFunction(paged, "installActiveStage");
const applyStage = namedFunction(polycss, "applyStage");
const publishStageShapeVisibility = optionalNamedFunction(polycss, "publishStageShapeVisibility");
const publishSurfaceTarget = optionalNamedFunction(polycss, "publishSurfaceTarget");
const publishSurfaceRangeWithForced = optionalNamedFunction(polycss, "publishSurfaceRangeWithForced");
const applySurface = namedFunction(polycss, "applySurface");
const stageProfileVisibility = namedFunction(polycss, "stageProfileVisibility");
const recoverSurface = optionalNamedFunction(polycss, "recoverSurface");
const recoverPendingTransforms = optionalNamedFunction(polycss, "recoverPendingTransforms");
const publishProfileVisibility = optionalNamedFunction(polycss, "publishProfileVisibility");
const publishRecoveredShapeVisibility = optionalNamedFunction(polycss, "publishRecoveredShapeVisibility");
const validatePagedPlaybackBoundaryFromCanonical = namedFunction(statePages, "validatePagedPlaybackBoundaryFromCanonical");
const stagePlaybackSequential = branchWithCondition(paged, stagePlayback, /frame\s*===\s*expected|frame\s*===\s*\(.*expected/u, "stagePlayback sequential branch");
const stageVariantsSequential = branchWithCondition(paged, stageVariants, /frame\s*===\s*expected/u, "stageVariants sequential branch");
const pageBoundaryValidationCalled = /validatePagedPlaybackBoundaryFromCanonical\s*\(/u.test(stagePlaybackSequential.getText(paged));
const applyStageRange = optionalBranchWithCondition(polycss, applyStage, /next\.kind\s*===\s*["']range["']/u);
const missingScopes = [
...(publishVariantTarget ? [] : ["publishVariantTarget"]),
...(installActiveStage ? [] : ["installActiveStage"]),
...(applyStageRange ? [] : ["applyStage:range"]),
...(publishStageShapeVisibility ? [] : ["publishStageShapeVisibility"]),
...(publishSurfaceTarget ? [] : ["publishSurfaceTarget"]),
...(publishSurfaceRangeWithForced ? [] : ["publishSurfaceRangeWithForced"]),
...(recoverSurface ? [] : ["recoverSurface"]),
...(recoverPendingTransforms ? [] : ["recoverPendingTransforms"]),
...(publishProfileVisibility ? [] : ["publishProfileVisibility"]),
...(publishRecoveredShapeVisibility ? [] : ["publishRecoveredShapeVisibility"]),
...(pageBoundaryValidationCalled ? [] : ["validatePagedPlaybackBoundaryFromCanonical:call-site"]),
];
const violations = [
...auditNode(paged, playbackSparseStage.body, "playbackSparseStage"),
...auditNode(paged, stagePlaybackSequential, "stagePlayback:sequential"),
...auditNode(paged, stageVariantsSequential, "stageVariants:sequential"),
...auditNode(paged, applyPlaybackStage.body, "applyPlaybackStage:range", completeBranches(paged, applyPlaybackStage)),
...auditNode(paged, applyVariantStage.body, "applyVariantStage:range", completeBranches(paged, applyVariantStage)),
...(publishVariantTarget ? auditNode(paged, publishVariantTarget.body, "publishVariantTarget") : []),
...(installActiveStage ? auditNode(paged, installActiveStage.body, "installActiveStage") : []),
...(applyStageRange ? auditNode(polycss, applyStageRange, "applyStage:range") : []),
...(publishStageShapeVisibility ? auditNode(polycss, publishStageShapeVisibility.body, "publishStageShapeVisibility") : []),
...(publishSurfaceTarget ? auditNode(polycss, publishSurfaceTarget.body, "publishSurfaceTarget") : []),
...(publishSurfaceRangeWithForced ? auditNode(polycss, publishSurfaceRangeWithForced.body, "publishSurfaceRangeWithForced") : []),
...auditNode(polycss, applySurface.body, "applySurface"),
...auditNode(polycss, stageProfileVisibility.body, "stageProfileVisibility"),
...(recoverSurface ? auditNode(polycss, recoverSurface.body, "recoverSurface") : []),
...(recoverPendingTransforms ? auditNode(polycss, recoverPendingTransforms.body, "recoverPendingTransforms") : []),
...(publishProfileVisibility ? auditNode(polycss, publishProfileVisibility.body, "publishProfileVisibility") : []),
...(publishRecoveredShapeVisibility ? auditNode(polycss, publishRecoveredShapeVisibility.body, "publishRecoveredShapeVisibility") : []),
...auditNode(statePages, validatePagedPlaybackBoundaryFromCanonical.body, "validatePagedPlaybackBoundaryFromCanonical"),
];
const pagedDispatchBeforeInlineMaterialization = pagedDispatchGuard(polycss);
return Object.freeze({
schema: "domformat-sequential-paged-source-guard@1",
method: "typescript-ast-bounded-forbidden-form-guard",
measuredHeapAllocations: false,
files: Object.freeze([pagedFile, polycssFile, statePagesFile]),
scopes: Object.freeze([...FORBIDDEN_SCOPE_NAMES, "validatePagedPlaybackBoundaryFromCanonical"]),
forbiddenOperations: Object.freeze(["slice-copy", "array-from-copy", "array-constructor", "array-literal", "spread-array-clone", "set-map-constructor", "sort-call", "nested-closure"]),
pagedDispatchBeforeInlineMaterialization,
pageBoundaryValidationCalled,
missingScopes: Object.freeze(missingScopes),
violations: Object.freeze(violations),
pass: pagedDispatchBeforeInlineMaterialization && missingScopes.length === 0 && violations.length === 0,
limitation: "This bounded source guard rejects selected source forms only in the named guarded scopes. It does not traverse the call graph and is not a general JavaScript heap-allocation measurement.",
});
}
73 changes: 73 additions & 0 deletions packages/domformat/scripts/publication-diagnostics-viewer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { readDomBrowserUrl } from "/dist/browser.js";
import { createPolycssPublicationDiagnostics } from "/dist/internal-conformance.js";
import { mountConformanceDom } from "/conformance/viewer/mount.js";

const host = document.querySelector("#viewer");
const status = document.querySelector("#status");
const parameters = new URLSearchParams(location.search);
const modelUrl = parameters.get("model");
let runtime = null;

async function loadStatePage(record, signal) {
const packageUrl = new URL(modelUrl, location.href);
const resourceUrl = new URL(record.path, packageUrl);
if (resourceUrl.origin !== packageUrl.origin || resourceUrl.username || resourceUrl.password) throw new Error(`State page ${record.id} escapes the package origin.`);
const response = await fetch(resourceUrl, { cache: "no-store", credentials: "omit", redirect: "error", signal });
if (!response.ok || !response.body) throw new Error(`State page ${record.id} request failed.`);
const declared = response.headers.get("content-length");
if (declared !== null && (!/^\d+$/u.test(declared) || Number(declared) !== record.byteLength)) throw new Error(`State page ${record.id} has the wrong Content-Length.`);
const reader = response.body.getReader();
const chunks = [];
let length = 0;
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
length += value.byteLength;
if (length > record.byteLength) throw new Error(`State page ${record.id} exceeds its declared length.`);
chunks.push(value);
}
} finally {
try { reader.releaseLock(); } catch {}
}
if (length !== record.byteLength) throw new Error(`State page ${record.id} has the wrong length.`);
const output = new Uint8Array(length);
let offset = 0;
for (const chunk of chunks) {
output.set(chunk, offset);
offset += chunk.byteLength;
}
return output;
}

try {
if (!modelUrl) throw new Error("Missing required ?model=/path/to/model.json URL.");
const result = await readDomBrowserUrl(modelUrl);
const diagnostics = createPolycssPublicationDiagnostics();
runtime = await mountConformanceDom(result, host, {
animate: true,
mode: "animation",
viewportWidth: innerWidth,
viewportHeight: innerHeight,
loadStatePage,
diagnostics,
});
document.documentElement.dataset.domformatReady = "";
globalThis.domformatDiagnosticProof = Object.freeze({
diagnostics,
implementation: "repo-internal-conformance",
get sourceFrame() { return runtime.sourceFrame; },
destroy() {
runtime.destroy();
document.documentElement.removeAttribute("data-domformat-ready");
document.documentElement.dataset.domformatDestroyed = "";
},
});
addEventListener("pagehide", () => runtime.destroy(), { once: true });
status.textContent = `${result.document.meta.format} · internal publication diagnostics`;
} catch (error) {
runtime?.destroy();
document.documentElement.dataset.domformatError = "";
status.textContent = error instanceof Error ? error.message : String(error);
console.error(error);
}
36 changes: 36 additions & 0 deletions packages/domformat/scripts/publication-trace-policy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { invariant } from "../src/errors.js";

export const PUBLICATION_PAGE_PREPARATION_MAX_TASK_MS = 50;
export const PUBLICATION_MAIN_TASK_EVENT = "ThreadControllerImpl::RunTask";
export const PUBLICATION_PAGE_PREPARATION_ATTRIBUTION = `${PUBLICATION_MAIN_TASK_EVENT} containing FireIdleCallback`;
export const PUBLICATION_TRACE_START_CONFIG = Object.freeze({
transferMode: "ReportEvents",
traceConfig: Object.freeze({
recordMode: "recordAsMuchAsPossible",
includedCategories: Object.freeze([
"blink.user_timing",
"devtools.timeline",
"toplevel",
]),
}),
});

export function assertPublicationTraceComplete(completion) {
invariant(completion?.dataLossOccurred === false, "PUBLICATION_TRACE_DATA_LOSS", "Chrome reported data loss in the publication trace.");
}

export function assertPublicationPagePreparationGate(trace) {
const preparation = trace?.pagePreparation;
invariant(
preparation?.attribution === PUBLICATION_PAGE_PREPARATION_ATTRIBUTION
&& preparation.idleCallbackCount > 0
&& preparation.taskCount > 0,
"PAGE_PREPARATION_ATTRIBUTION_MISSING",
"Publication trace contains no attributable page-preparation task; the 50 ms gate cannot pass vacuously.",
);
invariant(
preparation.maxTaskMs <= PUBLICATION_PAGE_PREPARATION_MAX_TASK_MS,
"PAGE_PREPARATION_LONG_TASK",
`Publication trace page preparation reached ${preparation.maxTaskMs} ms, above ${PUBLICATION_PAGE_PREPARATION_MAX_TASK_MS} ms.`,
);
}
20 changes: 20 additions & 0 deletions packages/domformat/scripts/publication-trace-window.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { invariant } from "../src/errors.js";

export function publicationFrameAdvances(startFrame, endFrame, frameCount) {
invariant(startFrame >= 1 && startFrame <= frameCount && endFrame >= 1 && endFrame <= frameCount, "PUBLICATION_DIAGNOSTIC_WINDOW", "Publication diagnostic window contains an invalid frame.");
return endFrame >= startFrame ? endFrame - startFrame : frameCount - startFrame + endFrame;
}

export function publicationPageBoundariesCrossed(startFrame, endFrame, frameCount, framesPerPage) {
invariant(frameCount % framesPerPage === 0, "PUBLICATION_DIAGNOSTIC_WINDOW", "Publication diagnostic pages do not divide the frame cycle.");
const advances = publicationFrameAdvances(startFrame, endFrame, frameCount);
if (advances === 0) return 0;
const startPage = Math.floor((startFrame - 1) / framesPerPage);
const endPage = Math.floor((endFrame - 1) / framesPerPage);
return endFrame >= startFrame ? endPage - startPage : frameCount / framesPerPage - startPage + endPage;
}

export function assertSingleCycleTraceDuration(durationMs, frameCount, tickRateHz) {
invariant(Number.isFinite(durationMs) && durationMs >= 40_000, "PUBLICATION_TRACE_DURATION", "The publication trace must run for at least 40 seconds.");
invariant(durationMs <= frameCount / tickRateHz * 1_000 - 3_000, "PUBLICATION_TRACE_DURATION", "The publication trace must retain three seconds of headroom before one prepared playback cycle so frame-window evidence is unambiguous.");
}
2 changes: 1 addition & 1 deletion packages/domformat/src/internal-conformance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ export { createPolycssCompositorTiming } from "./state/compositor-timing.js";
export { createPolycssEffects } from "./state/effects.js";
export { createPolycssInteraction } from "./state/interaction.js";
export { createPolycssOrbitInput } from "./state/orbit.js";
export { createPolycssPagedState } from "./state/paged-state.js";
export { createPolycssPagedState, createPolycssPublicationDiagnostics } from "./state/paged-state.js";
export { createPolycssPlayback, materializePolycssState } from "./state/polycss.js";
export { createStaticPresentation } from "./state/presentation.js";
Loading
Loading