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