mirror of
https://github.com/servo/servo
synced 2026-04-26 01:25:32 +02:00
This change implements the `scrollend` event. Since there is no support for asynchronous / smooth scrolling, with this change all `scroll` events simply send a `scrollend` event in a way that looks a bit like what the specification says. Note that there are currently some serious issues (w3c/csswg-drafts#8396) with specification, so things are still a bit weird overall. In addition, this organizes and reduces duplication in some of the existing scroll event code. Testing: This causes some WPT tests to start passing or to stop timing out. --------- Signed-off-by: abdelrahman1234567 <abdelrahman.hossameldin.awadalla@huawei.com> Signed-off-by: Martin Robinson <mrobinson@igalia.com> Co-authored-by: abdelrahman1234567 <abdelrahman.hossameldin.awadalla@huawei.com> Co-authored-by: Martin Robinson <mrobinson@igalia.com>
226 lines
9.1 KiB
Rust
226 lines
9.1 KiB
Rust
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||
|
||
use std::cell::Cell;
|
||
|
||
use bitflags::bitflags;
|
||
use dom_struct::dom_struct;
|
||
use euclid::{Rect, Scale, Size2D};
|
||
use paint_api::PinchZoomInfos;
|
||
use script_bindings::codegen::GenericBindings::VisualViewportBinding::VisualViewportMethods;
|
||
use script_bindings::codegen::GenericBindings::WindowBinding::WindowMethods;
|
||
use script_bindings::inheritance::Castable;
|
||
use script_bindings::num::Finite;
|
||
use script_bindings::root::{Dom, DomRoot};
|
||
use script_bindings::script_runtime::CanGc;
|
||
use style_traits::CSSPixel;
|
||
use webrender_api::units::DevicePixel;
|
||
|
||
use crate::dom::bindings::reflector::reflect_dom_object;
|
||
use crate::dom::eventtarget::EventTarget;
|
||
use crate::dom::window::Window;
|
||
|
||
/// <https://drafts.csswg.org/cssom-view/#the-visualviewport-interface>
|
||
#[dom_struct]
|
||
pub(crate) struct VisualViewport {
|
||
eventtarget: EventTarget,
|
||
|
||
/// The associated [`Window`] for this [`VisualViewport`],
|
||
window: Dom<Window>,
|
||
|
||
/// The rectangle bound of the [`VisualViewport`], relative to the layout viewport.
|
||
#[no_trace]
|
||
viewport_rect: Cell<Rect<f32, CSSPixel>>,
|
||
|
||
/// The scale factor of [`VisualViewport`], which is also commonly known as pinch-zoom.
|
||
/// <https://drafts.csswg.org/cssom-view/#scale-factor>
|
||
#[no_trace]
|
||
scale: Cell<Scale<f32, DevicePixel, DevicePixel>>,
|
||
}
|
||
|
||
bitflags! {
|
||
/// The changes of the [`VisualViewport`] after an update to determine events that might need to be fired.
|
||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||
pub struct VisualViewportChanges: u8 {
|
||
/// Size or the scale factor of the [`VisualViewport`] has changed.
|
||
const DimensionChanged = 1 << 0;
|
||
/// The offset or the origin of the [`VisualViewport`] has changed.
|
||
const OffsetChanged = 1 << 1;
|
||
}
|
||
}
|
||
|
||
impl VisualViewport {
|
||
fn new_inherited(
|
||
window: &Window,
|
||
viewport_rect: Rect<f32, CSSPixel>,
|
||
scale: Scale<f32, DevicePixel, DevicePixel>,
|
||
) -> Self {
|
||
Self {
|
||
eventtarget: EventTarget::new_inherited(),
|
||
window: Dom::from_ref(window),
|
||
viewport_rect: Cell::new(viewport_rect),
|
||
scale: Cell::new(scale),
|
||
}
|
||
}
|
||
|
||
/// The initial visual viewport based on a layout viewport relative to the initial containing block, where
|
||
/// the dimension would be the same as layout viewport leaving the offset and the scale to its default value.
|
||
pub(crate) fn new_from_layout_viewport(
|
||
window: &Window,
|
||
viewport_size: Size2D<f32, CSSPixel>,
|
||
can_gc: CanGc,
|
||
) -> DomRoot<Self> {
|
||
reflect_dom_object(
|
||
Box::new(Self::new_inherited(
|
||
window,
|
||
Rect::from_size(viewport_size),
|
||
Scale::identity(),
|
||
)),
|
||
window,
|
||
can_gc,
|
||
)
|
||
}
|
||
|
||
fn check_for_update(
|
||
&self,
|
||
old_rect: Rect<f32, CSSPixel>,
|
||
old_scale: Scale<f32, DevicePixel, DevicePixel>,
|
||
) -> VisualViewportChanges {
|
||
let mut state = VisualViewportChanges::empty();
|
||
if old_rect.origin != self.viewport_rect.get().origin {
|
||
state |= VisualViewportChanges::OffsetChanged;
|
||
}
|
||
if old_rect.size != self.viewport_rect.get().size || old_scale != self.scale.get() {
|
||
state |= VisualViewportChanges::DimensionChanged;
|
||
}
|
||
state
|
||
}
|
||
|
||
/// Update the [`VisualViewport`] state based on a new [`PinchZoomInfos`].
|
||
pub(crate) fn update_from_pinch_zoom_infos(
|
||
&self,
|
||
pinch_zoom_infos: PinchZoomInfos,
|
||
) -> VisualViewportChanges {
|
||
let old_rect = self.viewport_rect.replace(pinch_zoom_infos.rect);
|
||
let old_scale = self.scale.replace(pinch_zoom_infos.zoom_factor);
|
||
|
||
self.check_for_update(old_rect, old_scale)
|
||
}
|
||
|
||
/// <https://drafts.csswg.org/cssom-view/#scrolling-events>
|
||
///
|
||
/// > Whenever a visual viewport gets scrolled (whether in response to user
|
||
/// > interaction or by an API), the user agent must run these steps:
|
||
pub(crate) fn handle_scroll_event(&self) {
|
||
// Step 1: Let vv be the VisualViewport object that was scrolled.
|
||
// Note: This is self.
|
||
// Step 2: Let doc be vv’s associated document.
|
||
// Steps 3 and 4 are shared with other scroll targets.
|
||
self.window
|
||
.Document()
|
||
.finish_handle_scroll_event(self.upcast());
|
||
}
|
||
}
|
||
|
||
impl VisualViewportMethods<crate::DomTypeHolder> for VisualViewport {
|
||
/// <https://drafts.csswg.org/cssom-view/#dom-visualviewport-offsetleft>
|
||
fn OffsetLeft(&self) -> Finite<f64> {
|
||
// > 1. If the visual viewport’s associated document is not fully active, return 0.
|
||
if !self.window.Document().is_fully_active() {
|
||
return Finite::wrap(0.);
|
||
}
|
||
|
||
// > 2. Otherwise, return the offset of the left edge of the visual viewport from the left edge of the
|
||
// > layout viewport.
|
||
Finite::wrap(self.viewport_rect.get().min_x() as f64)
|
||
}
|
||
|
||
/// <https://drafts.csswg.org/cssom-view/#dom-visualviewport-offsettop>
|
||
fn OffsetTop(&self) -> Finite<f64> {
|
||
// > 1. If the visual viewport’s associated document is not fully active, return 0.
|
||
if !self.window.Document().is_fully_active() {
|
||
return Finite::wrap(0.);
|
||
}
|
||
|
||
// > 2. Otherwise, return the offset of the top edge of the visual viewport from the top edge of the
|
||
// > layout viewport.
|
||
Finite::wrap(self.viewport_rect.get().min_y() as f64)
|
||
}
|
||
|
||
/// <https://drafts.csswg.org/cssom-view/#dom-visualviewport-pageleft>
|
||
fn PageLeft(&self) -> Finite<f64> {
|
||
// > 1. If the visual viewport’s associated document is not fully active, return 0.
|
||
if !self.window.Document().is_fully_active() {
|
||
return Finite::wrap(0.);
|
||
}
|
||
|
||
// > 2. Otherwise, return the offset of the left edge of the visual viewport from the left edge of the
|
||
// > initial containing block of the layout viewport’s document.
|
||
let page_left = self.viewport_rect.get().min_x() + self.window.scroll_offset().x;
|
||
Finite::wrap(page_left as f64)
|
||
}
|
||
|
||
/// <https://drafts.csswg.org/cssom-view/#dom-visualviewport-pagetop>
|
||
fn PageTop(&self) -> Finite<f64> {
|
||
// > 1. If the visual viewport’s associated document is not fully active, return 0.
|
||
if !self.window.Document().is_fully_active() {
|
||
return Finite::wrap(0.);
|
||
}
|
||
|
||
// > 2. Otherwise, return the offset of the top edge of the visual viewport from the top edge of the
|
||
// > initial containing block of the layout viewport’s document.
|
||
let page_top = self.viewport_rect.get().min_y() + self.window.scroll_offset().y;
|
||
Finite::wrap(page_top as f64)
|
||
}
|
||
|
||
/// <https://drafts.csswg.org/cssom-view/#dom-visualviewport-width>
|
||
fn Width(&self) -> Finite<f64> {
|
||
// > 1. If the visual viewport’s associated document is not fully active, return 0.
|
||
if !self.window.Document().is_fully_active() {
|
||
return Finite::wrap(0.);
|
||
}
|
||
|
||
// > 2. Otherwise, return the width of the visual viewport excluding the width of any rendered vertical
|
||
// > classic scrollbar that is fixed to the visual viewport.
|
||
// TODO(#41341): when classic scrollbar is implemented, exclude it's size from visual viewport width.
|
||
Finite::wrap(self.viewport_rect.get().width() as f64)
|
||
}
|
||
|
||
/// <https://drafts.csswg.org/cssom-view/#dom-visualviewport-height>
|
||
fn Height(&self) -> Finite<f64> {
|
||
// > 1. If the visual viewport’s associated document is not fully active, return 0.
|
||
if !self.window.Document().is_fully_active() {
|
||
return Finite::wrap(0.);
|
||
}
|
||
|
||
// > 2. Otherwise, return the height of the visual viewport excluding the height of any rendered horizontal
|
||
// > classic scrollbar that is fixed to the visual viewport.
|
||
// TODO(#41341): when classic scrollbar is implemented, exclude it's size from visual viewport height.
|
||
Finite::wrap(self.viewport_rect.get().height() as f64)
|
||
}
|
||
|
||
/// <https://drafts.csswg.org/cssom-view/#dom-visualviewport-scale>
|
||
fn Scale(&self) -> Finite<f64> {
|
||
// > 1. If the visual viewport’s associated document is not fully active, return 0.
|
||
if !self.window.Document().is_fully_active() {
|
||
return Finite::wrap(0.);
|
||
}
|
||
|
||
// > 2. If there is no output device, return 1 and abort these steps.
|
||
// TODO(#41341): check for output device.
|
||
|
||
// > 3. Otherwise, return the visual viewport’s scale factor.
|
||
Finite::wrap(self.scale.get().get() as f64)
|
||
}
|
||
|
||
// <https://drafts.csswg.org/cssom-view/#dom-visualviewport-onresize>
|
||
event_handler!(resize, GetOnresize, SetOnresize);
|
||
|
||
// <https://drafts.csswg.org/cssom-view/#dom-visualviewport-onscroll>
|
||
event_handler!(scroll, GetOnscroll, SetOnscroll);
|
||
|
||
// <https://drafts.csswg.org/cssom-view/#dom-visualviewport-onscrollend>
|
||
event_handler!(scrollend, GetOnscrollend, SetOnscrollend);
|
||
}
|