script: Use HTMLElement.scrollParent to implement Element.scrollIntoView (#39144)

To find scrolling ancestors, we need to walk up the flat tree and only
consider the elements that are in the chain of containing block
ancestors of an element. `scrollParent` now does this so we can use it
to properly implement `scrollIntoView`.

Testing: There are WPT tests for this change.

---------

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
Martin Robinson 2025-09-07 14:44:11 -07:00 committed by GitHub
parent 286bbe6cb1
commit 9f4f598f44
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 141 additions and 71 deletions

View file

@ -12,7 +12,8 @@ use euclid::{SideOffsets2D, Size2D};
use itertools::Itertools;
use layout_api::wrapper_traits::{LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode};
use layout_api::{
BoxAreaType, LayoutElementType, LayoutNodeType, OffsetParentResponse, ScrollParentResponse,
BoxAreaType, LayoutElementType, LayoutNodeType, OffsetParentResponse, ScrollContainerQueryType,
ScrollContainerResponse,
};
use script::layout_dom::{ServoLayoutNode, ServoThreadSafeLayoutNode};
use servo_arc::Arc as ServoArc;
@ -647,12 +648,14 @@ pub fn process_offset_parent_query(node: ServoLayoutNode<'_>) -> Option<OffsetPa
})
}
/// This is an implementation of
/// An implementation of `scrollParent` that can also be used to for `scrollIntoView`:
/// <https://drafts.csswg.org/cssom-view/#dom-htmlelement-scrollparent>.
///
#[inline]
pub(crate) fn process_scroll_parent_query(
pub(crate) fn process_scroll_container_query(
node: ServoLayoutNode<'_>,
) -> Option<ScrollParentResponse> {
query_type: ScrollContainerQueryType,
) -> Option<ScrollContainerResponse> {
let layout_data = node.to_threadsafe().inner_layout_data()?;
// 1. If any of the following holds true, return null and terminate this algorithm:
@ -665,14 +668,22 @@ pub(crate) fn process_scroll_parent_query(
// - The element is the root element.
// - The element is the body element.
// - The elements computed value of the position property is fixed and no ancestor
// establishes a fixed position containing block.
if flags.intersects(
FragmentFlags::IS_ROOT_ELEMENT | FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT,
) {
//
// Note: We only do this for `scrollParent`, which needs to be null. But `scrollIntoView` on the
// `<body>` or root element should still bring it into view by scrolling the viewport.
if query_type == ScrollContainerQueryType::ForScrollParent &&
flags.intersects(
FragmentFlags::IS_ROOT_ELEMENT | FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT,
)
{
return None;
}
// - The elements computed value of the position property is fixed and no ancestor
// establishes a fixed position containing block.
//
// This is handled below in step 2.
// 2. Let ancestor be the containing block of the element in the flat tree and repeat these substeps:
// - If ancestor is the initial containing block, return the scrollingElement for the
// elements document if it is not closed-shadow-hidden from the element, otherwise
@ -721,7 +732,7 @@ pub(crate) fn process_scroll_parent_query(
}
if ancestor_style.establishes_scroll_container(ancestor_flags) {
return Some(ScrollParentResponse::Element(
return Some(ScrollContainerResponse::Element(
ancestor.as_node().opaque().into(),
));
}
@ -731,7 +742,7 @@ pub(crate) fn process_scroll_parent_query(
match current_position_value {
Position::Fixed => None,
_ => Some(ScrollParentResponse::DocumentScrollingElement),
_ => Some(ScrollContainerResponse::Viewport),
}
}