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