script: Reduce code duplication in the implementation of scrollIntoView (#39407)

- Expose a couple helpers on `ScrollingBox` that will also be used for
  keyboard scrolling.
- Calculate `scrollIntoView` positions using points rather than doing
  things by axis components. This greatly reduces the amount of code in
  the implementation.

Testing: This is just a refactor so shouldn't change any tests.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Martin Robinson 2025-09-20 15:40:58 +02:00 committed by GitHub
parent bdf630563c
commit 629b7dba3d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -12,6 +12,7 @@ use std::rc::Rc;
use std::str::FromStr; use std::str::FromStr;
use std::{fmt, mem}; use std::{fmt, mem};
use app_units::Au;
use cssparser::match_ignore_ascii_case; use cssparser::match_ignore_ascii_case;
use devtools_traits::AttrInfo; use devtools_traits::AttrInfo;
use dom_struct::dom_struct; use dom_struct::dom_struct;
@ -61,7 +62,7 @@ use style::values::{AtomIdent, AtomString, CSSFloat, computed, specified};
use style::{ArcSlice, CaseSensitivityExt, dom_apis, thread_state}; use style::{ArcSlice, CaseSensitivityExt, dom_apis, thread_state};
use stylo_atoms::Atom; use stylo_atoms::Atom;
use stylo_dom::ElementState; use stylo_dom::ElementState;
use webrender_api::units::LayoutVector2D; use webrender_api::units::{LayoutSize, LayoutVector2D};
use xml5ever::serialize::TraversalScope::{ use xml5ever::serialize::TraversalScope::{
ChildrenOnly as XmlChildrenOnly, IncludeNode as XmlIncludeNode, ChildrenOnly as XmlChildrenOnly, IncludeNode as XmlIncludeNode,
}; };
@ -291,6 +292,15 @@ impl ScrollingBox {
} }
} }
fn size(&self) -> LayoutSize {
match self {
ScrollingBox::Element(element) => element.client_rect().size.to_f32().cast_unit(),
ScrollingBox::Viewport(document) => {
document.window().viewport_details().size.cast_unit()
},
}
}
fn parent(&self) -> Option<ScrollingBox> { fn parent(&self) -> Option<ScrollingBox> {
match self { match self {
ScrollingBox::Element(element) => element.scrolling_box(), ScrollingBox::Element(element) => element.scrolling_box(),
@ -304,6 +314,19 @@ impl ScrollingBox {
ScrollingBox::Viewport(document) => document.upcast(), ScrollingBox::Viewport(document) => document.upcast(),
} }
} }
pub(crate) fn scroll_to(&self, position: LayoutVector2D, behavior: ScrollBehavior) {
match self {
ScrollingBox::Element(element) => {
element
.owner_window()
.scroll_an_element(element, position.x, position.y, behavior);
},
ScrollingBox::Viewport(document) => {
document.window().scroll(position.x, position.y, behavior);
},
}
}
} }
// //
@ -916,26 +939,17 @@ impl Element {
// //
// TODO: Handle smooth scrolling. // TODO: Handle smooth scrolling.
if position != scrolling_box.scroll_position() { if position != scrolling_box.scroll_position() {
match &scrolling_box { // ↪ If `scrolling box` is associated with an element
// ↪ If `scrolling box` is associated with an element // Perform a scroll of the elements scrolling box to `position`,
ScrollingBox::Element(element) => { // with the `element` as the associated element and `behavior` as the
// Perform a scroll of the elements scrolling box to `position`, // scroll behavior.
// with the `element` as the associated element and `behavior` as the // ↪ If `scrolling box` is associated with a viewport
// scroll behavior. // Step 1: Let `document` be the viewports associated Document.
element // Step 2: Let `root element` be documents root element, if there is one, or
.owner_window() // null otherwise.
.scroll_an_element(element, position.x, position.y, behavior); // Step 3: Perform a scroll of the viewport to `position`, with `root element`
}, // as the associated element and `behavior` as the scroll behavior.
// ↪ If `scrolling box` is associated with a viewport scrolling_box.scroll_to(position, behavior);
ScrollingBox::Viewport(document) => {
// Step 1: Let `document` be the viewports associated Document.
// Step 2: Let `root element` be documents root element, if there is one, or
// null otherwise.
// Step 3: Perform a scroll of the viewport to `position`, with `root element`
// as the associated element and `behavior` as the scroll behavior.
document.window().scroll(position.x, position.y, behavior);
},
}
} }
// Step 1.4: If `container` is not null and either `scrolling box` is a shadow-including // Step 1.4: If `container` is not null and either `scrolling box` is a shadow-including
@ -969,96 +983,40 @@ impl Element {
.get(); .get();
// Target element bounds // Target element bounds
let element_left = target_bounding_box let to_pixel = |value: Au| value.to_nearest_pixel(device_pixel_ratio);
.origin let element_top_left = target_bounding_box.origin.map(to_pixel).to_untyped();
.x let element_bottom_right = target_bounding_box.max().map(to_pixel);
.to_nearest_pixel(device_pixel_ratio); let element_size = target_bounding_box.size.to_vector().map(to_pixel);
let element_top = target_bounding_box
.origin
.y
.to_nearest_pixel(device_pixel_ratio);
let element_width = target_bounding_box
.size
.width
.to_nearest_pixel(device_pixel_ratio);
let element_height = target_bounding_box
.size
.height
.to_nearest_pixel(device_pixel_ratio);
let element_right = element_left + element_width;
let element_bottom = element_top + element_height;
let (target_x, target_y) = match scrolling_box {
ScrollingBox::Viewport(document) => {
let window = document.window();
let viewport_width = window.InnerWidth() as f32;
let viewport_height = window.InnerHeight() as f32;
let current_scroll_x = window.ScrollX() as f32;
let current_scroll_y = window.ScrollY() as f32;
let scrolling_box_size = scrolling_box.size();
let current_scroll_position = scrolling_box.scroll_position().to_untyped();
let (adjusted_element_top_left, adjusted_element_bottom_right) = match scrolling_box {
ScrollingBox::Viewport(_) => {
// For viewport scrolling, we need to add current scroll to get document-relative positions // For viewport scrolling, we need to add current scroll to get document-relative positions
let document_element_left = element_left + current_scroll_x;
let document_element_top = element_top + current_scroll_y;
let document_element_right = element_right + current_scroll_x;
let document_element_bottom = element_bottom + current_scroll_y;
( (
self.calculate_scroll_position_one_axis( element_top_left + current_scroll_position,
inline, element_bottom_right + current_scroll_position,
document_element_left,
document_element_right,
element_width,
viewport_width,
current_scroll_x,
),
self.calculate_scroll_position_one_axis(
block,
document_element_top,
document_element_bottom,
element_height,
viewport_height,
current_scroll_y,
),
) )
}, },
ScrollingBox::Element(scrolling_element) => { ScrollingBox::Element(scrolling_element) => {
let scrolling_node = scrolling_element.upcast::<Node>(); let scrolling_node = scrolling_element.upcast::<Node>();
let scrolling_box = scrolling_node.border_box().unwrap_or_default(); let scrolling_box = scrolling_node.border_box().unwrap_or_default();
let scrolling_left = scrolling_box.origin.x.to_nearest_pixel(device_pixel_ratio); let scrolling_top_left = scrolling_box.origin.map(to_pixel);
let scrolling_top = scrolling_box.origin.y.to_nearest_pixel(device_pixel_ratio);
let scrolling_width = scrolling_box
.size
.width
.to_nearest_pixel(device_pixel_ratio);
let scrolling_height = scrolling_box
.size
.height
.to_nearest_pixel(device_pixel_ratio);
// Calculate element position in scroller's content coordinate system // Calculate element position in scroller's content coordinate system
// Element's viewport position relative to scroller, then add scroll offset to get content position // Element's viewport position relative to scroller, then add scroll offset to get content position
let viewport_relative_left = element_left - scrolling_left; let viewport_relative_top_left = element_top_left - scrolling_top_left.to_vector();
let viewport_relative_top = element_top - scrolling_top; let viewport_relative_bottom_right =
let viewport_relative_right = element_right - scrolling_left; element_bottom_right - scrolling_top_left.to_vector();
let viewport_relative_bottom = element_bottom - scrolling_top;
// For absolutely positioned elements, we need to account for the positioning context // For absolutely positioned elements, we need to account for the positioning context
// If the element is positioned relative to an ancestor that's within the scrolling container, // If the element is positioned relative to an ancestor that's within the scrolling container,
// we need to adjust coordinates accordingly // we need to adjust coordinates accordingly
let ( let (adjusted_relative_top_left, adjusted_relative_bottom_right) = {
adjusted_relative_left,
adjusted_relative_top,
adjusted_relative_right,
adjusted_relative_bottom,
) = {
// Check if this element has a positioned ancestor between it and the scrolling container // Check if this element has a positioned ancestor between it and the scrolling container
let mut current_node = self.upcast::<Node>().GetParentNode(); let mut current_node = self.upcast::<Node>().GetParentNode();
let mut final_coords = ( let mut final_coords =
viewport_relative_left, (viewport_relative_top_left, viewport_relative_bottom_right);
viewport_relative_top,
viewport_relative_right,
viewport_relative_bottom,
);
while let Some(node) = current_node { while let Some(node) = current_node {
// Stop if we reach the scrolling container // Stop if we reach the scrolling container
@ -1075,25 +1033,15 @@ impl Element {
// If this element establishes a positioning context, // If this element establishes a positioning context,
// Get its bounding box to calculate the offset // Get its bounding box to calculate the offset
let positioning_box = node.border_box().unwrap_or_default(); let positioning_box = node.border_box().unwrap_or_default();
let positioning_left = positioning_box let positioning_top_left = positioning_box.origin.map(to_pixel);
.origin
.x
.to_nearest_pixel(device_pixel_ratio);
let positioning_top = positioning_box
.origin
.y
.to_nearest_pixel(device_pixel_ratio);
// Calculate the offset of the positioning context relative to the scrolling container // Calculate the offset of the positioning context relative to the scrolling container
let offset_left = positioning_left - scrolling_left; let offset_top_left = positioning_top_left - scrolling_top_left;
let offset_top = positioning_top - scrolling_top;
// Adjust the coordinates by subtracting the positioning context offset // Adjust the coordinates by subtracting the positioning context offset
final_coords = ( final_coords = (
viewport_relative_left - offset_left, viewport_relative_top_left - offset_top_left,
viewport_relative_top - offset_top, viewport_relative_bottom_right - offset_top_left,
viewport_relative_right - offset_left,
viewport_relative_bottom - offset_top,
); );
break; break;
} }
@ -1106,35 +1054,32 @@ impl Element {
final_coords final_coords
}; };
let current_scroll_x = scrolling_element.ScrollLeft() as f32; let content_element_top_left = adjusted_relative_top_left + current_scroll_position;
let current_scroll_y = scrolling_element.ScrollTop() as f32; let content_element_bottom_right =
let content_element_left = adjusted_relative_left + current_scroll_x; adjusted_relative_bottom_right + current_scroll_position;
let content_element_top = adjusted_relative_top + current_scroll_y;
let content_element_right = adjusted_relative_right + current_scroll_x;
let content_element_bottom = adjusted_relative_bottom + current_scroll_y;
( (content_element_top_left, content_element_bottom_right)
self.calculate_scroll_position_one_axis(
inline,
content_element_left,
content_element_right,
element_width,
scrolling_width,
current_scroll_x,
),
self.calculate_scroll_position_one_axis(
block,
content_element_top,
content_element_bottom,
element_height,
scrolling_height,
current_scroll_y,
),
)
}, },
}; };
Vector2D::new(target_x, target_y) Vector2D::new(
self.calculate_scroll_position_one_axis(
inline,
adjusted_element_top_left.x,
adjusted_element_bottom_right.x,
element_size.x,
scrolling_box_size.width,
current_scroll_position.x,
),
self.calculate_scroll_position_one_axis(
block,
adjusted_element_top_left.y,
adjusted_element_bottom_right.y,
element_size.y,
scrolling_box_size.height,
current_scroll_position.y,
),
)
} }
fn calculate_scroll_position_one_axis( fn calculate_scroll_position_one_axis(