v1.1.0: pooled runtime, 959 tests, production hardening (0 squash)
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
// Formatting context boundary computation for Imhotep topology engine.
|
||||
|
||||
import {
|
||||
type DomTree,
|
||||
type StyleFacts,
|
||||
type TopologyGraph,
|
||||
Display,
|
||||
Overflow,
|
||||
Position,
|
||||
ConfidenceLevel,
|
||||
INVALID_ID,
|
||||
setConfidence,
|
||||
} from './graph.js';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Formatting context establishment predicates
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns true if the element establishes an independent formatting context.
|
||||
*
|
||||
* Rules (simplified, covering the common cases):
|
||||
* 1. Root element.
|
||||
* 2. display: inline-block, flex, grid, table, table-cell.
|
||||
* 3. overflow != visible on a block container (block formatting context).
|
||||
* 4. position: absolute or fixed (they establish a new containing block,
|
||||
* which implies an independent formatting context for their contents).
|
||||
* 5. contain: layout or paint (CSS Containment).
|
||||
*
|
||||
* Floats also establish BFCs, but we do not model float here.
|
||||
*/
|
||||
export function establishesFormattingContext(
|
||||
subjectId: number,
|
||||
isRoot: boolean,
|
||||
styles: StyleFacts,
|
||||
): { yes: boolean; confidence: number } {
|
||||
if (isRoot) {
|
||||
return { yes: true, confidence: ConfidenceLevel.EXACT };
|
||||
}
|
||||
|
||||
const disp = styles.display[subjectId];
|
||||
|
||||
// Inline-block, flex, grid, table all establish independent formatting contexts.
|
||||
if (
|
||||
disp === Display.INLINE_BLOCK ||
|
||||
disp === Display.FLEX ||
|
||||
disp === Display.GRID ||
|
||||
disp === Display.TABLE
|
||||
) {
|
||||
return { yes: true, confidence: ConfidenceLevel.DERIVED };
|
||||
}
|
||||
|
||||
// Block container with overflow != visible establishes a BFC.
|
||||
if (disp === Display.BLOCK) {
|
||||
const overflowHidden =
|
||||
styles.overflowX[subjectId] !== Overflow.VISIBLE ||
|
||||
styles.overflowY[subjectId] !== Overflow.VISIBLE;
|
||||
if (overflowHidden) {
|
||||
return { yes: true, confidence: ConfidenceLevel.DERIVED };
|
||||
}
|
||||
}
|
||||
|
||||
// Absolutely or fixed positioned elements establish an independent formatting
|
||||
// context for their contents because they form a containing block.
|
||||
const pos = styles.position[subjectId];
|
||||
if (pos === Position.ABSOLUTE || pos === Position.FIXED) {
|
||||
return { yes: true, confidence: ConfidenceLevel.DERIVED };
|
||||
}
|
||||
|
||||
// CSS contain: layout or paint.
|
||||
const contain = styles.containFlags[subjectId];
|
||||
if (contain & (0x01 | 0x02)) {
|
||||
return { yes: true, confidence: ConfidenceLevel.DERIVED };
|
||||
}
|
||||
|
||||
return { yes: false, confidence: ConfidenceLevel.EXACT };
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Formatting context assignment
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* For each node, find the nearest ancestor (including itself) that establishes
|
||||
* a formatting context. This is the formatting context the node participates in.
|
||||
*/
|
||||
export function assignFormattingContexts(
|
||||
dom: DomTree,
|
||||
styles: StyleFacts,
|
||||
graph: TopologyGraph,
|
||||
): void {
|
||||
// Pre-compute formatting context roots.
|
||||
const isFormattingRoot = new Uint8Array(dom.nodeCount).fill(0);
|
||||
const formattingConfidence = new Float32Array(dom.nodeCount).fill(ConfidenceLevel.EXACT);
|
||||
|
||||
for (let i = 0; i < dom.nodeCount; i++) {
|
||||
const isRoot = dom.parentNodeId[i] === INVALID_ID;
|
||||
const { yes, confidence } = establishesFormattingContext(i, isRoot, styles);
|
||||
if (yes) {
|
||||
isFormattingRoot[i] = 1;
|
||||
}
|
||||
formattingConfidence[i] = confidence;
|
||||
}
|
||||
|
||||
for (let i = 0; i < dom.nodeCount; i++) {
|
||||
let cursor = i;
|
||||
let found = INVALID_ID;
|
||||
|
||||
while (cursor !== INVALID_ID) {
|
||||
if (isFormattingRoot[cursor]) {
|
||||
found = cursor;
|
||||
break;
|
||||
}
|
||||
cursor = dom.parentNodeId[cursor];
|
||||
}
|
||||
|
||||
graph.formattingContextOf[i] = found;
|
||||
|
||||
const minConfidence: number =
|
||||
found === INVALID_ID
|
||||
? ConfidenceLevel.INDETERMINATE
|
||||
: Math.min(formattingConfidence[found], ConfidenceLevel.DERIVED);
|
||||
|
||||
setConfidence(graph, i, minConfidence as any, 40 /* formatting context */);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user