1use std::cell::Cell;
6
7use app_units::Au;
8use euclid::{Rect, Vector2D};
9use js::context::{JSContext, NoGC};
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, no_gc: &NoGC) -> 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) => {
138 element.client_rect(no_gc).size.to_f32().cast_unit()
139 },
140 ScrollingBoxSource::Viewport(document) => {
141 document.window().viewport_details().size.cast_unit()
142 },
143 };
144 self.cached_size.set(Some(size));
145 size
146 }
147
148 pub(crate) fn parent(&self) -> Option<ScrollingBox> {
149 match &self.target {
150 ScrollingBoxSource::Element(element) => {
151 element.scrolling_box(ScrollContainerQueryFlags::empty())
152 },
153 ScrollingBoxSource::Viewport(_) => None,
154 }
155 }
156
157 pub(crate) fn node(&self) -> &Node {
158 match &self.target {
159 ScrollingBoxSource::Element(element) => element.upcast(),
160 ScrollingBoxSource::Viewport(document) => document.upcast(),
161 }
162 }
163
164 pub(crate) fn scroll_to(
165 &self,
166 cx: &mut JSContext,
167 position: LayoutVector2D,
168 behavior: ScrollBehavior,
169 ) {
170 match &self.target {
171 ScrollingBoxSource::Element(element) => {
172 element
173 .owner_window()
174 .scroll_an_element(cx, element, position.x, position.y, behavior);
175 },
176 ScrollingBoxSource::Viewport(document) => {
177 document
178 .window()
179 .scroll(cx, position.x, position.y, behavior);
180 },
181 }
182 }
183
184 pub(crate) fn can_keyboard_scroll_in_axis(&self, no_gc: &NoGC, axis: ScrollingBoxAxis) -> bool {
185 let overflow = match axis {
186 ScrollingBoxAxis::X => self.overflow.x,
187 ScrollingBoxAxis::Y => self.overflow.y,
188 };
189 if overflow == Overflow::Hidden {
190 return false;
191 }
192 match axis {
193 ScrollingBoxAxis::X => self.content_size().width > self.size(no_gc).width,
194 ScrollingBoxAxis::Y => self.content_size().height > self.size(no_gc).height,
195 }
196 }
197
198 pub(crate) fn determine_scroll_into_view_position(
200 &self,
201 no_gc: &NoGC,
202 block: ScrollAxisState,
203 inline: ScrollAxisState,
204 target_rect: Rect<Au, CSSPixel>,
205 ) -> LayoutVector2D {
206 let device_pixel_ratio = self.node().owner_window().device_pixel_ratio().get();
207 let to_pixel = |value: Au| value.to_nearest_pixel(device_pixel_ratio);
208
209 let target_top_left = target_rect.origin.map(to_pixel);
214 let target_bottom_right = target_rect.max().map(to_pixel);
215
216 let (adjusted_element_top_left, adjusted_element_bottom_right) = match self.target() {
221 ScrollingBoxSource::Viewport(_) => (target_top_left, target_bottom_right),
222 ScrollingBoxSource::Element(scrolling_element) => {
223 let scrolling_padding_rect_top_left = scrolling_element
224 .upcast::<Node>()
225 .padding_box()
226 .unwrap_or_default()
227 .origin
228 .map(to_pixel);
229 (
230 target_top_left - scrolling_padding_rect_top_left.to_vector(),
231 target_bottom_right - scrolling_padding_rect_top_left.to_vector(),
232 )
233 },
234 };
235
236 let size = self.size(no_gc);
237 let current_scroll_position = self.scroll_position();
238 Vector2D::new(
239 Self::calculate_scroll_position_one_axis(
240 inline,
241 adjusted_element_top_left.x,
242 adjusted_element_bottom_right.x,
243 size.width,
244 current_scroll_position.x,
245 ),
246 Self::calculate_scroll_position_one_axis(
247 block,
248 adjusted_element_top_left.y,
249 adjusted_element_bottom_right.y,
250 size.height,
251 current_scroll_position.y,
252 ),
253 )
254 }
255
256 fn calculate_scroll_position_one_axis(
259 state: ScrollAxisState,
260 element_start: f32,
261 element_end: f32,
262 container_size: f32,
263 current_scroll_offset: f32,
264 ) -> f32 {
265 if !state
266 .requirement
267 .compute_need_scroll(element_start, element_end, container_size)
268 {
269 return current_scroll_offset;
270 }
271
272 let element_size = element_end - element_start;
273
274 current_scroll_offset +
275 match state.position {
276 ScrollLogicalPosition::Start => element_start,
278 ScrollLogicalPosition::End => element_end - container_size,
281 ScrollLogicalPosition::Center => {
284 element_start + (element_size - container_size) / 2.0
285 },
286 ScrollLogicalPosition::Nearest => {
288 let scrollport_start = 0.;
289 let scrollport_end = container_size;
290
291 if (element_start < scrollport_start && element_size <= container_size) ||
296 (element_end > scrollport_end && element_size >= container_size)
297 {
298 element_start
299 }
300 else if (element_end > scrollport_end && element_size < container_size) ||
305 (element_start < scrollport_start && element_size > container_size)
306 {
307 element_end - container_size
308 }
309 else {
312 0.
313 }
314 },
315 }
316 }
317}