1use std::cell::Cell;
6
7use app_units::Au;
8use euclid::Vector2D;
9use euclid::default::Rect;
10use layout_api::{AxesOverflow, ScrollContainerQueryFlags};
11use script_bindings::codegen::GenericBindings::WindowBinding::ScrollBehavior;
12use script_bindings::inheritance::Castable;
13use script_bindings::root::DomRoot;
14use style::values::computed::Overflow;
15use webrender_api::units::{LayoutSize, LayoutVector2D};
16
17use crate::dom::bindings::codegen::Bindings::ElementBinding::ScrollLogicalPosition;
18use crate::dom::node::{Node, NodeTraits};
19use crate::dom::types::{Document, Element};
20
21pub(crate) struct ScrollingBox {
22 target: ScrollingBoxSource,
23 overflow: AxesOverflow,
24 cached_content_size: Cell<Option<LayoutSize>>,
25 cached_size: Cell<Option<LayoutSize>>,
26}
27
28pub(crate) enum ScrollingBoxSource {
31 Element(DomRoot<Element>),
32 Viewport(DomRoot<Document>),
33}
34
35#[derive(Copy, Clone)]
36pub(crate) enum ScrollingBoxAxis {
37 X,
38 Y,
39}
40
41#[derive(Copy, Clone)]
42pub(crate) enum ScrollRequirement {
43 Always,
44 IfNotVisible,
45}
46
47impl ScrollRequirement {
48 fn compute_need_scroll(
49 &self,
50 element_start: f32,
51 element_end: f32,
52 container_size: f32,
53 ) -> bool {
54 match self {
55 ScrollRequirement::Always => true,
56 ScrollRequirement::IfNotVisible => {
57 let scrollport_start = 0.;
58 let scrollport_end = container_size;
59
60 element_end <= scrollport_start || element_start >= scrollport_end
61 },
62 }
63 }
64}
65
66#[derive(Copy, Clone)]
67pub(crate) struct ScrollAxisState {
68 pub(crate) position: ScrollLogicalPosition,
69 pub(crate) requirement: ScrollRequirement,
70}
71
72impl ScrollAxisState {
73 pub fn new_always_scroll_position(position: ScrollLogicalPosition) -> Self {
74 ScrollAxisState {
75 position,
76 requirement: ScrollRequirement::Always,
77 }
78 }
79}
80
81impl ScrollingBox {
82 pub(crate) fn new(target: ScrollingBoxSource, overflow: AxesOverflow) -> Self {
83 Self {
84 target,
85 overflow,
86 cached_content_size: Default::default(),
87 cached_size: Default::default(),
88 }
89 }
90
91 pub(crate) fn target(&self) -> &ScrollingBoxSource {
92 &self.target
93 }
94
95 pub(crate) fn is_viewport(&self) -> bool {
96 matches!(self.target, ScrollingBoxSource::Viewport(..))
97 }
98
99 pub(crate) fn scroll_position(&self) -> LayoutVector2D {
100 match &self.target {
101 ScrollingBoxSource::Element(element) => element
102 .owner_window()
103 .scroll_offset_query(element.upcast::<Node>()),
104 ScrollingBoxSource::Viewport(document) => document.window().scroll_offset(),
105 }
106 }
107
108 pub(crate) fn content_size(&self) -> LayoutSize {
109 if let Some(content_size) = self.cached_content_size.get() {
110 return content_size;
111 }
112
113 let (document, node_to_query) = match &self.target {
114 ScrollingBoxSource::Element(element) => {
115 (element.owner_document(), Some(element.upcast()))
116 },
117 ScrollingBoxSource::Viewport(document) => (document.clone(), None),
118 };
119
120 let content_size = document
121 .window()
122 .scrolling_area_query(node_to_query)
123 .size
124 .to_f32()
125 .cast_unit();
126 self.cached_content_size.set(Some(content_size));
127 content_size
128 }
129
130 pub(crate) fn size(&self) -> LayoutSize {
131 if let Some(size) = self.cached_size.get() {
132 return size;
133 }
134
135 let size = match &self.target {
136 ScrollingBoxSource::Element(element) => element.client_rect().size.to_f32().cast_unit(),
137 ScrollingBoxSource::Viewport(document) => {
138 document.window().viewport_details().size.cast_unit()
139 },
140 };
141 self.cached_size.set(Some(size));
142 size
143 }
144
145 pub(crate) fn parent(&self) -> Option<ScrollingBox> {
146 match &self.target {
147 ScrollingBoxSource::Element(element) => {
148 element.scrolling_box(ScrollContainerQueryFlags::empty())
149 },
150 ScrollingBoxSource::Viewport(_) => None,
151 }
152 }
153
154 pub(crate) fn node(&self) -> &Node {
155 match &self.target {
156 ScrollingBoxSource::Element(element) => element.upcast(),
157 ScrollingBoxSource::Viewport(document) => document.upcast(),
158 }
159 }
160
161 pub(crate) fn scroll_to(&self, position: LayoutVector2D, behavior: ScrollBehavior) {
162 match &self.target {
163 ScrollingBoxSource::Element(element) => {
164 element
165 .owner_window()
166 .scroll_an_element(element, position.x, position.y, behavior);
167 },
168 ScrollingBoxSource::Viewport(document) => {
169 document.window().scroll(position.x, position.y, behavior);
170 },
171 }
172 }
173
174 pub(crate) fn can_keyboard_scroll_in_axis(&self, axis: ScrollingBoxAxis) -> bool {
175 let overflow = match axis {
176 ScrollingBoxAxis::X => self.overflow.x,
177 ScrollingBoxAxis::Y => self.overflow.y,
178 };
179 if overflow == Overflow::Hidden {
180 return false;
181 }
182 match axis {
183 ScrollingBoxAxis::X => self.content_size().width > self.size().width,
184 ScrollingBoxAxis::Y => self.content_size().height > self.size().height,
185 }
186 }
187
188 pub(crate) fn determine_scroll_into_view_position(
190 &self,
191 block: ScrollAxisState,
192 inline: ScrollAxisState,
193 target_rect: Rect<Au>,
194 ) -> LayoutVector2D {
195 let device_pixel_ratio = self.node().owner_window().device_pixel_ratio().get();
196 let to_pixel = |value: Au| value.to_nearest_pixel(device_pixel_ratio);
197
198 let target_top_left = target_rect.origin.map(to_pixel).to_untyped();
203 let target_bottom_right = target_rect.max().map(to_pixel);
204
205 let (adjusted_element_top_left, adjusted_element_bottom_right) = match self.target() {
210 ScrollingBoxSource::Viewport(_) => (target_top_left, target_bottom_right),
211 ScrollingBoxSource::Element(scrolling_element) => {
212 let scrolling_padding_rect_top_left = scrolling_element
213 .upcast::<Node>()
214 .padding_box()
215 .unwrap_or_default()
216 .origin
217 .map(to_pixel);
218 (
219 target_top_left - scrolling_padding_rect_top_left.to_vector(),
220 target_bottom_right - scrolling_padding_rect_top_left.to_vector(),
221 )
222 },
223 };
224
225 let size = self.size();
226 let current_scroll_position = self.scroll_position();
227 Vector2D::new(
228 Self::calculate_scroll_position_one_axis(
229 inline,
230 adjusted_element_top_left.x,
231 adjusted_element_bottom_right.x,
232 size.width,
233 current_scroll_position.x,
234 ),
235 Self::calculate_scroll_position_one_axis(
236 block,
237 adjusted_element_top_left.y,
238 adjusted_element_bottom_right.y,
239 size.height,
240 current_scroll_position.y,
241 ),
242 )
243 }
244
245 fn calculate_scroll_position_one_axis(
248 state: ScrollAxisState,
249 element_start: f32,
250 element_end: f32,
251 container_size: f32,
252 current_scroll_offset: f32,
253 ) -> f32 {
254 if !state
255 .requirement
256 .compute_need_scroll(element_start, element_end, container_size)
257 {
258 return current_scroll_offset;
259 }
260
261 let element_size = element_end - element_start;
262
263 current_scroll_offset +
264 match state.position {
265 ScrollLogicalPosition::Start => element_start,
267 ScrollLogicalPosition::End => element_end - container_size,
270 ScrollLogicalPosition::Center => {
273 element_start + (element_size - container_size) / 2.0
274 },
275 ScrollLogicalPosition::Nearest => {
277 let scrollport_start = 0.;
278 let scrollport_end = container_size;
279
280 if (element_start < scrollport_start && element_size <= container_size) ||
285 (element_end > scrollport_end && element_size >= container_size)
286 {
287 element_start
288 }
289 else if (element_end > scrollport_end && element_size < container_size) ||
294 (element_start < scrollport_start && element_size > container_size)
295 {
296 element_end - container_size
297 }
298 else {
301 0.
302 }
303 },
304 }
305 }
306}