Hit-testing misses position: fixed elements with transform when nested inside a non-stacking-context parent - #618
Open
jerry4718 wants to merge 2 commits into
Conversation
Recompute stacking context content areas after layout and transform resolution. The earlier calculation ran before final layouts existed and ignored CSS transforms, so hit-testing could skip transformed hoisted children.
Fixed-position elements are positioned relative to the viewport, but hoisted children inherited their parent's layout offset while being propagated to the parent stacking context. A body margin or other ancestor offset could therefore shift fixed overlays and cause hit-testing to miss nested modal content. Skip the parent layout and scroll offset when propagating fixed-position hoisted children. Keep the parent offset behavior for absolute and other positioned elements whose coordinates depend on the parent context. Tests: - dynamically_inserted_modal_content_is_hittable - dynamically_inserted_modal_content_hittable_at_transformed_edge
jerry4718
force-pushed
the
fix/hoisted-content-area-stale-layout
branch
from
August 6, 2026 05:16
d5c7c71 to
aaa5f53
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A
position: fixedelement withtransform: translate(-50%, -50%)(centered modal) nested inside a full-screenposition: fixedwrapper is not hit-testable. Clicks land on the wrapper instead of the modal content.This affects any structure like:
The bug is especially reproducible when the modal is dynamically inserted (e.g. Vue
v-if) after initial render, triggering the incremental layout path.Root causes
1. Fixed-position hoisted children get incorrect parent layout offset
In
flush_styles_to_layout_impl(packages/blitz-dom/src/layout/damage.rs), when a hoisted child is passed up from a non-stacking-context parent to the parent's stacking context, the parent'sfinal_layout().locationis added to the child's hoisted position:For
position: fixedelements this is wrong. They are positioned relative to the viewport, not the parent's content box. If<body>has a margin (e.g. 21px from default user-agent styles or margin collapsing), this offset is incorrectly applied to the fixed element, shifting all hit-test coordinates by that amount.When the modal is small enough that the offset pushes the click coordinate outside the modal's transformed bounds, the modal becomes unclickable.
Fix: Skip adding the parent layout offset for
position: fixedhoisted children.2.
content_areadoes not account for transforms on hoisted childrencompute_content_sizeruns duringflush_styles_to_layout, which executes beforeresolve_layoutandresolve_transforms. At that pointfinal_layout()returns zeros and transforms are not yet computed, socontent_areaisRect::ZERO. This meansmatches_hoisted_contentinhit_inneris always false for any stacking context whose children were flushed in the same pass.Even after layout resolves,
content_areawas computed from pre-transformfinal_layout().location. A hoisted child withtransform: translate(-50%, -50%)may be visually present at a point outside its untransformedcontent_area, causing thematches_hoisted_contentgatekeeper to incorrectly skip it.Fix: Added
recompute_stacking_context_content_areacalled afterresolve_transformsinresolve.rs. It recomputescontent_areausing transform-adjusted corner positions (applyingAffine::translate(layout_location) * transformto the child's border box corners), so the gatekeeper correctly reflects where hoisted children are actually rendered.Reproduction
Without the fixes, step 5 hits
mask-contentinstead ofmodal-content.WPT results
No changes in test results compared to
main.Generated by the WPT workflow.