/* 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 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 webrender_api::units::{LayoutSize, LayoutVector2D}; use crate::dom::node::{Node, NodeTraits}; use crate::dom::types::{Document, Element}; pub(crate) struct ScrollingBox { target: ScrollingBoxSource, overflow: AxesOverflow, cached_content_size: Cell>, cached_size: Cell>, } /// Represents a scrolling box that can be either an element or the viewport /// pub(crate) enum ScrollingBoxSource { Element(DomRoot), Viewport(DomRoot), } #[derive(Copy, Clone)] pub(crate) enum ScrollingBoxAxis { X, Y, } 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::()), 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) -> LayoutSize { if let Some(size) = self.cached_size.get() { return size; } let size = match &self.target { ScrollingBoxSource::Element(element) => element.client_rect().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 { 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, position: LayoutVector2D, behavior: ScrollBehavior) { match &self.target { ScrollingBoxSource::Element(element) => { element .owner_window() .scroll_an_element(element, position.x, position.y, behavior); }, ScrollingBoxSource::Viewport(document) => { document.window().scroll(position.x, position.y, behavior); }, } } pub(crate) fn can_keyboard_scroll_in_axis(&self, 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().width, ScrollingBoxAxis::Y => self.content_size().height > self.size().height, } } }