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::num::Finite;
14use script_bindings::root::{Dom, DomRoot};
15use script_bindings::script_runtime::CanGc;
16use style_traits::CSSPixel;
17use webrender_api::units::DevicePixel;
18
19use crate::dom::bindings::reflector::reflect_dom_object;
20use crate::dom::eventtarget::EventTarget;
21use crate::dom::window::Window;
22
23/// <https://drafts.csswg.org/cssom-view/#the-visualviewport-interface>
24#[dom_struct]
25pub(crate) struct VisualViewport {
26    eventtarget: EventTarget,
27
28    /// The associated [`Window`] for this [`VisualViewport`],
29    window: Dom<Window>,
30
31    /// The rectangle bound of the [`VisualViewport`], relative to the layout viewport.
32    #[no_trace]
33    viewport_rect: Cell<Rect<f32, CSSPixel>>,
34
35    /// The scale factor of [`VisualViewport`], which is also commonly known as pinch-zoom.
36    /// <https://drafts.csswg.org/cssom-view/#scale-factor>
37    #[no_trace]
38    scale: Cell<Scale<f32, DevicePixel, DevicePixel>>,
39}
40
41bitflags! {
42    /// The changes of the [`VisualViewport`] after an update to determine events that might need to be fired.
43    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
44    pub struct VisualViewportChanges: u8 {
45        /// Size or the scale factor of the [`VisualViewport`] has changed.
46        const DimensionChanged = 1 << 0;
47        /// The offset or the origin of the [`VisualViewport`] has changed.
48        const OffsetChanged = 1 << 1;
49    }
50}
51
52impl VisualViewport {
53    fn new_inherited(
54        window: &Window,
55        viewport_rect: Rect<f32, CSSPixel>,
56        scale: Scale<f32, DevicePixel, DevicePixel>,
57    ) -> Self {
58        Self {
59            eventtarget: EventTarget::new_inherited(),
60            window: Dom::from_ref(window),
61            viewport_rect: Cell::new(viewport_rect),
62            scale: Cell::new(scale),
63        }
64    }
65
66    /// The initial visual viewport based on a layout viewport relative to the initial containing block, where
67    /// the dimension would be the same as layout viewport leaving the offset and the scale to its default value.
68    pub(crate) fn new_from_layout_viewport(
69        window: &Window,
70        viewport_size: Size2D<f32, CSSPixel>,
71        can_gc: CanGc,
72    ) -> DomRoot<Self> {
73        reflect_dom_object(
74            Box::new(Self::new_inherited(
75                window,
76                Rect::from_size(viewport_size),
77                Scale::identity(),
78            )),
79            window,
80            can_gc,
81        )
82    }
83
84    fn check_for_update(
85        &self,
86        old_rect: Rect<f32, CSSPixel>,
87        old_scale: Scale<f32, DevicePixel, DevicePixel>,
88    ) -> VisualViewportChanges {
89        let mut state = VisualViewportChanges::empty();
90        if old_rect.origin != self.viewport_rect.get().origin {
91            state |= VisualViewportChanges::OffsetChanged;
92        }
93        if old_rect.size != self.viewport_rect.get().size || old_scale != self.scale.get() {
94            state |= VisualViewportChanges::DimensionChanged;
95        }
96        state
97    }
98
99    /// Update the [`VisualViewport`] state based on a new [`PinchZoomInfos`].
100    pub(crate) fn update_from_pinch_zoom_infos(
101        &self,
102        pinch_zoom_infos: PinchZoomInfos,
103    ) -> VisualViewportChanges {
104        let old_rect = self.viewport_rect.replace(pinch_zoom_infos.rect);
105        let old_scale = self.scale.replace(pinch_zoom_infos.zoom_factor);
106
107        self.check_for_update(old_rect, old_scale)
108    }
109}
110
111impl VisualViewportMethods<crate::DomTypeHolder> for VisualViewport {
112    /// <https://drafts.csswg.org/cssom-view/#dom-visualviewport-offsetleft>
113    fn OffsetLeft(&self) -> Finite<f64> {
114        // > 1. If the visual viewport’s associated document is not fully active, return 0.
115        if !self.window.Document().is_fully_active() {
116            return Finite::wrap(0.);
117        }
118
119        // > 2. Otherwise, return the offset of the left edge of the visual viewport from the left edge of the
120        // >    layout viewport.
121        Finite::wrap(self.viewport_rect.get().min_x() as f64)
122    }
123
124    /// <https://drafts.csswg.org/cssom-view/#dom-visualviewport-offsettop>
125    fn OffsetTop(&self) -> Finite<f64> {
126        // > 1. If the visual viewport’s associated document is not fully active, return 0.
127        if !self.window.Document().is_fully_active() {
128            return Finite::wrap(0.);
129        }
130
131        // > 2. Otherwise, return the offset of the top edge of the visual viewport from the top edge of the
132        // >    layout viewport.
133        Finite::wrap(self.viewport_rect.get().min_y() as f64)
134    }
135
136    /// <https://drafts.csswg.org/cssom-view/#dom-visualviewport-pageleft>
137    fn PageLeft(&self) -> Finite<f64> {
138        // > 1. If the visual viewport’s associated document is not fully active, return 0.
139        if !self.window.Document().is_fully_active() {
140            return Finite::wrap(0.);
141        }
142
143        // > 2. Otherwise, return the offset of the left edge of the visual viewport from the left edge of the
144        // >    initial containing block of the layout viewport’s document.
145        let page_left = self.viewport_rect.get().min_x() + self.window.scroll_offset().x;
146        Finite::wrap(page_left as f64)
147    }
148
149    /// <https://drafts.csswg.org/cssom-view/#dom-visualviewport-pagetop>
150    fn PageTop(&self) -> Finite<f64> {
151        // > 1. If the visual viewport’s associated document is not fully active, return 0.
152        if !self.window.Document().is_fully_active() {
153            return Finite::wrap(0.);
154        }
155
156        // > 2. Otherwise, return the offset of the top edge of the visual viewport from the top edge of the
157        // >    initial containing block of the layout viewport’s document.
158        let page_top = self.viewport_rect.get().min_y() + self.window.scroll_offset().y;
159        Finite::wrap(page_top as f64)
160    }
161
162    /// <https://drafts.csswg.org/cssom-view/#dom-visualviewport-width>
163    fn Width(&self) -> Finite<f64> {
164        // > 1. If the visual viewport’s associated document is not fully active, return 0.
165        if !self.window.Document().is_fully_active() {
166            return Finite::wrap(0.);
167        }
168
169        // > 2. Otherwise, return the width of the visual viewport excluding the width of any rendered vertical
170        // >    classic scrollbar that is fixed to the visual viewport.
171        // TODO(#41341): when classic scrollbar is implemented, exclude it's size from visual viewport width.
172        Finite::wrap(self.viewport_rect.get().width() as f64)
173    }
174
175    /// <https://drafts.csswg.org/cssom-view/#dom-visualviewport-height>
176    fn Height(&self) -> Finite<f64> {
177        // > 1. If the visual viewport’s associated document is not fully active, return 0.
178        if !self.window.Document().is_fully_active() {
179            return Finite::wrap(0.);
180        }
181
182        // > 2. Otherwise, return the height of the visual viewport excluding the height of any rendered horizontal
183        // >    classic scrollbar that is fixed to the visual viewport.
184        // TODO(#41341): when classic scrollbar is implemented, exclude it's size from visual viewport height.
185        Finite::wrap(self.viewport_rect.get().height() as f64)
186    }
187
188    /// <https://drafts.csswg.org/cssom-view/#dom-visualviewport-scale>
189    fn Scale(&self) -> Finite<f64> {
190        // > 1. If the visual viewport’s associated document is not fully active, return 0.
191        if !self.window.Document().is_fully_active() {
192            return Finite::wrap(0.);
193        }
194
195        // > 2. If there is no output device, return 1 and abort these steps.
196        // TODO(#41341): check for output device.
197
198        // > 3. Otherwise, return the visual viewport’s scale factor.
199        Finite::wrap(self.scale.get().get() as f64)
200    }
201
202    // <https://drafts.csswg.org/cssom-view/#dom-visualviewport-onresize>
203    event_handler!(resize, GetOnresize, SetOnresize);
204
205    // <https://drafts.csswg.org/cssom-view/#dom-visualviewport-onscroll>
206    event_handler!(scroll, GetOnscroll, SetOnscroll);
207
208    // <https://drafts.csswg.org/cssom-view/#dom-visualviewport-onscrollend>
209    event_handler!(scrollend, GetOnscrollend, SetOnscrollend);
210}