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