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