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