commit cb0e9ab3be4e5d8310155a978463a59f8c49493b
parent 39952d088a8c1a636a93469db97c52d4cdb1eeda
Author: Emilio Cobos Álvarez <emilio@crisal.io>
Date: Wed, 1 Oct 2025 14:49:18 +0000
Bug 1991410 - Don't apply intersection root rect clip to targets inside popups. r=tnikkel,layout-reviewers
Since they don't get clipped by the viewport.
Differential Revision: https://phabricator.services.mozilla.com/D266983
Diffstat:
2 files changed, 35 insertions(+), 4 deletions(-)
diff --git a/dom/base/DOMIntersectionObserver.cpp b/dom/base/DOMIntersectionObserver.cpp
@@ -556,9 +556,10 @@ static Maybe<nsRect> ComputeTheIntersection(
intersectionRect = intersectionRectRelativeToRoot.EdgeInclusiveIntersection(
*aRemoteDocumentVisibleRect);
- if (intersectionRect.isNothing()) {
- return Nothing();
- }
+ } else if (aTarget->HasAnyStateBits(NS_FRAME_IN_POPUP)) {
+ // Popups don't get clipped to the viewport, so avoid applying the root
+ // intersection rect, see bug 1991410.
+ intersectionRect = Some(intersectionRectRelativeToRoot);
} else {
intersectionRect =
intersectionRectRelativeToRoot.EdgeInclusiveIntersection(aRootBounds);
diff --git a/toolkit/content/tests/chrome/window_largemenu.xhtml b/toolkit/content/tests/chrome/window_largemenu.xhtml
@@ -93,13 +93,41 @@ async function nextTest()
popup.openPopupAtScreen(100, y, false);
}
+async function assertItemsOcclusionState(popup) {
+ info("Checking intersection state");
+ let items = popup.querySelectorAll("menuitem");
+ isnot(items.length, 0, "Should have items");
+ let {promise, resolve} = Promise.withResolvers();
+ let io = new IntersectionObserver(function(entries) {
+ resolve(entries)
+ });
+ for (let item of items) {
+ io.observe(item);
+ }
+ let entries = await promise;
+ io.disconnect();
+ is(entries.length, items.length, "Should have an entry per element");
+
+ let scrollbox = popup.scrollBox.scrollbox;
+ let scrollBoxRect = scrollbox.getBoundingClientRect();
+ let borderTop = parseFloat(getComputedStyle(scrollbox).borderTop);
+ let borderBottom = parseFloat(getComputedStyle(scrollbox).borderBottom);
+
+ for (let entry of entries) {
+ let itemRect = entry.boundingClientRect;
+ // Importantly, we don't consider the page's viewport.
+ let shouldIntersect = itemRect.bottom >= scrollBoxRect.top + borderTop && itemRect.top <= scrollBoxRect.bottom - borderBottom;
+ is(entry.isIntersecting, shouldIntersect, `${entry.target.outerHTML} intersection state matches`);
+ }
+}
+
async function popupShown() {
// This is needed for overflow events to run.
await nextFrame();
startTest();
}
-function startTest()
+async function startTest()
{
if (gTests[gTestIndex] == "menu movement")
return testPopupMovement();
@@ -114,6 +142,8 @@ function startTest()
var scrollbox = popup.scrollBox.scrollbox;
var expectedScrollPos = 0;
+ await assertItemsOcclusionState(popup);
+
info(`${gTests[gTestIndex]}: ${JSON.stringify(rect)} | ${screen.width}x${screen.height} | ${gScreenY}`);
if (gTestIndex == 0) {
// the popup should be in the center of the screen