fix: topology array reordering by subject order + unary inStackingContext
Topology array order mismatch (critical bug): - CDP browser script iterates elements in document order (querySelectorAll), but solver accesses topology arrays by selector-resolution order. - Fix: add subjectIds array to TopologyRecord tracking the backendNodeId at each document-order position; remapTopologyIds now reorders all 6 topology arrays before remapping backendNodeIds to solver IDs. - Fallback: when subjectIds is missing (cached pre-fix data), falls back to simple remap without reordering. Unary inStackingContext compilation: - compileCanonicalClauseToFormula treated inStackingContext as always binary, creating referenceBinding with undefined selector for unary assertions. - Fix: add (inStackingContext && !clause.reference) to unaryPredicate/isUnary. Hard test fixture (fixtures package): - 29-element geometric fixture with 10 scenarios - 57 assertions (54 spatial + 3 topology) all pass end-to-end
This commit is contained in:
@@ -744,7 +744,28 @@ function remapTopologyIds(world: GeometryWorld): void {
|
||||
backendToSolver.set(world.subjects.domNodeId[i], world.subjects.ids[i])
|
||||
}
|
||||
|
||||
const remap = (src: ArrayLike<number>): number[] => {
|
||||
const rawSubjectIds = (world as any)._topologySubjectIds as number[] | undefined
|
||||
|
||||
function reorderAndRemap(rawValues: ArrayLike<number>, targetLength: number): number[] {
|
||||
if (!rawSubjectIds || rawSubjectIds.length === 0) {
|
||||
return remapSimple(rawValues)
|
||||
}
|
||||
|
||||
const rawToValue = new Map<number, number>()
|
||||
for (let j = 0; j < rawSubjectIds.length; j++) {
|
||||
rawToValue.set(rawSubjectIds[j], rawValues[j] ?? 0)
|
||||
}
|
||||
|
||||
const out = new Array<number>(targetLength)
|
||||
for (let i = 0; i < targetLength; i++) {
|
||||
const backendId = world.subjects.domNodeId[i]
|
||||
const rawVal = rawToValue.get(backendId) ?? 0
|
||||
out[i] = rawVal > 0 ? (backendToSolver.get(rawVal) ?? 0) : 0
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
function remapSimple(src: ArrayLike<number>): number[] {
|
||||
const out = new Array<number>(src.length)
|
||||
for (let i = 0; i < src.length; i++) {
|
||||
const backendId = src[i]
|
||||
@@ -754,19 +775,21 @@ function remapTopologyIds(world: GeometryWorld): void {
|
||||
}
|
||||
|
||||
const t = world.topology
|
||||
t.containingBlockOf = remap(t.containingBlockOf)
|
||||
t.nearestPositionedAncestorOf = remap(t.nearestPositionedAncestorOf)
|
||||
t.scrollContainerOf = remap(t.scrollContainerOf)
|
||||
t.stackingContextOf = remap(t.stackingContextOf)
|
||||
t.formattingContextOf = remap(t.formattingContextOf)
|
||||
t.clippingRootOf = remap(t.clippingRootOf)
|
||||
t.containingBlockOf = reorderAndRemap(t.containingBlockOf, nSubjects)
|
||||
t.nearestPositionedAncestorOf = reorderAndRemap(t.nearestPositionedAncestorOf, nSubjects)
|
||||
t.scrollContainerOf = reorderAndRemap(t.scrollContainerOf, nSubjects)
|
||||
t.stackingContextOf = reorderAndRemap(t.stackingContextOf, nSubjects)
|
||||
t.formattingContextOf = reorderAndRemap(t.formattingContextOf, nSubjects)
|
||||
t.clippingRootOf = reorderAndRemap(t.clippingRootOf, nSubjects)
|
||||
|
||||
if (world.clipping) {
|
||||
world.clipping.subjectId = remap(world.clipping.subjectId)
|
||||
world.clipping.subjectId = remapSimple(world.clipping.subjectId)
|
||||
}
|
||||
if (world.scroll) {
|
||||
world.scroll.containerId = remap(world.scroll.containerId)
|
||||
world.scroll.containerId = remapSimple(world.scroll.containerId)
|
||||
}
|
||||
|
||||
delete (world as any)._topologySubjectIds
|
||||
}
|
||||
|
||||
export async function extractWorldCdp(
|
||||
@@ -865,6 +888,8 @@ export async function extractWorldCdp(
|
||||
fontWeight: Array.from(canonical.styles.fontWeight),
|
||||
}
|
||||
|
||||
;(world as any)._topologySubjectIds = (canonical as any).topology?.subjectIds
|
||||
|
||||
remapTopologyIds(world)
|
||||
|
||||
const selectorToIds = new Map<string, number[]>()
|
||||
@@ -1166,6 +1191,7 @@ export function compileCanonicalClauseToFormula(clause: CanonicalClauseDescripto
|
||||
const unaryPredicate = clause.relation === 'atLeast'
|
||||
|| clause.relation === 'atMost'
|
||||
|| clause.relation === 'aspectRatio'
|
||||
|| (clause.relation === 'inStackingContext' && !clause.reference)
|
||||
|
||||
body = {
|
||||
type: 'FormulaNode',
|
||||
@@ -1213,6 +1239,7 @@ export function compileCanonicalClauseToFormula(clause: CanonicalClauseDescripto
|
||||
|| clause.relation === 'atMost'
|
||||
|| clause.relation === 'between'
|
||||
|| clause.relation === 'aspectRatio'
|
||||
|| (clause.relation === 'inStackingContext' && !clause.reference)
|
||||
|
||||
if (isUnary) {
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user