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