Skip to main content

paint/
webview_renderer.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;
6use std::collections::hash_map::Entry;
7use std::rc::Rc;
8
9use crossbeam_channel::Sender;
10use embedder_traits::{
11    AnimationState, InputEvent, InputEventAndId, InputEventId, InputEventResult, MouseButton,
12    MouseButtonAction, MouseButtonEvent, MouseMoveEvent, PaintHitTestResult, Scroll, TouchEvent,
13    TouchEventType, ViewportDetails, WebViewPoint, WheelEvent,
14};
15use euclid::{Scale, Size2D, Vector2D};
16use log::{debug, warn};
17use malloc_size_of::MallocSizeOf;
18use paint_api::display_list::ScrollType;
19use paint_api::viewport_description::{
20    DEFAULT_PAGE_ZOOM, MAX_PAGE_ZOOM, MIN_PAGE_ZOOM, ViewportDescription,
21};
22use paint_api::{PipelineExitSource, SendableFrameTree, WebViewTrait};
23use rustc_hash::FxHashMap;
24use servo_base::id::{PipelineId, WebViewId};
25use servo_constellation_traits::{
26    EmbedderToConstellationMessage, ScrollStateUpdate, WindowSizeType,
27};
28use servo_geometry::DeviceIndependentPixel;
29use style_traits::CSSPixel;
30use webrender::RenderApi;
31use webrender_api::units::{DevicePixel, DevicePoint, DeviceRect, DeviceVector2D, LayoutVector2D};
32use webrender_api::{DocumentId, ExternalScrollId, ScrollLocation};
33
34use crate::paint::RepaintReason;
35use crate::painter::Painter;
36use crate::pinch_zoom::PinchZoom;
37use crate::pipeline_details::PipelineDetails;
38use crate::refresh_driver::BaseRefreshDriver;
39use crate::touch::{
40    PendingTouchInputEvent, TouchHandler, TouchIdMoveTracking, TouchMoveAllowed, TouchSequenceState,
41};
42
43#[derive(Clone, Copy)]
44pub(crate) struct ScrollEvent {
45    /// Scroll by this offset, or to Start or End
46    pub scroll: Scroll,
47    /// Scroll the scroll node that is found at this point.
48    pub point: DevicePoint,
49}
50
51#[derive(Clone, Copy)]
52pub(crate) enum ScrollZoomEvent {
53    /// A pinch zoom event that magnifies the view by the given factor from the given
54    /// center point.
55    PinchZoom(f32, DevicePoint),
56    /// A scroll event that scrolls the scroll node at the given location by the
57    /// given amount.
58    Scroll(ScrollEvent),
59}
60
61#[derive(Clone, Debug)]
62pub(crate) struct ScrollResult {
63    pub hit_test_result: PaintHitTestResult,
64    /// The [`ExternalScrollId`] of the node that was actually scrolled.
65    ///
66    /// Note that this is an inclusive ancestor of `external_scroll_id` in
67    /// [`Self::hit_test_result`].
68    pub external_scroll_id: ExternalScrollId,
69    pub offset: LayoutVector2D,
70}
71
72#[derive(Debug, PartialEq)]
73pub(crate) enum PinchZoomResult {
74    DidPinchZoom,
75    DidNotPinchZoom,
76}
77
78/// A renderer for a libservo `WebView`. This is essentially the [`ServoRenderer`]'s interface to a
79/// libservo `WebView`, but the code here cannot depend on libservo in order to prevent circular
80/// dependencies, which is why we store a `dyn WebViewTrait` here instead of the `WebView` itself.
81pub(crate) struct WebViewRenderer {
82    /// The [`WebViewId`] of the `WebView` associated with this [`WebViewDetails`].
83    pub id: WebViewId,
84    /// The renderer's view of the embedding layer `WebView` as a trait implementation,
85    /// so that the renderer doesn't need to depend on the embedding layer. This avoids
86    /// a dependency cycle.
87    pub webview: Box<dyn WebViewTrait>,
88    /// The root [`PipelineId`] of the currently displayed page in this WebView.
89    pub root_pipeline_id: Option<PipelineId>,
90    /// The rectangle of the [`WebView`] in device pixels, which is the viewport.
91    pub rect: DeviceRect,
92    /// Tracks details about each active pipeline that `Paint` knows about.
93    pub pipelines: FxHashMap<PipelineId, PipelineDetails>,
94    /// Pending scroll/zoom events.
95    pending_scroll_zoom_events: Vec<ScrollZoomEvent>,
96    /// A map of pending wheel events. These are events that have been sent to script,
97    /// but are waiting for processing. When they are handled by script, they may trigger
98    /// scroll events depending on whether `preventDefault()` was called on the event.
99    pending_wheel_events: FxHashMap<InputEventId, WheelEvent>,
100    /// Touch input state machine
101    touch_handler: TouchHandler,
102    /// "Desktop-style" zoom that resizes the viewport to fit the window.
103    pub page_zoom: Scale<f32, CSSPixel, DeviceIndependentPixel>,
104    /// "Mobile-style" zoom that does not reflow the page. When there is no [`PinchZoom`] a
105    /// zoom factor of 1.0 is implied and the [`PinchZoom::transform`] will be the identity.
106    pinch_zoom: PinchZoom,
107    /// The HiDPI scale factor for the `WebView` associated with this renderer. This is controlled
108    /// by the embedding layer.
109    hidpi_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
110    /// Whether or not this [`WebViewRenderer`] is hidden.
111    hidden: bool,
112    /// Whether or not this [`WebViewRenderer`] isn't throttled and has a pipeline with
113    /// active animations or animation frame callbacks.
114    animating: bool,
115    /// A [`ViewportDescription`] for this [`WebViewRenderer`], which contains the limitations
116    /// and initial values for zoom derived from the `viewport` meta tag in web content.
117    viewport_description: ViewportDescription,
118
119    /// The dimensions of the screen on which this WebView is rendering.
120    screen_size: Size2D<f32, DevicePixel>,
121
122    //
123    // Data that is shared with the parent renderer.
124    //
125    /// The channel on which messages can be sent to the constellation.
126    embedder_to_constellation_sender: Sender<EmbedderToConstellationMessage>,
127    /// The [`BaseRefreshDriver`] which manages the painting of `WebView`s during animations.
128    refresh_driver: Rc<BaseRefreshDriver>,
129    /// The active webrender document.
130    webrender_document: DocumentId,
131}
132
133impl WebViewRenderer {
134    pub(crate) fn new(
135        renderer_webview: Box<dyn WebViewTrait>,
136        viewport_details: ViewportDetails,
137        embedder_to_constellation_sender: Sender<EmbedderToConstellationMessage>,
138        refresh_driver: Rc<BaseRefreshDriver>,
139        webrender_document: DocumentId,
140    ) -> Self {
141        let hidpi_scale_factor = viewport_details.hidpi_scale_factor;
142        let size = viewport_details.size * viewport_details.hidpi_scale_factor;
143        let rect = DeviceRect::from_origin_and_size(DevicePoint::origin(), size);
144        let webview_id = renderer_webview.id();
145        Self {
146            id: webview_id,
147            webview: renderer_webview,
148            root_pipeline_id: None,
149            rect,
150            pipelines: Default::default(),
151            touch_handler: TouchHandler::new(webview_id),
152            pending_scroll_zoom_events: Default::default(),
153            pending_wheel_events: Default::default(),
154            page_zoom: DEFAULT_PAGE_ZOOM,
155            pinch_zoom: PinchZoom::new(rect),
156            hidpi_scale_factor: Scale::new(hidpi_scale_factor.0),
157            hidden: false,
158            animating: false,
159            viewport_description: Default::default(),
160            screen_size: viewport_details.device_size,
161            embedder_to_constellation_sender,
162            refresh_driver,
163            webrender_document,
164        }
165    }
166
167    fn hit_test(&self, webrender_api: &RenderApi, point: DevicePoint) -> Vec<PaintHitTestResult> {
168        Painter::hit_test_at_point_with_api_and_document(
169            webrender_api,
170            self.webrender_document,
171            point,
172        )
173    }
174
175    pub(crate) fn animation_callbacks_running(&self) -> bool {
176        self.pipelines
177            .values()
178            .any(PipelineDetails::animation_callbacks_running)
179    }
180
181    pub(crate) fn animating(&self) -> bool {
182        self.animating
183    }
184
185    pub(crate) fn hidden(&self) -> bool {
186        self.hidden
187    }
188
189    /// Set whether this [`WebViewRenderer`] is in the hidden state or not. Return `true` if the
190    /// value changed or `false` otherwise.
191    pub(crate) fn set_hidden(&mut self, new_value: bool) -> bool {
192        let old_value = std::mem::replace(&mut self.hidden, new_value);
193        new_value != old_value
194    }
195
196    /// Returns the [`PipelineDetails`] for the given [`PipelineId`], creating it if needed.
197    pub(crate) fn ensure_pipeline_details(
198        &mut self,
199        pipeline_id: PipelineId,
200    ) -> &mut PipelineDetails {
201        self.pipelines
202            .entry(pipeline_id)
203            .or_insert_with(PipelineDetails::new)
204    }
205
206    pub(crate) fn pipeline_exited(&mut self, pipeline_id: PipelineId, source: PipelineExitSource) {
207        let pipeline = self.pipelines.entry(pipeline_id);
208        let Entry::Occupied(mut pipeline) = pipeline else {
209            return;
210        };
211
212        pipeline.get_mut().exited.insert(source);
213
214        // Do not remove pipeline details until both the Constellation and Script have
215        // finished processing the pipeline shutdown. This prevents any followup messges
216        // from re-adding the pipeline details and creating a zombie.
217        if !pipeline.get().exited.is_all() {
218            return;
219        }
220
221        pipeline.remove_entry();
222    }
223
224    pub(crate) fn set_frame_tree(&mut self, frame_tree: &SendableFrameTree) {
225        let pipeline_id = frame_tree.pipeline.id;
226        let old_pipeline_id = self.root_pipeline_id.replace(pipeline_id);
227
228        if old_pipeline_id != self.root_pipeline_id {
229            debug!(
230                "Updating webview ({:?}) from pipeline {:?} to {:?}",
231                3, old_pipeline_id, self.root_pipeline_id
232            );
233        }
234
235        self.set_frame_tree_on_pipeline_details(frame_tree, None);
236    }
237
238    pub(crate) fn send_scroll_positions_to_layout_for_pipeline(
239        &self,
240        pipeline_id: PipelineId,
241        scrolled_node: ExternalScrollId,
242    ) {
243        let Some(details) = self.pipelines.get(&pipeline_id) else {
244            return;
245        };
246
247        let offsets = details.scroll_tree.scroll_offsets();
248
249        // This might be true if we have not received a display list from the layout
250        // associated with this pipeline yet. In that case, the layout is not ready to
251        // receive scroll offsets anyway, so just save time and prevent other issues by
252        // not sending them.
253        if offsets.is_empty() {
254            return;
255        }
256
257        let _ = self.embedder_to_constellation_sender.send(
258            EmbedderToConstellationMessage::SetScrollStates(
259                pipeline_id,
260                ScrollStateUpdate {
261                    scrolled_node,
262                    offsets,
263                },
264            ),
265        );
266    }
267
268    pub(crate) fn set_frame_tree_on_pipeline_details(
269        &mut self,
270        frame_tree: &SendableFrameTree,
271        parent_pipeline_id: Option<PipelineId>,
272    ) {
273        let pipeline_id = frame_tree.pipeline.id;
274        let pipeline_details = self.ensure_pipeline_details(pipeline_id);
275        pipeline_details.pipeline = Some(frame_tree.pipeline.clone());
276        pipeline_details.parent_pipeline_id = parent_pipeline_id;
277        pipeline_details.children = frame_tree
278            .children
279            .iter()
280            .map(|frame_tree| frame_tree.pipeline.id)
281            .collect();
282
283        for kid in &frame_tree.children {
284            self.set_frame_tree_on_pipeline_details(kid, Some(pipeline_id));
285        }
286    }
287
288    /// Sets or unsets the animations-running flag for the given pipeline. Returns
289    /// true if the pipeline has started animating.
290    pub(crate) fn change_pipeline_running_animations_state(
291        &mut self,
292        pipeline_id: PipelineId,
293        animation_state: AnimationState,
294    ) -> bool {
295        let pipeline_details = self.ensure_pipeline_details(pipeline_id);
296        let was_animating = pipeline_details.animating();
297        match animation_state {
298            AnimationState::AnimationsPresent => {
299                pipeline_details.animations_running = true;
300            },
301            AnimationState::AnimationCallbacksPresent => {
302                pipeline_details.animation_callbacks_running = true;
303            },
304            AnimationState::NoAnimationsPresent => {
305                pipeline_details.animations_running = false;
306            },
307            AnimationState::NoAnimationCallbacksPresent => {
308                pipeline_details.animation_callbacks_running = false;
309            },
310        }
311        let started_animating = !was_animating && pipeline_details.animating();
312
313        self.update_animation_state();
314
315        // It's important that an animation tick is triggered even if the
316        // WebViewRenderer's overall animation state hasn't changed. It's possible that
317        // the WebView was animating, but not producing new display lists. In that case,
318        // no repaint will happen and thus no repaint will trigger the next animation tick.
319        started_animating
320    }
321
322    /// Sets or unsets the throttled flag for the given pipeline. Returns
323    /// true if the pipeline has started animating.
324    pub(crate) fn set_throttled(&mut self, pipeline_id: PipelineId, throttled: bool) -> bool {
325        let pipeline_details = self.ensure_pipeline_details(pipeline_id);
326        let was_animating = pipeline_details.animating();
327        pipeline_details.throttled = throttled;
328        let started_animating = !was_animating && pipeline_details.animating();
329
330        // Throttling a pipeline can cause it to be taken into the "not-animating" state.
331        self.update_animation_state();
332
333        // It's important that an animation tick is triggered even if the
334        // WebViewRenderer's overall animation state hasn't changed. It's possible that
335        // the WebView was animating, but not producing new display lists. In that case,
336        // no repaint will happen and thus no repaint will trigger the next animation tick.
337        started_animating
338    }
339
340    fn update_animation_state(&mut self) {
341        self.animating = self.pipelines.values().any(PipelineDetails::animating);
342        self.webview.set_animating(self.animating());
343    }
344
345    pub(crate) fn for_each_connected_pipeline(&self, callback: &mut impl FnMut(&PipelineDetails)) {
346        if let Some(root_pipeline_id) = self.root_pipeline_id {
347            self.for_each_connected_pipeline_internal(root_pipeline_id, callback);
348        }
349    }
350
351    fn for_each_connected_pipeline_internal(
352        &self,
353        pipeline_id: PipelineId,
354        callback: &mut impl FnMut(&PipelineDetails),
355    ) {
356        let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
357            return;
358        };
359        callback(pipeline);
360        for child_pipeline_id in &pipeline.children {
361            self.for_each_connected_pipeline_internal(*child_pipeline_id, callback);
362        }
363    }
364
365    /// Update touch-based animations (currently just fling) during a `RefreshDriver`-based
366    /// frame tick. Returns `true` if we should continue observing frames (the fling is ongoing)
367    /// or `false` if we should stop observing frames (the fling has finished).
368    pub(crate) fn update_touch_handling_at_new_frame_start(&mut self) -> bool {
369        let Some(fling_action) = self.touch_handler.notify_new_frame_start() else {
370            return false;
371        };
372
373        self.on_scroll_window_event(
374            Scroll::Delta((-fling_action.delta).into()),
375            fling_action.cursor,
376        );
377        true
378    }
379
380    fn dispatch_input_event_with_hit_testing(
381        &mut self,
382        render_api: &RenderApi,
383        event: InputEventAndId,
384    ) -> bool {
385        let event_point = event
386            .event
387            .point()
388            .map(|point| point.as_device_point(self.device_pixels_per_page_pixel()));
389        let hit_test_result = match event_point {
390            Some(point) => {
391                let hit_test_result = match event.event {
392                    InputEvent::Touch(_) => self.touch_handler.get_hit_test_result_cache_value(),
393                    _ => None,
394                }
395                .or_else(|| self.hit_test(render_api, point).into_iter().nth(0));
396                if hit_test_result.is_none() {
397                    warn!("Empty hit test result for input event, ignoring.");
398                    return false;
399                }
400                hit_test_result
401            },
402            None => None,
403        };
404
405        if let Err(error) = self.embedder_to_constellation_sender.send(
406            EmbedderToConstellationMessage::ForwardInputEvent(self.id, event, hit_test_result),
407        ) {
408            warn!("Sending event to constellation failed ({error:?}).");
409            false
410        } else {
411            true
412        }
413    }
414
415    pub(crate) fn notify_input_event(
416        &mut self,
417        render_api: &RenderApi,
418        repaint_reason: &Cell<RepaintReason>,
419        event_and_id: InputEventAndId,
420    ) -> bool {
421        if let InputEvent::Touch(touch_event) = event_and_id.event {
422            return self.on_touch_event(render_api, repaint_reason, touch_event, event_and_id.id);
423        }
424
425        if let InputEvent::Wheel(wheel_event) = event_and_id.event {
426            self.pending_wheel_events
427                .insert(event_and_id.id, wheel_event);
428        }
429
430        self.dispatch_input_event_with_hit_testing(render_api, event_and_id)
431    }
432
433    fn send_touch_event(
434        &mut self,
435        render_api: &RenderApi,
436        event: TouchEvent,
437        id: InputEventId,
438    ) -> bool {
439        let cancelable = event.is_cancelable();
440        let event_type = event.event_type;
441
442        let input_event_and_id = InputEventAndId {
443            event: InputEvent::Touch(event),
444            id,
445        };
446
447        let result = self.dispatch_input_event_with_hit_testing(render_api, input_event_and_id);
448
449        // We only post-process events that are actually cancelable. Uncancelable ones
450        // are processed immediately and can be ignored once they have been sent to the
451        // Constellation.
452        if cancelable && result {
453            self.touch_handler
454                .add_pending_touch_input_event(id, event.touch_id, event_type);
455        }
456
457        result
458    }
459
460    pub(crate) fn on_touch_event(
461        &mut self,
462        render_api: &RenderApi,
463        repaint_reason: &Cell<RepaintReason>,
464        event: TouchEvent,
465        id: InputEventId,
466    ) -> bool {
467        let result = match event.event_type {
468            TouchEventType::Down => self.on_touch_down(render_api, event, id),
469            TouchEventType::Move => self.on_touch_move(render_api, event, id),
470            TouchEventType::Up => self.on_touch_up(render_api, event, id),
471            TouchEventType::Cancel => self.on_touch_cancel(render_api, event, id),
472        };
473
474        self.touch_handler
475            .add_touch_move_refresh_observer_if_necessary(
476                self.refresh_driver.clone(),
477                repaint_reason,
478            );
479        result
480    }
481
482    fn on_touch_down(
483        &mut self,
484        render_api: &RenderApi,
485        event: TouchEvent,
486        id: InputEventId,
487    ) -> bool {
488        let point = event
489            .point
490            .as_device_point(self.device_pixels_per_page_pixel());
491        self.touch_handler.on_touch_down(event.touch_id, point);
492        self.send_touch_event(render_api, event, id)
493    }
494
495    fn on_touch_move(
496        &mut self,
497        render_api: &RenderApi,
498        mut event: TouchEvent,
499        id: InputEventId,
500    ) -> bool {
501        let point = event
502            .point
503            .as_device_point(self.device_pixels_per_page_pixel());
504        let action = self.touch_handler.on_touch_move(
505            event.touch_id,
506            point,
507            self.device_pixels_per_page_pixel_not_including_pinch_zoom()
508                .get(),
509        );
510        if let Some(action) = action {
511            // if first move processed and allowed, we directly process the move event,
512            // without waiting for the script handler.
513            if self
514                .touch_handler
515                .move_allowed(self.touch_handler.current_sequence_id)
516            {
517                // https://w3c.github.io/touch-events/#cancelability
518                event.disable_cancelable();
519                self.pending_scroll_zoom_events.push(action);
520            }
521        }
522        let mut reached_constellation = false;
523        // When the event is touchmove, if the script thread is processing the touch
524        // move event, we skip sending the event to the script thread.
525        // This prevents the script thread from stacking up for a large amount of time.
526        if !self.touch_handler.is_handling_touch_move_for_touch_id(
527            self.touch_handler.current_sequence_id,
528            event.touch_id,
529        ) {
530            reached_constellation = self.send_touch_event(render_api, event, id);
531            if reached_constellation && event.is_cancelable() {
532                self.touch_handler.set_handling_touch_move_for_touch_id(
533                    self.touch_handler.current_sequence_id,
534                    event.touch_id,
535                    TouchIdMoveTracking::Track,
536                );
537            }
538        }
539        reached_constellation
540    }
541
542    fn on_touch_up(&mut self, render_api: &RenderApi, event: TouchEvent, id: InputEventId) -> bool {
543        let point = event
544            .point
545            .as_device_point(self.device_pixels_per_page_pixel());
546        self.touch_handler.on_touch_up(event.touch_id, point);
547        self.send_touch_event(render_api, event, id)
548    }
549
550    fn on_touch_cancel(
551        &mut self,
552        render_api: &RenderApi,
553        event: TouchEvent,
554        id: InputEventId,
555    ) -> bool {
556        let point = event
557            .point
558            .as_device_point(self.device_pixels_per_page_pixel());
559        self.touch_handler.on_touch_cancel(event.touch_id, point);
560        self.send_touch_event(render_api, event, id)
561    }
562
563    fn on_touch_event_processed(
564        &mut self,
565        render_api: &RenderApi,
566        pending_touch_input_event: PendingTouchInputEvent,
567        result: InputEventResult,
568    ) {
569        let PendingTouchInputEvent {
570            sequence_id,
571            event_type,
572            touch_id,
573        } = pending_touch_input_event;
574
575        if result.contains(InputEventResult::DefaultPrevented) {
576            debug!(
577                "Touch event {:?} in sequence {:?} prevented!",
578                event_type, sequence_id
579            );
580            match event_type {
581                TouchEventType::Down => {
582                    // prevents both click and move
583                    self.touch_handler.prevent_click(sequence_id);
584                    self.touch_handler.prevent_move(sequence_id);
585                    self.touch_handler
586                        .remove_pending_touch_move_actions(sequence_id);
587                },
588                TouchEventType::Move => {
589                    // script thread processed the touch move event, mark this false.
590                    if let Some(info) = self.touch_handler.get_touch_sequence_mut(sequence_id) {
591                        info.prevent_move = TouchMoveAllowed::Prevented;
592                        if let TouchSequenceState::PendingFling { .. } = info.state {
593                            info.state = TouchSequenceState::Finished;
594                        }
595                        self.touch_handler.set_handling_touch_move_for_touch_id(
596                            self.touch_handler.current_sequence_id,
597                            touch_id,
598                            TouchIdMoveTracking::Remove,
599                        );
600                        self.touch_handler
601                            .remove_pending_touch_move_actions(sequence_id);
602                    }
603                },
604                TouchEventType::Up => {
605                    // Note: We don't have to consider PendingFling here, since we handle that
606                    // in the DefaultAllowed case of the touch_move event.
607                    // Note: Removing can and should fail, if we still have an active Fling,
608                    let Some(info) = &mut self.touch_handler.get_touch_sequence_mut(sequence_id)
609                    else {
610                        // The sequence ID could already be removed, e.g. if Fling finished,
611                        // before the touch_up event was handled (since fling can start
612                        // immediately if move was previously allowed, and clicks are anyway not
613                        // happening from fling).
614                        return;
615                    };
616                    match info.state {
617                        TouchSequenceState::PendingClick(_) => {
618                            info.state = TouchSequenceState::Finished;
619                            self.touch_handler.remove_touch_sequence(sequence_id);
620                        },
621                        TouchSequenceState::Flinging { .. } => {
622                            // We can't remove the touch sequence yet
623                        },
624                        TouchSequenceState::Finished => {
625                            self.touch_handler.remove_touch_sequence(sequence_id);
626                        },
627                        TouchSequenceState::Touching |
628                        TouchSequenceState::Panning { .. } |
629                        TouchSequenceState::Pinching |
630                        TouchSequenceState::MultiTouch |
631                        TouchSequenceState::PendingFling { .. } => {
632                            // It's possible to transition from Pinch to pan, Which means that
633                            // a touch_up event for a pinch might have arrived here, but we
634                            // already transitioned to pan or even PendingFling.
635                            // We don't need to do anything in these cases though.
636                        },
637                    }
638                },
639                TouchEventType::Cancel => {
640                    // We could still have pending event handlers, so we remove the pending
641                    // actions, and try to remove the touch sequence.
642                    self.touch_handler
643                        .remove_pending_touch_move_actions(sequence_id);
644                    self.touch_handler.try_remove_touch_sequence(sequence_id);
645                },
646            }
647        } else {
648            debug!(
649                "Touch event {:?} in sequence {:?} allowed",
650                event_type, sequence_id
651            );
652            match event_type {
653                TouchEventType::Down => {},
654                TouchEventType::Move => {
655                    self.pending_scroll_zoom_events.extend(
656                        self.touch_handler
657                            .take_pending_touch_move_actions(sequence_id),
658                    );
659                    self.touch_handler.set_handling_touch_move_for_touch_id(
660                        self.touch_handler.current_sequence_id,
661                        touch_id,
662                        TouchIdMoveTracking::Remove,
663                    );
664                    if let Some(info) = self.touch_handler.get_touch_sequence_mut(sequence_id) &&
665                        info.prevent_move == TouchMoveAllowed::Pending
666                    {
667                        info.prevent_move = TouchMoveAllowed::Allowed;
668                        if let TouchSequenceState::PendingFling { velocity, point } = info.state {
669                            info.state = TouchSequenceState::Flinging { velocity, point }
670                        }
671                    }
672                },
673                TouchEventType::Up => {
674                    let Some(info) = self.touch_handler.get_touch_sequence_mut(sequence_id) else {
675                        // The sequence was already removed because there is no default action.
676                        return;
677                    };
678                    match info.state {
679                        TouchSequenceState::PendingClick(point) => {
680                            info.state = TouchSequenceState::Finished;
681                            // PreventDefault from touch_down may have been processed after
682                            // touch_up already occurred.
683                            if !info.prevent_click {
684                                self.simulate_mouse_click(render_api, point);
685                            }
686                            self.touch_handler.remove_touch_sequence(sequence_id);
687                        },
688                        TouchSequenceState::Flinging { .. } => {
689                            // We can't remove the touch sequence yet
690                        },
691                        TouchSequenceState::Finished => {
692                            self.touch_handler.remove_touch_sequence(sequence_id);
693                        },
694                        TouchSequenceState::Panning { .. } |
695                        TouchSequenceState::Pinching |
696                        TouchSequenceState::PendingFling { .. } => {
697                            // It's possible to transition from Pinch to pan, Which means that
698                            // a touch_up event for a pinch might have arrived here, but we
699                            // already transitioned to pan or even PendingFling.
700                            // We don't need to do anything in these cases though.
701                        },
702                        TouchSequenceState::MultiTouch | TouchSequenceState::Touching => {
703                            // We transitioned to touching from multi-touch or pinching.
704                        },
705                    }
706                },
707                TouchEventType::Cancel => {
708                    self.touch_handler
709                        .remove_pending_touch_move_actions(sequence_id);
710                    self.touch_handler.try_remove_touch_sequence(sequence_id);
711                },
712            }
713        }
714    }
715
716    /// <http://w3c.github.io/touch-events/#mouse-events>
717    fn simulate_mouse_click(&mut self, render_api: &RenderApi, point: DevicePoint) {
718        let button = MouseButton::Left;
719        self.dispatch_input_event_with_hit_testing(
720            render_api,
721            InputEvent::MouseMove(MouseMoveEvent::new_compatibility_for_touch(point.into())).into(),
722        );
723        self.dispatch_input_event_with_hit_testing(
724            render_api,
725            InputEvent::MouseButton(MouseButtonEvent::new(
726                MouseButtonAction::Down,
727                button,
728                point.into(),
729            ))
730            .into(),
731        );
732        self.dispatch_input_event_with_hit_testing(
733            render_api,
734            InputEvent::MouseButton(MouseButtonEvent::new(
735                MouseButtonAction::Up,
736                button,
737                point.into(),
738            ))
739            .into(),
740        );
741    }
742
743    pub(crate) fn notify_scroll_event(&mut self, scroll: Scroll, point: WebViewPoint) {
744        let point = point.as_device_point(self.device_pixels_per_page_pixel());
745        self.on_scroll_window_event(scroll, point);
746    }
747
748    fn on_scroll_window_event(&mut self, scroll: Scroll, cursor: DevicePoint) {
749        self.pending_scroll_zoom_events
750            .push(ScrollZoomEvent::Scroll(ScrollEvent {
751                scroll,
752                point: cursor,
753            }));
754    }
755
756    /// Process pending scroll events for this [`WebViewRenderer`]. Returns a tuple containing:
757    ///
758    ///  - A boolean that is true if a zoom occurred.
759    ///  - An optional [`ScrollResult`] if a scroll occurred.
760    ///
761    /// It is up to the caller to ensure that these events update the rendering appropriately.
762    pub(crate) fn process_pending_scroll_and_pinch_zoom_events(
763        &mut self,
764        render_api: &RenderApi,
765    ) -> (PinchZoomResult, Option<ScrollResult>) {
766        if self.pending_scroll_zoom_events.is_empty() {
767            return (PinchZoomResult::DidNotPinchZoom, None);
768        }
769
770        // Batch up all scroll events and changes to pinch zoom into a single change, or
771        // else we'll do way too much painting.
772        let mut combined_scroll_event: Option<ScrollEvent> = None;
773        let mut new_pinch_zoom = self.pinch_zoom;
774        let device_pixels_per_page_pixel = self.device_pixels_per_page_pixel();
775
776        for scroll_event in self.pending_scroll_zoom_events.drain(..) {
777            match scroll_event {
778                ScrollZoomEvent::PinchZoom(magnification, center) => {
779                    let new_factor = self
780                        .viewport_description
781                        .clamp_zoom(self.pinch_zoom.zoom_factor().0 * magnification);
782                    new_pinch_zoom.set_zoom(new_factor, center);
783                },
784                ScrollZoomEvent::Scroll(scroll_event_info) => {
785                    let combined_event = match combined_scroll_event.as_mut() {
786                        None => {
787                            combined_scroll_event = Some(scroll_event_info);
788                            continue;
789                        },
790                        Some(combined_event) => combined_event,
791                    };
792
793                    match (combined_event.scroll, scroll_event_info.scroll) {
794                        (Scroll::Delta(old_delta), Scroll::Delta(new_delta)) => {
795                            let old_delta =
796                                old_delta.as_device_vector(device_pixels_per_page_pixel);
797                            let new_delta =
798                                new_delta.as_device_vector(device_pixels_per_page_pixel);
799                            combined_event.scroll = Scroll::Delta((old_delta + new_delta).into());
800                        },
801                        (Scroll::Start, _) | (Scroll::End, _) => {
802                            // Once we see Start or End, we shouldn't process any more events.
803                            break;
804                        },
805                        (_, Scroll::Start) | (_, Scroll::End) => {
806                            // If this is an event which is scrolling to the start or end of the page,
807                            // disregard other pending events and exit the loop.
808                            *combined_event = scroll_event_info;
809                            break;
810                        },
811                    }
812                },
813            }
814        }
815
816        // When zoomed in via pinch zoom, first try to move the center of the zoom and use the rest
817        // of the delta for scrolling. This allows moving the zoomed into viewport around in the
818        // unzoomed viewport before actually scrolling the underlying layers.
819        if let Some(combined_scroll_event) = combined_scroll_event.as_mut() {
820            new_pinch_zoom.pan(
821                &mut combined_scroll_event.scroll,
822                self.device_pixels_per_page_pixel(),
823            )
824        }
825
826        let scroll_result = combined_scroll_event.and_then(|combined_event| {
827            self.scroll_node_at_device_point(
828                render_api,
829                combined_event.point.to_f32(),
830                combined_event.scroll,
831            )
832        });
833        if let Some(ref scroll_result) = scroll_result {
834            self.send_scroll_positions_to_layout_for_pipeline(
835                scroll_result.hit_test_result.pipeline_id,
836                scroll_result.external_scroll_id,
837            );
838        } else {
839            self.touch_handler.stop_fling_if_needed();
840        }
841
842        // Additionally notify pinch zoom update to the script.
843        let pinch_zoom_result = self.set_pinch_zoom(new_pinch_zoom);
844        if pinch_zoom_result == PinchZoomResult::DidPinchZoom {
845            self.send_pinch_zoom_infos_to_script();
846        }
847
848        (pinch_zoom_result, scroll_result)
849    }
850
851    /// Perform a hit test at the given [`DevicePoint`] and apply the [`Scroll`]
852    /// scrolling to the applicable scroll node under that point. If a scroll was
853    /// performed, returns the hit test result contains [`PipelineId`] of the node
854    /// scrolled, the id, and the final scroll delta.
855    fn scroll_node_at_device_point(
856        &mut self,
857        render_api: &RenderApi,
858        cursor: DevicePoint,
859        scroll: Scroll,
860    ) -> Option<ScrollResult> {
861        let scroll_location = match scroll {
862            Scroll::Delta(delta) => {
863                let device_pixels_per_page = self.device_pixels_per_page_pixel();
864                let calculate_delta =
865                    delta.as_device_vector(device_pixels_per_page) / device_pixels_per_page;
866                ScrollLocation::Delta(calculate_delta.cast_unit())
867            },
868            Scroll::Start => ScrollLocation::Start,
869            Scroll::End => ScrollLocation::End,
870        };
871
872        let hit_test_results: Vec<_> = self
873            .touch_handler
874            .get_hit_test_result_cache_value()
875            .map(|result| vec![result])
876            .unwrap_or_else(|| self.hit_test(render_api, cursor));
877
878        // Iterate through all hit test results, processing only the first node of each pipeline.
879        // This is needed to propagate the scroll events from a pipeline representing an iframe to
880        // its ancestor pipelines.
881        let mut previous_pipeline_id = None;
882        for hit_test_result in hit_test_results {
883            let pipeline_details = self.pipelines.get_mut(&hit_test_result.pipeline_id)?;
884            if previous_pipeline_id.replace(hit_test_result.pipeline_id) !=
885                Some(hit_test_result.pipeline_id)
886            {
887                let scroll_result = pipeline_details.scroll_tree.scroll_node_or_ancestor(
888                    hit_test_result.external_scroll_id,
889                    scroll_location,
890                    ScrollType::InputEvents,
891                );
892                if let Some((external_scroll_id, offset)) = scroll_result {
893                    // We would like to cache the hit test for the node that that actually scrolls
894                    // while panning, which we don't know until right now (as some nodes
895                    // might be at the end of their scroll area). In particular, directionality of
896                    // scroll matters. That's why this is done here and not as soon as the touch
897                    // starts.
898                    self.touch_handler.set_hit_test_result_cache_value(
899                        hit_test_result.clone(),
900                        self.device_pixels_per_page_pixel(),
901                    );
902                    return Some(ScrollResult {
903                        hit_test_result,
904                        external_scroll_id,
905                        offset,
906                    });
907                }
908            }
909        }
910        None
911    }
912
913    /// Scroll the viewport (root pipeline, root scroll node) of this WebView, but first
914    /// attempting to pan the pinch zoom viewport. This is called when processing
915    /// key-based scrolling from script.
916    pub(crate) fn scroll_viewport_by_delta(
917        &mut self,
918        delta: LayoutVector2D,
919    ) -> (PinchZoomResult, Vec<ScrollResult>) {
920        let device_pixels_per_page_pixel = self.device_pixels_per_page_pixel();
921        let delta_in_device_pixels = delta.cast_unit() * device_pixels_per_page_pixel;
922        let remaining = self.pinch_zoom.pan_with_device_scroll(
923            Scroll::Delta(delta_in_device_pixels.into()),
924            device_pixels_per_page_pixel,
925        );
926
927        let pinch_zoom_result = match remaining == delta_in_device_pixels {
928            true => PinchZoomResult::DidNotPinchZoom,
929            false => PinchZoomResult::DidPinchZoom,
930        };
931        if remaining == Vector2D::zero() {
932            return (pinch_zoom_result, vec![]);
933        }
934
935        let Some(root_pipeline_id) = self.root_pipeline_id else {
936            return (pinch_zoom_result, vec![]);
937        };
938        let Some(root_pipeline) = self.pipelines.get_mut(&root_pipeline_id) else {
939            return (pinch_zoom_result, vec![]);
940        };
941
942        let remaining = remaining / device_pixels_per_page_pixel;
943        let Some((external_scroll_id, offset)) = root_pipeline.scroll_tree.scroll_node_or_ancestor(
944            ExternalScrollId(0, root_pipeline_id.into()),
945            ScrollLocation::Delta(remaining.cast_unit()),
946            // These are initiated only by keyboard events currently.
947            ScrollType::InputEvents,
948        ) else {
949            return (pinch_zoom_result, vec![]);
950        };
951
952        let hit_test_result = PaintHitTestResult {
953            pipeline_id: root_pipeline_id,
954            // It's difficult to get a good value for this as it needs to be piped
955            // all the way through script and back here.
956            point_in_viewport: Default::default(),
957            external_scroll_id,
958        };
959
960        self.send_scroll_positions_to_layout_for_pipeline(root_pipeline_id, external_scroll_id);
961
962        if pinch_zoom_result == PinchZoomResult::DidPinchZoom {
963            self.send_pinch_zoom_infos_to_script();
964        }
965
966        let scroll_result = ScrollResult {
967            hit_test_result,
968            external_scroll_id,
969            offset,
970        };
971        (pinch_zoom_result, vec![scroll_result])
972    }
973
974    /// Send [`PinchZoom`] update to the script's root pipeline.
975    fn send_pinch_zoom_infos_to_script(&self) {
976        // Pinch-zoom is applicable only to the root pipeline.
977        let Some(pipeline_id) = self.root_pipeline_id else {
978            return;
979        };
980
981        let pinch_zoom_infos = self.pinch_zoom.get_pinch_zoom_infos_for_script(
982            self.device_pixels_per_page_pixel_not_including_pinch_zoom(),
983        );
984
985        let _ = self.embedder_to_constellation_sender.send(
986            EmbedderToConstellationMessage::UpdatePinchZoomInfos(pipeline_id, pinch_zoom_infos),
987        );
988    }
989
990    pub(crate) fn pinch_zoom(&self) -> PinchZoom {
991        self.pinch_zoom
992    }
993
994    fn set_pinch_zoom(&mut self, requested_pinch_zoom: PinchZoom) -> PinchZoomResult {
995        if requested_pinch_zoom == self.pinch_zoom {
996            return PinchZoomResult::DidNotPinchZoom;
997        }
998
999        self.pinch_zoom = requested_pinch_zoom;
1000        PinchZoomResult::DidPinchZoom
1001    }
1002
1003    pub(crate) fn set_page_zoom(
1004        &mut self,
1005        new_page_zoom: Scale<f32, CSSPixel, DeviceIndependentPixel>,
1006    ) {
1007        let new_page_zoom = new_page_zoom.clamp(MIN_PAGE_ZOOM, MAX_PAGE_ZOOM);
1008        let old_zoom = std::mem::replace(&mut self.page_zoom, new_page_zoom);
1009        if old_zoom != self.page_zoom {
1010            self.send_window_size_message();
1011        }
1012    }
1013
1014    /// The scale to use when displaying this [`WebViewRenderer`] in WebRender
1015    /// including both viewport scale (page zoom and hidpi scale) as well as any
1016    /// pinch zoom applied. This is based on the latest display list received,
1017    /// as page zoom changes are applied asynchronously and the rendered view
1018    /// should reflect the latest display list.
1019    pub(crate) fn device_pixels_per_page_pixel(&self) -> Scale<f32, CSSPixel, DevicePixel> {
1020        let viewport_scale = self
1021            .root_pipeline_id
1022            .and_then(|pipeline_id| self.pipelines.get(&pipeline_id))
1023            .and_then(|pipeline| pipeline.viewport_scale)
1024            .unwrap_or_else(|| self.page_zoom * self.hidpi_scale_factor);
1025        viewport_scale * self.pinch_zoom.zoom_factor()
1026    }
1027
1028    /// The current viewport scale (hidpi scale and page zoom and not pinch
1029    /// zoom) based on the current setting of the WebView. Note that this may
1030    /// not be the rendered viewport zoom as that is based on the latest display
1031    /// list and zoom changes are applied asynchronously.
1032    pub(crate) fn device_pixels_per_page_pixel_not_including_pinch_zoom(
1033        &self,
1034    ) -> Scale<f32, CSSPixel, DevicePixel> {
1035        self.page_zoom * self.hidpi_scale_factor
1036    }
1037
1038    /// Adjust the pinch zoom of the [`WebView`] by the given zoom delta.
1039    pub(crate) fn adjust_pinch_zoom(&mut self, magnification: f32, center: DevicePoint) {
1040        if magnification == 1.0 {
1041            return;
1042        }
1043
1044        self.pending_scroll_zoom_events
1045            .push(ScrollZoomEvent::PinchZoom(magnification, center));
1046    }
1047
1048    fn send_window_size_message(&self) {
1049        // The device pixel ratio used by the style system should include the scale from page pixels
1050        // to device pixels, but not including any pinch zoom.
1051        let device_pixel_ratio = self.device_pixels_per_page_pixel_not_including_pinch_zoom();
1052        // From <https://www.w3.org/TR/css-viewport-1/#actual-viewport>:
1053        // This is the viewport you get after processing the viewport <meta> tag.
1054        let layout_viewport = self.rect.size().to_f32() /
1055            (device_pixel_ratio * Scale::new(self.viewport_description.initial_scale.get()));
1056        let _ = self.embedder_to_constellation_sender.send(
1057            EmbedderToConstellationMessage::ChangeViewportDetails(
1058                self.id,
1059                ViewportDetails {
1060                    hidpi_scale_factor: device_pixel_ratio,
1061                    size: layout_viewport,
1062                    device_size: self.screen_size,
1063                },
1064                WindowSizeType::Resize,
1065            ),
1066        );
1067    }
1068
1069    /// Set the `hidpi_scale_factor` for this renderer, returning `true` if the value actually changed.
1070    pub(crate) fn set_hidpi_scale_factor(
1071        &mut self,
1072        new_scale: Scale<f32, DeviceIndependentPixel, DevicePixel>,
1073    ) -> bool {
1074        let old_scale_factor = std::mem::replace(&mut self.hidpi_scale_factor, new_scale);
1075        if self.hidpi_scale_factor == old_scale_factor {
1076            return false;
1077        }
1078
1079        self.send_window_size_message();
1080        true
1081    }
1082
1083    /// Set the `screen_size` for this renderer, returning `true` if the value actually changed.
1084    pub(crate) fn set_screen_size(&mut self, new_size: Size2D<f32, DevicePixel>) -> bool {
1085        if self.screen_size == new_size {
1086            return false;
1087        }
1088        self.screen_size = new_size;
1089
1090        self.send_window_size_message();
1091        true
1092    }
1093
1094    /// Set the `rect` for this renderer, returning `true` if the value actually changed.
1095    pub(crate) fn set_rect(&mut self, new_rect: DeviceRect) -> bool {
1096        let old_rect = std::mem::replace(&mut self.rect, new_rect);
1097        if old_rect.size() != self.rect.size() {
1098            self.send_window_size_message();
1099            self.pinch_zoom.resize_unscaled_viewport(new_rect);
1100            self.send_pinch_zoom_infos_to_script();
1101        }
1102        old_rect != self.rect
1103    }
1104
1105    pub fn set_viewport_description(&mut self, viewport_description: ViewportDescription) {
1106        self.viewport_description = viewport_description;
1107        self.send_window_size_message();
1108        self.adjust_pinch_zoom(
1109            self.viewport_description.initial_scale.get(),
1110            DevicePoint::origin(),
1111        );
1112    }
1113
1114    pub(crate) fn scroll_trees_memory_usage(
1115        &self,
1116        ops: &mut malloc_size_of::MallocSizeOfOps,
1117    ) -> usize {
1118        self.pipelines
1119            .values()
1120            .map(|pipeline| pipeline.scroll_tree.size_of(ops))
1121            .sum::<usize>()
1122    }
1123
1124    pub(crate) fn notify_input_event_handled(
1125        &mut self,
1126        render_api: &RenderApi,
1127        repaint_reason: &Cell<RepaintReason>,
1128        id: InputEventId,
1129        result: InputEventResult,
1130    ) {
1131        if let Some(pending_touch_input_event) =
1132            self.touch_handler.take_pending_touch_input_event(id)
1133        {
1134            self.on_touch_event_processed(render_api, pending_touch_input_event, result);
1135            self.touch_handler
1136                .add_touch_move_refresh_observer_if_necessary(
1137                    self.refresh_driver.clone(),
1138                    repaint_reason,
1139                );
1140        }
1141
1142        if let Some(wheel_event) = self.pending_wheel_events.remove(&id) &&
1143            !result.contains(InputEventResult::DefaultPrevented)
1144        {
1145            // A scroll delta for a wheel event is the inverse of the wheel delta.
1146            let scroll_delta =
1147                DeviceVector2D::new(-wheel_event.delta.x as f32, -wheel_event.delta.y as f32);
1148            self.notify_scroll_event(Scroll::Delta(scroll_delta.into()), wheel_event.point);
1149        }
1150    }
1151}
1152
1153#[derive(Clone, Copy, Debug, PartialEq)]
1154pub struct UnknownWebView(pub WebViewId);