Skip to content

Commit 7454f54

Browse files
committed
Merge main into stripe-child detection
Preserve stripe-child findings while adopting upstream rendered-rule changes. Rebuild the detector bundle and update only upstream descriptions in stripe and aggregate oracle goldens. Full Rust, rebuilt-engine Bun/Node, distribution and real Chrome fixture checks pass. AI-assisted by Codex under pbakaus's instructions.
2 parents 8b0cc87 + 83c2c73 commit 7454f54

61 files changed

Lines changed: 1529 additions & 213 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎browser-bundle/10-probe.js‎

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,35 @@ function __rectArray(r) {
4646
return [r.x, r.y, r.width, r.height, r.top, r.right, r.bottom, r.left];
4747
}
4848

49+
// The client rects of the non-blank text nodes under `node`, in document
50+
// order. `deep` walks element children too: one line of prose is one line
51+
// box however the markup splits it, and an inline <strong>, an <a> or a
52+
// framework marker in the middle of a sentence is a separate text node whose
53+
// rects belong to the same line. Nothing is merged here — the rects travel as
54+
// the page gave them and the consumer groups them into lines (see
55+
// merge_text_rects_into_lines in crates/foundation/src/browser/dom.rs).
56+
function __collectTextRects(node, deep, out) {
57+
for (const child of node.childNodes) {
58+
if (child.nodeType === 3) {
59+
if (!(child.textContent || '').trim()) continue;
60+
const range = document.createRange();
61+
range.selectNodeContents(child);
62+
for (const rect of range.getClientRects()) {
63+
if (rect.width >= 1 && rect.height >= 1) out.push(rect);
64+
}
65+
range.detach?.();
66+
} else if (deep && child.nodeType === 1) {
67+
__collectTextRects(child, true, out);
68+
}
69+
}
70+
return out;
71+
}
72+
73+
// The element's own direct text, unmerged: what the union rect is built from.
74+
function __directTextRects(el) {
75+
return __collectTextRects(__el(el), false, []);
76+
}
77+
4978
const __impeccableDom = {
5079
document_element() { return __intern(document.documentElement); },
5180
body() { return __intern(document.body); },
@@ -181,22 +210,23 @@ const __impeccableDom = {
181210
// getDirectTextRect(el) from the JS driver: union of the client rects of
182211
// the element's non-blank direct text nodes.
183212
direct_text_rect(el) {
184-
const node = __el(el);
185-
const rects = [];
186-
for (const child of node.childNodes) {
187-
if (child.nodeType !== 3 || !(child.textContent || '').trim()) continue;
188-
const range = document.createRange();
189-
range.selectNodeContents(child);
190-
for (const rect of range.getClientRects()) {
191-
if (rect.width >= 1 && rect.height >= 1) rects.push(rect);
192-
}
193-
range.detach?.();
194-
}
213+
const rects = __directTextRects(el);
195214
if (rects.length === 0) return [];
196215
const left = Math.min(...rects.map(r => r.left));
197216
const top = Math.min(...rects.map(r => r.top));
198217
const right = Math.max(...rects.map(r => r.right));
199218
const bottom = Math.max(...rects.map(r => r.bottom));
200219
return [left, top, right - left, bottom - top, top, right, bottom, left];
201220
},
221+
// Every rect of the element's rendered text, descendants included, flattened
222+
// into eights. The scope is the element's whole text_content, which is the
223+
// text a caller counts characters from; the caller merges the rects that
224+
// share a row into the line they rendered on.
225+
text_rects(el) {
226+
const out = [];
227+
for (const r of __collectTextRects(__el(el), true, [])) {
228+
out.push(r.left, r.top, r.width, r.height, r.top, r.right, r.bottom, r.left);
229+
}
230+
return out;
231+
},
202232
};

‎browser-bundle/15-snapshot.js‎

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,19 +69,26 @@ const __SNAP_DEFAULT_MAX_BYTES = 48 * 1024 * 1024;
6969
function __snapRect4(r) { return [r.x, r.y, r.width, r.height]; }
7070
function __snapNum(v) { return typeof v === 'number' ? v : null; }
7171

72-
// getDirectTextRect(el): union of the client rects of the element's
73-
// non-blank direct text nodes (same measure as 10-probe.js).
74-
function __snapDirectTextRect(node) {
75-
const rects = [];
72+
// The client rects of `node`'s own non-blank text nodes (same walk as
73+
// 10-probe.js#__collectTextRects with `deep` off). Each element records only
74+
// its own, so a line that rendered is recorded exactly once in a snapshot.
75+
function __snapTextRects(node, out) {
7676
for (const child of node.childNodes) {
77-
if (child.nodeType !== 3 || !(child.textContent || '').trim()) continue;
77+
if (child.nodeType !== 3) continue;
78+
if (!(child.textContent || '').trim()) continue;
7879
const range = document.createRange();
7980
range.selectNodeContents(child);
8081
for (const rect of range.getClientRects()) {
81-
if (rect.width >= 1 && rect.height >= 1) rects.push(rect);
82+
if (rect.width >= 1 && rect.height >= 1) out.push(rect);
8283
}
8384
range.detach?.();
8485
}
86+
return out;
87+
}
88+
89+
// getDirectTextRect(el) over rects already collected: their union, as
90+
// 10-probe.js#direct_text_rect builds it.
91+
function __snapDirectTextRectOf(rects) {
8592
if (rects.length === 0) return null;
8693
const left = Math.min(...rects.map(r => r.left));
8794
const top = Math.min(...rects.map(r => r.top));
@@ -630,8 +637,17 @@ const __impeccableSnapshot = {
630637
rec.v = typeof el.checkVisibility === 'function'
631638
? (el.checkVisibility({ checkOpacity: false, checkVisibilityCSS: true }) ? 1 : 0)
632639
: -1;
633-
const dtr = __snapDirectTextRect(el);
640+
// The element's OWN text rects, unmerged (`dl`), and their union
641+
// (`d`, the long-standing field). Own and not the subtree's: every
642+
// ancestor would otherwise carry a copy of every line under it, which
643+
// on a deep text-heavy page multiplies the snapshot by its depth and
644+
// can push it past the byte cap. The consumer walks the tree and
645+
// assembles an element's lines from the rects its descendants each
646+
// recorded once (`SnapshotDom::text_line_rects`).
647+
const own = __snapTextRects(el, []);
648+
const dtr = __snapDirectTextRectOf(own);
634649
if (dtr) rec.d = dtr;
650+
if (own.length) rec.dl = own.map(r => [r.x, r.y, r.width, r.height]);
635651
if (el.isContentEditable) rec.e = true;
636652
if (el.hidden) rec.h = true;
637653
if (typeof el.id !== 'string') rec.i = true;
@@ -661,6 +677,7 @@ const __impeccableSnapshot = {
661677
}
662678
const snapshot = {
663679
v: 1,
680+
textLines: true,
664681
hostname: location.hostname,
665682
quirks: document.compatMode === 'BackCompat',
666683
innerWidth: window.innerWidth,

0 commit comments

Comments
 (0)