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::root::{Dom, DomRoot};
16use script_bindings::script_runtime::CanGc;
17use style_traits::CSSPixel;
18use webrender_api::units::DevicePixel;
19
20use crate::dom::bindings::reflector::reflect_dom_object;
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    fn check_for_update(
86        &self,
87        old_rect: Rect<f32, CSSPixel>,
88        old_scale: Scale<f32, DevicePixel, DevicePixel>,
89    ) -> VisualViewportChanges {
90        let mut state = VisualViewportChanges::empty();
91        if old_rect.origin != self.viewport_rect.get().origin {
92            state |= VisualViewportChanges::OffsetChanged;
93        }
94        if old_rect.size != self.viewport_rect.get().size || old_scale != self.scale.get() {
95            state |= VisualViewportChanges::DimensionChanged;
96        }
97        state
98    }
99
100    /// Update the [`VisualViewport`] state based on a new [`PinchZoomInfos`].
101    pub(crate) fn update_from_pinch_zoom_infos(
102        &self,
103        pinch_zoom_infos: PinchZoomInfos,
104    ) -> VisualViewportChanges {
105        let old_rect = self.viewport_rect.replace(pinch_zoom_infos.rect);
106        let old_scale = self.scale.replace(pinch_zoom_infos.zoom_factor);
107
108        self.check_for_update(old_rect, old_scale)
109    }
110
111    /// <https://drafts.csswg.org/cssom-view/#scrolling-events>
112    ///
113    /// > Whenever a visual viewport gets scrolled (whether in response to user
114    /// > interaction or by an API), the user agent must run these steps:
115    pub(crate) fn handle_scroll_event(&self) {
116        // Step 1:  Let vv be the VisualViewport object that was scrolled.
117        // Note: This is self.
118        // Step 2: Let doc be vv’s associated document.
119        // Steps 3 and 4 are shared with other scroll targets.
120        self.window
121            .Document()
122            .finish_handle_scroll_event(self.upcast());
123    }
124}
125
126impl VisualViewportMethods<crate::DomTypeHolder> for VisualViewport {
127    /// <https://drafts.csswg.org/cssom-view/#dom-visualviewport-offsetleft>
128    fn OffsetLeft(&self) -> Finite<f64> {
129        // > 1. If the visual viewport’s associated document is not fully active, return 0.
130        if !self.window.Document().is_fully_active() {
131            return Finite::wrap(0.);
132        }
133
134        // > 2. Otherwise, return the offset of the left edge of the visual viewport from the left edge of the
135        // >    layout viewport.
136        Finite::wrap(self.viewport_rect.get().min_x() as f64)
137    }
138
139    /// <https://drafts.csswg.org/cssom-view/#dom-visualviewport-offsettop>
140    fn OffsetTop(&self) -> Finite<f64> {
141        // > 1. If the visual viewport’s associated document is not fully active, return 0.
142        if !self.window.Document().is_fully_active() {
143            return Finite::wrap(0.);
144        }
145
146        // > 2. Otherwise, return the offset of the top edge of the visual viewport from the top edge of the
147        // >    layout viewport.
148        Finite::wrap(self.viewport_rect.get().min_y() as f64)
149    }
150
151    /// <https://drafts.csswg.org/cssom-view/#dom-visualviewport-pageleft>
152    fn PageLeft(&self) -> Finite<f64> {
153        // > 1. If the visual viewport’s associated document is not fully active, return 0.
154        if !self.window.Document().is_fully_active() {
155            return Finite::wrap(0.);
156        }
157
158        // > 2. Otherwise, return the offset of the left edge of the visual viewport from the left edge of the
159        // >    initial containing block of the layout viewport’s document.
160        let page_left = self.viewport_rect.get().min_x() + self.window.scroll_offset().x;
161        Finite::wrap(page_left as f64)
162    }
163
164    /// <https://drafts.csswg.org/cssom-view/#dom-visualviewport-pagetop>
165    fn PageTop(&self) -> Finite<f64> {
166        // > 1. If the visual viewport’s associated document is not fully active, return 0.
167        if !self.window.Document().is_fully_active() {
168            return Finite::wrap(0.);
169        }
170
171        // > 2. Otherwise, return the offset of the top edge of the visual viewport from the top edge of the
172        // >    initial containing block of the layout viewport’s document.
173        let page_top = self.viewport_rect.get().min_y() + self.window.scroll_offset().y;
174        Finite::wrap(page_top as f64)
175    }
176
177    /// <https://drafts.csswg.org/cssom-view/#dom-visualviewport-width>
178    fn Width(&self) -> Finite<f64> {
179        // > 1. If the visual viewport’s associated document is not fully active, return 0.
180        if !self.window.Document().is_fully_active() {
181            return Finite::wrap(0.);
182        }
183
184        // > 2. Otherwise, return the width of the visual viewport excluding the width of any rendered vertical
185        // >    classic scrollbar that is fixed to the visual viewport.
186        // TODO(#41341): when classic scrollbar is implemented, exclude it's size from visual viewport width.
187        Finite::wrap(self.viewport_rect.get().width() as f64)
188    }
189
190    /// <https://drafts.csswg.org/cssom-view/#dom-visualviewport-height>
191    fn Height(&self) -> Finite<f64> {
192        // > 1. If the visual viewport’s associated document is not fully active, return 0.
193        if !self.window.Document().is_fully_active() {
194            return Finite::wrap(0.);
195        }
196
197        // > 2. Otherwise, return the height of the visual viewport excluding the height of any rendered horizontal
198        // >    classic scrollbar that is fixed to the visual viewport.
199        // TODO(#41341): when classic scrollbar is implemented, exclude it's size from visual viewport height.
200        Finite::wrap(self.viewport_rect.get().height() as f64)
201    }
202
203    /// <https://drafts.csswg.org/cssom-view/#dom-visualviewport-scale>
204    fn Scale(&self) -> Finite<f64> {
205        // > 1. If the visual viewport’s associated document is not fully active, return 0.
206        if !self.window.Document().is_fully_active() {
207            return Finite::wrap(0.);
208        }
209
210        // > 2. If there is no output device, return 1 and abort these steps.
211        // TODO(#41341): check for output device.
212
213        // > 3. Otherwise, return the visual viewport’s scale factor.
214        Finite::wrap(self.scale.get().get() as f64)
215    }
216
217    // <https://drafts.csswg.org/cssom-view/#dom-visualviewport-onresize>
218    event_handler!(resize, GetOnresize, SetOnresize);
219
220    // <https://drafts.csswg.org/cssom-view/#dom-visualviewport-onscroll>
221    event_handler!(scroll, GetOnscroll, SetOnscroll);
222
223    // <https://drafts.csswg.org/cssom-view/#dom-visualviewport-onscrollend>
224    event_handler!(scrollend, GetOnscrollend, SetOnscrollend);
225}