/
githubmirror
/
servo
Обзор
Документация
Войти
/
githubmirror
/
servo
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
components/script/dom/window/scrolling_box.rs
317 строк
12 KB
Tim van der Lippe
script: Move individual interfaces into subfolders (#47060)
07 авг 2026, 12:10
Не верифицирован
07 авг 2026, 12:10
33036ff
Код
Авторство
О чём код?
/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::cell::Cell; use app_units::Au; use euclid::{Rect, Vector2D}; use js::context::{JSContext, NoGC}; use layout_api::{AxesOverflow, ScrollContainerQueryFlags}; use script_bindings::codegen::GenericBindings::WindowBinding::ScrollBehavior; use script_bindings::inheritance::Castable; use script_bindings::root::DomRoot; use style::values::computed::Overflow; use style_traits::CSSPixel; use webrender_api::units::{LayoutSize, LayoutVector2D}; use crate::dom::bindings::codegen::Bindings::ElementBinding::ScrollLogicalPosition; use crate::dom::node::{Node, NodeTraits}; use crate::dom::types::{Document, Element}; pub(crate) struct ScrollingBox { target: ScrollingBoxSource, overflow: AxesOverflow, cached_content_size: Cell<Option<LayoutSize>>, cached_size: Cell<Option<LayoutSize>>, } /// Represents a scrolling box that can be either an element or the viewport /// <https://drafts.csswg.org/cssom-view/#scrolling-box> pub(crate) enum ScrollingBoxSource { Element(DomRoot<Element>), Viewport(DomRoot<Document>), } #[derive(Copy, Clone)] pub(crate) enum ScrollingBoxAxis { X, Y, } #[derive(Copy, Clone)] pub(crate) enum ScrollRequirement { Always, IfNotVisible, } impl ScrollRequirement { fn compute_need_scroll( &self, element_start: f32, element_end: f32, container_size: f32, ) -> bool { match self { ScrollRequirement::Always => true, ScrollRequirement::IfNotVisible => { let scrollport_start = 0.; let scrollport_end = container_size; element_end <= scrollport_start || element_start >= scrollport_end }, } } } #[derive(Copy, Clone)] pub(crate) struct ScrollAxisState { pub(crate) position: ScrollLogicalPosition, pub(crate) requirement: ScrollRequirement, } impl ScrollAxisState { pub fn new_always_scroll_position(position: ScrollLogicalPosition) -> Self { ScrollAxisState { position, requirement: ScrollRequirement::Always, } } } impl ScrollingBox { pub(crate) fn new(target: ScrollingBoxSource, overflow: AxesOverflow) -> Self { Self { target, overflow, cached_content_size: Default::default(), cached_size: Default::default(), } } pub(crate) fn target(&self) -> &ScrollingBoxSource { &self.target } pub(crate) fn is_viewport(&self) -> bool { matches!(self.target, ScrollingBoxSource::Viewport(..)) } pub(crate) fn scroll_position(&self) -> LayoutVector2D { match &self.target { ScrollingBoxSource::Element(element) => element .owner_window() .scroll_offset_query(element.upcast::<Node>()), ScrollingBoxSource::Viewport(document) => document.window().scroll_offset(), } } pub(crate) fn content_size(&self) -> LayoutSize { if let Some(content_size) = self.cached_content_size.get() { return content_size; } let (document, node_to_query) = match &self.target { ScrollingBoxSource::Element(element) => { (element.owner_document(), Some(element.upcast())) }, ScrollingBoxSource::Viewport(document) => (document.clone(), None), }; let content_size = document .window() .scrolling_area_query(node_to_query) .size .to_f32() .cast_unit(); self.cached_content_size.set(Some(content_size)); content_size } pub(crate) fn size(&self, no_gc: &NoGC) -> LayoutSize { if let Some(size) = self.cached_size.get() { return size; } let size = match &self.target { ScrollingBoxSource::Element(element) => { element.client_rect(no_gc).size.to_f32().cast_unit() }, ScrollingBoxSource::Viewport(document) => { document.window().viewport_details().size.cast_unit() }, }; self.cached_size.set(Some(size)); size } pub(crate) fn parent(&self) -> Option<ScrollingBox> { match &self.target { ScrollingBoxSource::Element(element) => { element.scrolling_box(ScrollContainerQueryFlags::empty()) }, ScrollingBoxSource::Viewport(_) => None, } } pub(crate) fn node(&self) -> &Node { match &self.target { ScrollingBoxSource::Element(element) => element.upcast(), ScrollingBoxSource::Viewport(document) => document.upcast(), } } pub(crate) fn scroll_to( &self, cx: &mut JSContext, position: LayoutVector2D, behavior: ScrollBehavior, ) { match &self.target { ScrollingBoxSource::Element(element) => { element .owner_window() .scroll_an_element(cx, element, position.x, position.y, behavior); }, ScrollingBoxSource::Viewport(document) => { document .window() .scroll(cx, position.x, position.y, behavior); }, } } pub(crate) fn can_keyboard_scroll_in_axis(&self, no_gc: &NoGC, axis: ScrollingBoxAxis) -> bool { let overflow = match axis { ScrollingBoxAxis::X => self.overflow.x, ScrollingBoxAxis::Y => self.overflow.y, }; if overflow == Overflow::Hidden { return false; } match axis { ScrollingBoxAxis::X => self.content_size().width > self.size(no_gc).width, ScrollingBoxAxis::Y => self.content_size().height > self.size(no_gc).height, } } /// <https://drafts.csswg.org/cssom-view/#determine-the-scroll-into-view-position> pub(crate) fn determine_scroll_into_view_position( &self, no_gc: &NoGC, block: ScrollAxisState, inline: ScrollAxisState, target_rect: Rect<Au, CSSPixel>, ) -> LayoutVector2D { let device_pixel_ratio = self.node().owner_window().device_pixel_ratio().get(); let to_pixel = |value: Au| value.to_nearest_pixel(device_pixel_ratio); // Step 1 should be handled by the caller, and provided as |target_rect|. // > Let target bounding border box be the box represented by the return value // > of invoking Element’s getBoundingClientRect(), if target is an Element, // > or Range’s getBoundingClientRect(), if target is a Range. let target_top_left = target_rect.origin.map(to_pixel); let target_bottom_right = target_rect.max().map(to_pixel); // The rest of the steps diverge from the specification here, but essentially try // to follow it using our own geometry types. // // TODO: This makes the code below wrong for the purposes of writing modes. let (adjusted_element_top_left, adjusted_element_bottom_right) = match self.target() { ScrollingBoxSource::Viewport(_) => (target_top_left, target_bottom_right), ScrollingBoxSource::Element(scrolling_element) => { let scrolling_padding_rect_top_left = scrolling_element .upcast::<Node>() .padding_box() .unwrap_or_default() .origin .map(to_pixel); ( target_top_left - scrolling_padding_rect_top_left.to_vector(), target_bottom_right - scrolling_padding_rect_top_left.to_vector(), ) }, }; let size = self.size(no_gc); let current_scroll_position = self.scroll_position(); Vector2D::new( Self::calculate_scroll_position_one_axis( inline, adjusted_element_top_left.x, adjusted_element_bottom_right.x, size.width, current_scroll_position.x, ), Self::calculate_scroll_position_one_axis( block, adjusted_element_top_left.y, adjusted_element_bottom_right.y, size.height, current_scroll_position.y, ), ) } /// Step 10 from <https://drafts.csswg.org/cssom-view/#determine-the-scroll-into-view-position>: // TODO: we are not considering the coordinate system of the element while deciding the scroll position. fn calculate_scroll_position_one_axis( state: ScrollAxisState, element_start: f32, element_end: f32, container_size: f32, current_scroll_offset: f32, ) -> f32 { if !state .requirement .compute_need_scroll(element_start, element_end, container_size) { return current_scroll_offset; } let element_size = element_end - element_start; current_scroll_offset + match state.position { // Step 1 & 5: If inline is "start", then align element start edge with scrolling box start edge. ScrollLogicalPosition::Start => element_start, // Step 2 & 6: If inline is "end", then align element end edge with // scrolling box end edge. ScrollLogicalPosition::End => element_end - container_size, // Step 3 & 7: If inline is "center", then align the center of target bounding // border box with the center of scrolling box in scrolling box’s inline base direction. ScrollLogicalPosition::Center => { element_start + (element_size - container_size) / 2.0 }, // Step 4 & 8: If inline is "nearest", ScrollLogicalPosition::Nearest => { let scrollport_start = 0.; let scrollport_end = container_size; // Step 4.2 & 8.2: If element start edge is outside scrolling box start edge and element // size is less than scrolling box size or If element end edge is outside // scrolling box end edge and element size is greater than scrolling box size: // Align element start edge with scrolling box start edge. if (element_start < scrollport_start && element_size <= container_size) || (element_end > scrollport_end && element_size >= container_size) { element_start } // Step 4.3 & 8.3: If element end edge is outside scrolling box start edge and element // size is greater than scrolling box size or If element start edge is outside // scrolling box end edge and element size is less than scrolling box size: // Align element end edge with scrolling box end edge. else if (element_end > scrollport_end && element_size < container_size) || (element_start < scrollport_start && element_size > container_size) { element_end - container_size } // Step 4.1 & 8.1: If element start edge and element end edge are both outside scrolling // box start edge and scrolling box end edge or an invalid situation: Do nothing. else { 0. } }, } } }