script/dom/visualviewport.rs
1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::Cell;
6
7use bitflags::bitflags;
8use dom_struct::dom_struct;
9use euclid::{Rect, Scale, Size2D};
10use paint_api::PinchZoomInfos;
11use script_bindings::codegen::GenericBindings::VisualViewportBinding::VisualViewportMethods;
12use script_bindings::codegen::GenericBindings::WindowBinding::WindowMethods;
13use script_bindings::inheritance::Castable;
14use script_bindings::num::Finite;
15use script_bindings::reflector::reflect_dom_object;
16use script_bindings::root::{Dom, DomRoot};
17use script_bindings::script_runtime::CanGc;
18use style_traits::CSSPixel;
19use webrender_api::units::DevicePixel;
20
21use crate::dom::eventtarget::EventTarget;
22use crate::dom::window::Window;
23
24/// <https://drafts.csswg.org/cssom-view/#the-visualviewport-interface>
25#[dom_struct]
26pub(crate) struct VisualViewport {
27 eventtarget: EventTarget,
28
29 /// The associated [`Window`] for this [`VisualViewport`],
30 window: Dom<Window>,
31
32 /// The rectangle bound of the [`VisualViewport`], relative to the layout viewport.
33 #[no_trace]
34 viewport_rect: Cell<Rect<f32, CSSPixel>>,
35
36 /// The scale factor of [`VisualViewport`], which is also commonly known as pinch-zoom.
37 /// <https://drafts.csswg.org/cssom-view/#scale-factor>
38 #[no_trace]
39 scale: Cell<Scale<f32, DevicePixel, DevicePixel>>,
40}
41
42bitflags! {
43 /// The changes of the [`VisualViewport`] after an update to determine events that might need to be fired.
44 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
45 pub struct VisualViewportChanges: u8 {
46 /// Size or the scale factor of the [`VisualViewport`] has changed.
47 const DimensionChanged = 1 << 0;
48 /// The offset or the origin of the [`VisualViewport`] has changed.
49 const OffsetChanged = 1 << 1;
50 }
51}
52
53impl VisualViewport {
54 fn new_inherited(
55 window: &Window,
56 viewport_rect: Rect<f32, CSSPixel>,
57 scale: Scale<f32, DevicePixel, DevicePixel>,
58 ) -> Self {
59 Self {
60 eventtarget: EventTarget::new_inherited(),
61 window: Dom::from_ref(window),
62 viewport_rect: Cell::new(viewport_rect),
63 scale: Cell::new(scale),
64 }
65 }
66
67 /// The initial visual viewport based on a layout viewport relative to the initial containing block, where
68 /// the dimension would be the same as layout viewport leaving the offset and the scale to its default value.
69 pub(crate) fn new_from_layout_viewport(
70 window: &Window,
71 viewport_size: Size2D<f32, CSSPixel>,
72 can_gc: CanGc,
73 ) -> DomRoot<Self> {
74 reflect_dom_object(
75 Box::new(Self::new_inherited(
76 window,
77 Rect::from_size(viewport_size),
78 Scale::identity(),
79 )),
80 window,
81 can_gc,
82 )
83 }
84
85 /// Update just the scale of the [`VisualViewport`] without modifying the rect.
86 /// Used to eagerly apply the initial-scale from a viewport meta tag
87 /// before the paint thread round-trip completes.
88 pub(crate) fn update_scale(&self, scale: f32) {
89 self.scale.set(Scale::new(scale));
90 }
91
92 fn check_for_update(
93 &self,
94 old_rect: Rect<f32, CSSPixel>,
95 old_scale: Scale<f32, DevicePixel, DevicePixel>,
96 ) -> VisualViewportChanges {
97 let mut state = VisualViewportChanges::empty();
98 if old_rect.origin != self.viewport_rect.get().origin {
99 state |= VisualViewportChanges::OffsetChanged;
100 }
101 if old_rect.size != self.viewport_rect.get().size || old_scale != self.scale.get() {
102 state |= VisualViewportChanges::DimensionChanged;
103 }
104 state
105 }
106
107 /// Update the [`VisualViewport`] state based on a new [`PinchZoomInfos`].
108 pub(crate) fn update_from_pinch_zoom_infos(
109 &self,
110 pinch_zoom_infos: PinchZoomInfos,
111 ) -> VisualViewportChanges {
112 let old_rect = self.viewport_rect.replace(pinch_zoom_infos.rect);
113 let old_scale = self.scale.replace(pinch_zoom_infos.zoom_factor);
114
115 self.check_for_update(old_rect, old_scale)
116 }
117
118 /// <https://drafts.csswg.org/cssom-view/#scrolling-events>
119 ///
120 /// > Whenever a visual viewport gets scrolled (whether in response to user
121 /// > interaction or by an API), the user agent must run these steps:
122 pub(crate) fn handle_scroll_event(&self) {
123 // Step 1: Let vv be the VisualViewport object that was scrolled.
124 // Note: This is self.
125 // Step 2: Let doc be vv’s associated document.
126 // Steps 3 and 4 are shared with other scroll targets.
127 self.window
128 .Document()
129 .finish_handle_scroll_event(self.upcast());
130 }
131}
132
133impl VisualViewportMethods<crate::DomTypeHolder> for VisualViewport {
134 /// <https://drafts.csswg.org/cssom-view/#dom-visualviewport-offsetleft>
135 fn OffsetLeft(&self) -> Finite<f64> {
136 // > 1. If the visual viewport’s associated document is not fully active, return 0.
137 if !self.window.Document().is_fully_active() {
138 return Finite::wrap(0.);
139 }
140
141 // > 2. Otherwise, return the offset of the left edge of the visual viewport from the left edge of the
142 // > layout viewport.
143 Finite::wrap(self.viewport_rect.get().min_x() as f64)
144 }
145
146 /// <https://drafts.csswg.org/cssom-view/#dom-visualviewport-offsettop>
147 fn OffsetTop(&self) -> Finite<f64> {
148 // > 1. If the visual viewport’s associated document is not fully active, return 0.
149 if !self.window.Document().is_fully_active() {
150 return Finite::wrap(0.);
151 }
152
153 // > 2. Otherwise, return the offset of the top edge of the visual viewport from the top edge of the
154 // > layout viewport.
155 Finite::wrap(self.viewport_rect.get().min_y() as f64)
156 }
157
158 /// <https://drafts.csswg.org/cssom-view/#dom-visualviewport-pageleft>
159 fn PageLeft(&self) -> Finite<f64> {
160 // > 1. If the visual viewport’s associated document is not fully active, return 0.
161 if !self.window.Document().is_fully_active() {
162 return Finite::wrap(0.);
163 }
164
165 // > 2. Otherwise, return the offset of the left edge of the visual viewport from the left edge of the
166 // > initial containing block of the layout viewport’s document.
167 let page_left = self.viewport_rect.get().min_x() + self.window.scroll_offset().x;
168 Finite::wrap(page_left as f64)
169 }
170
171 /// <https://drafts.csswg.org/cssom-view/#dom-visualviewport-pagetop>
172 fn PageTop(&self) -> Finite<f64> {
173 // > 1. If the visual viewport’s associated document is not fully active, return 0.
174 if !self.window.Document().is_fully_active() {
175 return Finite::wrap(0.);
176 }
177
178 // > 2. Otherwise, return the offset of the top edge of the visual viewport from the top edge of the
179 // > initial containing block of the layout viewport’s document.
180 let page_top = self.viewport_rect.get().min_y() + self.window.scroll_offset().y;
181 Finite::wrap(page_top as f64)
182 }
183
184 /// <https://drafts.csswg.org/cssom-view/#dom-visualviewport-width>
185 fn Width(&self) -> Finite<f64> {
186 // > 1. If the visual viewport’s associated document is not fully active, return 0.
187 if !self.window.Document().is_fully_active() {
188 return Finite::wrap(0.);
189 }
190
191 // > 2. Otherwise, return the width of the visual viewport excluding the width of any rendered vertical
192 // > classic scrollbar that is fixed to the visual viewport.
193 // TODO(#41341): when classic scrollbar is implemented, exclude it's size from visual viewport width.
194 Finite::wrap(self.viewport_rect.get().width() as f64)
195 }
196
197 /// <https://drafts.csswg.org/cssom-view/#dom-visualviewport-height>
198 fn Height(&self) -> Finite<f64> {
199 // > 1. If the visual viewport’s associated document is not fully active, return 0.
200 if !self.window.Document().is_fully_active() {
201 return Finite::wrap(0.);
202 }
203
204 // > 2. Otherwise, return the height of the visual viewport excluding the height of any rendered horizontal
205 // > classic scrollbar that is fixed to the visual viewport.
206 // TODO(#41341): when classic scrollbar is implemented, exclude it's size from visual viewport height.
207 Finite::wrap(self.viewport_rect.get().height() as f64)
208 }
209
210 /// <https://drafts.csswg.org/cssom-view/#dom-visualviewport-scale>
211 fn Scale(&self) -> Finite<f64> {
212 // > 1. If the visual viewport’s associated document is not fully active, return 0.
213 if !self.window.Document().is_fully_active() {
214 return Finite::wrap(0.);
215 }
216
217 // > 2. If there is no output device, return 1 and abort these steps.
218 // TODO(#41341): check for output device.
219
220 // > 3. Otherwise, return the visual viewport’s scale factor.
221 Finite::wrap(self.scale.get().get() as f64)
222 }
223
224 // <https://drafts.csswg.org/cssom-view/#dom-visualviewport-onresize>
225 event_handler!(resize, GetOnresize, SetOnresize);
226
227 // <https://drafts.csswg.org/cssom-view/#dom-visualviewport-onscroll>
228 event_handler!(scroll, GetOnscroll, SetOnscroll);
229
230 // <https://drafts.csswg.org/cssom-view/#dom-visualviewport-onscrollend>
231 event_handler!(scrollend, GetOnscrollend, SetOnscrollend);
232}