fix: prevent silent passing on missing topology data + visible cleanup failures
predicates.ts (missing-fact discipline):
- getTopologyValueBySubject: return -1 sentinel instead of ?? 0
when topology data is missing/unset. NaN and negative values
also treated as missing. Previously returned 0 which was
indistinguishable from root subject ID.
- clippedByPredicate: return 'indeterminate' when clippingRoot < 0
- inStackingContextPredicate: return 'indeterminate' when sc < 0
or refSC < 0 (both subject and reference). Previously treated
missing data as false — a silent wrong answer.
- attachedToScrollContainerPredicate: return 'indeterminate' when
scrollContainer < 0
- escapeClippingChainOfPredicate: return 'indeterminate' when
clippingRoot < 0
extraction.ts (cleanup visibility):
- Promote fast-geometry and CDP cleanup failures from console.debug
(invisible during test execution) to console.warn. Contaminated
pages are now diagnosable without debug-log inspection.
658 tests pass.
This commit is contained in:
@@ -747,7 +747,8 @@ export async function extractWorldFastGeometry(
|
||||
el.removeAttribute('data-imhotep-runtime-id')
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.debug('[imhotep-playwright] fast-geometry cleanup evaluate failed:', err instanceof Error ? err.message : err)
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('[imhotep-playwright] fast-geometry cleanup failed:', err instanceof Error ? err.message : err)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -933,7 +934,8 @@ export async function extractWorldCdp(
|
||||
el.removeAttribute('data-imhotep-runtime-id')
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.debug('[imhotep-playwright] CDP cleanup evaluate failed:', err instanceof Error ? err.message : err)
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('[imhotep-playwright] CDP cleanup failed:', err instanceof Error ? err.message : err)
|
||||
})
|
||||
await sessionManager.detach()
|
||||
}
|
||||
|
||||
@@ -195,7 +195,10 @@ function getSubjectIndex(world: GeometryWorld, subjectId: number): number {
|
||||
|
||||
function getTopologyValueBySubject(world: GeometryWorld, subjectId: number, values: number[]): number {
|
||||
const idx = getSubjectIndex(world, subjectId);
|
||||
return idx >= 0 ? values[idx] ?? 0 : 0;
|
||||
if (idx < 0 || idx >= values.length) return -1;
|
||||
const v = values[idx];
|
||||
// NaN or negative values indicate unset topology; treat as missing.
|
||||
return (v === undefined || Number.isNaN(v) || v < 0) ? -1 : v;
|
||||
}
|
||||
|
||||
function getSubjectDomNodeId(world: GeometryWorld, subjectId: number): number {
|
||||
@@ -782,6 +785,7 @@ export const clippedByPredicate: PredicateEvaluator = {
|
||||
}
|
||||
// Simplified: check if subject's clipping root is the reference
|
||||
const clipRoot = getTopologyValueBySubject(world, subjectId, world.topology.clippingRootOf);
|
||||
if (clipRoot < 0) return makePredicateResult('indeterminate');
|
||||
const pass = clipRoot === referenceId;
|
||||
// Determine clip kind from the clipping table entry for the reference.
|
||||
// Encoding: 1=contain:paint, 2=overflow:hidden/scroll/auto
|
||||
@@ -807,11 +811,13 @@ export const inStackingContextPredicate: PredicateEvaluator = {
|
||||
return makePredicateResult('indeterminate');
|
||||
}
|
||||
const sc = getTopologyValueBySubject(world, subjectId, world.topology.stackingContextOf);
|
||||
if (sc < 0) return makePredicateResult('indeterminate');
|
||||
const subjectHasSC = sc > 0;
|
||||
|
||||
if (tuple.length >= 2 && tuple[1] !== undefined && tuple[1] !== 0) {
|
||||
const referenceId = tuple[1];
|
||||
const refSC = getTopologyValueBySubject(world, referenceId, world.topology.stackingContextOf);
|
||||
if (refSC < 0) return makePredicateResult('indeterminate');
|
||||
const pass = subjectHasSC && refSC > 0 && sc === refSC;
|
||||
return makePredicateResult(pass ? 'true' : 'false', { stackingContext: sc, referenceStackingContext: refSC }, [subjectId, referenceId]);
|
||||
}
|
||||
@@ -829,6 +835,7 @@ export const attachedToScrollContainerPredicate: PredicateEvaluator = {
|
||||
return makePredicateResult('indeterminate');
|
||||
}
|
||||
const scrollContainer = getTopologyValueBySubject(world, subjectId, world.topology.scrollContainerOf);
|
||||
if (scrollContainer < 0) return makePredicateResult('indeterminate');
|
||||
const pass = scrollContainer > 0 && scrollContainer === referenceId;
|
||||
return makePredicateResult(pass ? 'true' : 'false', { scrollContainer, referenceId }, [subjectId]);
|
||||
},
|
||||
@@ -854,6 +861,7 @@ export const escapeClippingChainOfPredicate: PredicateEvaluator = {
|
||||
const overflowBottom = Math.max(0, sRect.bottom - clipRect.bottom);
|
||||
const overflow = overflowLeft + overflowTop + overflowRight + overflowBottom;
|
||||
const clippingRoot = getTopologyValueBySubject(world, subjectId, world.topology.clippingRootOf);
|
||||
if (clippingRoot < 0) return makePredicateResult('indeterminate', { hasClippingRoot: 0 }, [subjectId, referenceId]);
|
||||
const clippedByReference = clippingRoot === referenceId;
|
||||
const pass = overflow > 0 && !clippedByReference && ancestry !== false;
|
||||
const metrics: Record<string, number> = {
|
||||
|
||||
Reference in New Issue
Block a user