paint/
webview_renderer.rs

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