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