Popup dismissal hardening: shadow roots, overlay units, scroll-lock recovery
- Overlay detection resolves effective z-index through stacking ancestors, treats a zero-size max-z wrapper plus its full-viewport descendant as one removable unit, pierces open shadow roots, and relaxes the z floor when the page is scroll-locked; a lock persisting after dismissal reports blocking even with no detected overlay - clickDismiss runs in every frame (close buttons live in cross-origin popup iframes), matches decline/no-thanks/aria-label close affordances, and re-runs before every snapshot AND screenshot to catch late-mounting dialogs - rootUnclamp triggers from IR content extent (a scroll-lock collapses captured scrollHeight) and strips vendor-injected body position/overflow locks when firing - Pollution gate fails on the scroll-lock contradiction: scrollHeight pinned to one viewport while IR content spans multiple - Frame graft skips popup-creative vendor hosts; inline embedded signup forms remain graftable (regression-tested) 322 tests pass (24 new), typecheck clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
23505ee229
commit
0dd71a68d9
@@ -117,6 +117,34 @@ describe("IR drops font-metric probe nodes (fix 4)", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("IR drops popup-vendor OVERLAY containers, keeps inline embedded forms", () => {
|
||||
it("drops an email-capture popup overlay container (vendor overlay id) but keeps a real section", () => {
|
||||
const overlay = raw("div", { id: "attentive_overlay" }, [
|
||||
raw("iframe", { id: "attentive_creative", src: "https://creatives.attn.tv/x" }),
|
||||
]);
|
||||
const real = raw("section", { class: "hero" }, [raw("h1", {}, [{ text: "Real content" }])]);
|
||||
const body = raw("body", {}, [real, overlay]);
|
||||
|
||||
const root = buildFixtureIR(body);
|
||||
const kept = root.children.filter((c) => !isTextChild(c)).map((c) => (c as IRNode).tag);
|
||||
assert.deepEqual(kept, ["section"], "the attentive overlay subtree is dropped");
|
||||
assert.equal(findByTag(root, "iframe"), null, "the popup creative iframe never reaches the IR");
|
||||
});
|
||||
|
||||
it("does NOT drop an INLINE embedded signup form that merely carries a vendor name (feature, not popup)", () => {
|
||||
// A deliberately-grafted inline Klaviyo form: a real, sized form embedded in page content. Its
|
||||
// class names the vendor but is NOT an overlay-container marker, so it must survive.
|
||||
const inlineForm = raw("div", { class: "klaviyo-form klaviyo-form-inline" }, [
|
||||
raw("form", { id: "email-signup" }, [raw("input", { type: "email" })]),
|
||||
]);
|
||||
const body = raw("body", {}, [inlineForm]);
|
||||
|
||||
const root = buildFixtureIR(body);
|
||||
assert.ok(findByTag(root, "form"), "the inline signup form survives the prune");
|
||||
assert.ok(findByTag(root, "input"), "its input survives too");
|
||||
});
|
||||
});
|
||||
|
||||
// Defect C (normalize side) — an infinite CSS animation gated to a breakpoint (a Webflow `max-lg`
|
||||
// marquee) is `animation:none` at the widths it does not run, but the browser still reports the last
|
||||
// FROZEN translateX there. `neutralizeAnimatedTransforms` zeroes the transform at EVERY viewport
|
||||
|
||||
Reference in New Issue
Block a user