1use std::cell::Cell;
6use std::collections::hash_map::Entry;
7use std::rc::Rc;
8
9use crossbeam_channel::Sender;
10use embedder_traits::{
11 AnimationState, InputEvent, InputEventAndId, InputEventId, InputEventResult,
12 PaintHitTestResult, Scroll, TouchEvent, TouchEventType, ViewportDetails, WebViewPoint,
13 WheelEvent,
14};
15use euclid::{Scale, Size2D, Vector2D};
16use log::{debug, warn};
17use malloc_size_of::MallocSizeOf;
18use paint_api::display_list::ScrollType;
19use paint_api::viewport_description::{
20 DEFAULT_PAGE_ZOOM, MAX_PAGE_ZOOM, MIN_PAGE_ZOOM, ViewportDescription,
21};
22use paint_api::{PipelineExitSource, SendableFrameTree, WebViewTrait};
23use rustc_hash::FxHashMap;
24use servo_base::id::{PipelineId, WebViewId};
25use servo_constellation_traits::{
26 EmbedderToConstellationMessage, ScrollStateUpdate, WindowSizeType,
27};
28use servo_geometry::DeviceIndependentPixel;
29use style_traits::CSSPixel;
30use webrender::RenderApi;
31use webrender_api::units::{
32 DevicePixel, DevicePoint, DeviceRect, DeviceVector2D, LayoutTransform, LayoutVector2D,
33};
34use webrender_api::{DocumentId, ExternalScrollId, PropertyBindingKey, ScrollLocation};
35
36use crate::paint::RepaintReason;
37use crate::painter::Painter;
38use crate::pinch_zoom::PinchZoom;
39use crate::pipeline_details::PipelineDetails;
40use crate::refresh_driver::BaseRefreshDriver;
41use crate::touch::{
42 PanPolicyInput, PendingTouchInputEvent, TouchHandler, TouchIdMoveTracking, TouchMoveAllowed,
43 TouchSequenceState,
44};
45
46#[derive(Clone, Copy)]
47pub(crate) struct ScrollEvent {
48 pub scroll: Scroll,
50 pub point: DevicePoint,
52 pub scroll_type: ScrollType,
55}
56
57#[derive(Clone, Copy)]
58pub(crate) enum ScrollZoomEvent {
59 PinchZoom(f32, DevicePoint),
62 Scroll(ScrollEvent),
65}
66
67#[derive(Clone, Debug)]
68pub(crate) struct ScrollResult {
69 pub hit_test_result: PaintHitTestResult,
70 pub external_scroll_id: ExternalScrollId,
75 pub offset: LayoutVector2D,
76}
77
78#[derive(Debug, PartialEq)]
79pub(crate) enum PinchZoomResult {
80 DidPinchZoom,
81 DidNotPinchZoom,
82}
83
84pub(crate) struct WebViewRenderer {
88 pub id: WebViewId,
90 pub webview: Box<dyn WebViewTrait>,
94 pub root_pipeline_id: Option<PipelineId>,
96 pub rect: DeviceRect,
98 pub pipelines: FxHashMap<PipelineId, PipelineDetails>,
100 pending_scroll_zoom_events: Vec<ScrollZoomEvent>,
102 pending_wheel_events: FxHashMap<InputEventId, WheelEvent>,
106 touch_handler: TouchHandler,
108 pub page_zoom: Scale<f32, CSSPixel, DeviceIndependentPixel>,
110 pinch_zoom: PinchZoom,
113 hidpi_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
116 hidden: bool,
118 animating: bool,
121 viewport_description: ViewportDescription,
124 screen_size: Size2D<f32, DevicePixel>,
126 pinch_zoom_property_binding_key: PropertyBindingKey<LayoutTransform>,
128
129 embedder_to_constellation_sender: Sender<EmbedderToConstellationMessage>,
134 refresh_driver: Rc<BaseRefreshDriver>,
136 webrender_document: DocumentId,
138}
139
140impl WebViewRenderer {
141 pub(crate) fn new(
142 renderer_webview: Box<dyn WebViewTrait>,
143 viewport_details: ViewportDetails,
144 embedder_to_constellation_sender: Sender<EmbedderToConstellationMessage>,
145 refresh_driver: Rc<BaseRefreshDriver>,
146 webrender_document: DocumentId,
147 pinch_zoom_property_binding_key: PropertyBindingKey<LayoutTransform>,
148 ) -> Self {
149 let hidpi_scale_factor = viewport_details.hidpi_scale_factor;
150 let size = viewport_details.size * viewport_details.hidpi_scale_factor;
151 let rect = DeviceRect::from_origin_and_size(DevicePoint::origin(), size);
152 let webview_id = renderer_webview.id();
153 Self {
154 id: webview_id,
155 webview: renderer_webview,
156 root_pipeline_id: None,
157 rect,
158 pipelines: Default::default(),
159 touch_handler: TouchHandler::new(webview_id),
160 pending_scroll_zoom_events: Default::default(),
161 pending_wheel_events: Default::default(),
162 page_zoom: DEFAULT_PAGE_ZOOM,
163 pinch_zoom: PinchZoom::new(rect),
164 hidpi_scale_factor: Scale::new(hidpi_scale_factor.0),
165 hidden: false,
166 animating: false,
167 viewport_description: Default::default(),
168 screen_size: viewport_details.device_size,
169 embedder_to_constellation_sender,
170 refresh_driver,
171 webrender_document,
172 pinch_zoom_property_binding_key,
173 }
174 }
175
176 fn hit_test(&self, webrender_api: &RenderApi, point: DevicePoint) -> Vec<PaintHitTestResult> {
177 Painter::hit_test_at_point_with_api_and_document(
178 webrender_api,
179 self.webrender_document,
180 point,
181 )
182 }
183
184 pub(crate) fn animation_callbacks_running(&self) -> bool {
185 self.pipelines
186 .values()
187 .any(PipelineDetails::animation_callbacks_running)
188 }
189
190 pub(crate) fn animating(&self) -> bool {
193 self.animating && !self.hidden()
194 }
195
196 pub(crate) fn hidden(&self) -> bool {
197 self.hidden
198 }
199
200 pub(crate) fn set_hidden(&mut self, new_value: bool) -> bool {
203 let old_value = std::mem::replace(&mut self.hidden, new_value);
204 if new_value == old_value {
205 return false;
206 }
207
208 let _ = self.embedder_to_constellation_sender.send(
209 EmbedderToConstellationMessage::SetWebViewHidden(self.id, new_value),
210 );
211 self.update_animation_state();
212 true
213 }
214
215 pub(crate) fn ensure_pipeline_details(
217 &mut self,
218 pipeline_id: PipelineId,
219 ) -> &mut PipelineDetails {
220 self.pipelines
221 .entry(pipeline_id)
222 .or_insert_with(PipelineDetails::new)
223 }
224
225 pub(crate) fn pipeline_exited(
229 &mut self,
230 pipeline_id: PipelineId,
231 source: PipelineExitSource,
232 ) -> bool {
233 let pipeline = self.pipelines.entry(pipeline_id);
234 let Entry::Occupied(mut pipeline) = pipeline else {
235 return false;
236 };
237
238 pipeline.get_mut().exited.insert(source);
239
240 if !pipeline.get().exited.is_all() {
244 return false;
245 }
246
247 pipeline.get_mut().lcp_candidates.clear();
249
250 pipeline.remove_entry();
251 true
252 }
253
254 pub(crate) fn set_frame_tree(&mut self, frame_tree: &SendableFrameTree) {
255 let pipeline_id = frame_tree.pipeline.id;
256 let old_pipeline_id = self.root_pipeline_id.replace(pipeline_id);
257
258 if old_pipeline_id != self.root_pipeline_id {
259 debug!(
260 "Updating webview ({:?}) from pipeline {:?} to {:?}",
261 3, old_pipeline_id, self.root_pipeline_id
262 );
263 }
264
265 self.set_frame_tree_on_pipeline_details(frame_tree, None);
266 }
267
268 pub(crate) fn send_scroll_positions_to_layout_for_pipeline(
269 &self,
270 pipeline_id: PipelineId,
271 scrolled_node: ExternalScrollId,
272 ) {
273 let Some(details) = self.pipelines.get(&pipeline_id) else {
274 return;
275 };
276
277 let offsets = details.scroll_tree.scroll_offsets();
278
279 if offsets.is_empty() {
284 return;
285 }
286
287 let _ = self.embedder_to_constellation_sender.send(
288 EmbedderToConstellationMessage::SetScrollStates(
289 pipeline_id,
290 ScrollStateUpdate {
291 scrolled_node,
292 offsets,
293 },
294 ),
295 );
296 }
297
298 pub(crate) fn set_frame_tree_on_pipeline_details(
299 &mut self,
300 frame_tree: &SendableFrameTree,
301 parent_pipeline_id: Option<PipelineId>,
302 ) {
303 let pipeline_id = frame_tree.pipeline.id;
304 let pipeline_details = self.ensure_pipeline_details(pipeline_id);
305 pipeline_details.pipeline = Some(frame_tree.pipeline.clone());
306 pipeline_details.parent_pipeline_id = parent_pipeline_id;
307 pipeline_details.children = frame_tree
308 .children
309 .iter()
310 .map(|frame_tree| frame_tree.pipeline.id)
311 .collect();
312
313 for kid in &frame_tree.children {
314 self.set_frame_tree_on_pipeline_details(kid, Some(pipeline_id));
315 }
316 }
317
318 pub(crate) fn change_pipeline_running_animations_state(
321 &mut self,
322 pipeline_id: PipelineId,
323 animation_state: AnimationState,
324 ) -> bool {
325 let pipeline_details = self.ensure_pipeline_details(pipeline_id);
326 let was_animating = pipeline_details.animating();
327 match animation_state {
328 AnimationState::AnimationsPresent => {
329 pipeline_details.animations_running = true;
330 },
331 AnimationState::AnimationCallbacksPresent => {
332 pipeline_details.animation_callbacks_running = true;
333 },
334 AnimationState::NoAnimationsPresent => {
335 pipeline_details.animations_running = false;
336 },
337 AnimationState::AnimationCallbacksAbsent => {
338 pipeline_details.animation_callbacks_running = false;
339 },
340 }
341 let started_animating = !was_animating && pipeline_details.animating();
342
343 self.update_animation_state();
344
345 started_animating
350 }
351
352 pub(crate) fn set_throttled(&mut self, pipeline_id: PipelineId, throttled: bool) -> bool {
355 let pipeline_details = self.ensure_pipeline_details(pipeline_id);
356 let was_animating = pipeline_details.animating();
357 pipeline_details.throttled = throttled;
358 let started_animating = !was_animating && pipeline_details.animating();
359
360 self.update_animation_state();
362
363 started_animating
368 }
369
370 fn update_animation_state(&mut self) {
371 self.animating = self.pipelines.values().any(PipelineDetails::animating);
372 self.webview.set_animating(self.animating());
373 }
374
375 pub(crate) fn for_each_connected_pipeline(&self, callback: &mut impl FnMut(&PipelineDetails)) {
376 if let Some(root_pipeline_id) = self.root_pipeline_id {
377 self.for_each_connected_pipeline_internal(root_pipeline_id, callback);
378 }
379 }
380
381 fn for_each_connected_pipeline_internal(
382 &self,
383 pipeline_id: PipelineId,
384 callback: &mut impl FnMut(&PipelineDetails),
385 ) {
386 let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
387 return;
388 };
389 callback(pipeline);
390 for child_pipeline_id in &pipeline.children {
391 self.for_each_connected_pipeline_internal(*child_pipeline_id, callback);
392 }
393 }
394
395 pub(crate) fn update_touch_handling_at_new_frame_start(&mut self) -> bool {
399 let Some(fling_action) = self.touch_handler.notify_new_frame_start() else {
400 return false;
401 };
402
403 self.on_scroll_window_event(
404 Scroll::Delta((-fling_action.delta).into()),
405 fling_action.cursor,
406 ScrollType::Touch,
409 );
410 true
411 }
412
413 fn dispatch_input_event_with_hit_testing(
414 &mut self,
415 render_api: &RenderApi,
416 event: InputEventAndId,
417 ) -> bool {
418 let event_point = event
419 .event
420 .point()
421 .map(|point| point.as_device_point(self.device_pixels_per_page_pixel()));
422 let is_touch_down = matches!(
423 &event.event,
424 InputEvent::Touch(TouchEvent {
425 event_type: TouchEventType::Down,
426 ..
427 })
428 );
429 let hit_test_result = match event_point {
430 Some(point) => {
431 let hit_test_result = match event.event {
432 InputEvent::Touch(_) => self.touch_handler.get_hit_test_result_cache_value(),
433 _ => None,
434 }
435 .or_else(|| self.hit_test(render_api, point).into_iter().nth(0));
436 if hit_test_result.is_none() {
437 warn!("Empty hit test result for input event, ignoring.");
438 return false;
439 }
440 hit_test_result
441 },
442 None => None,
443 };
444
445 if is_touch_down && let Some(hit) = &hit_test_result {
449 let policy_input = self
450 .pipelines
451 .get(&hit.pipeline_id)
452 .and_then(|pipeline_details| {
453 pipeline_details
454 .scroll_tree
455 .touch_action_and_scrollable_axes_for(hit.external_scroll_id)
456 })
457 .map(
458 |(touch_action, scrollable_x, scrollable_y)| PanPolicyInput {
459 touch_action,
460 scrollable_x,
461 scrollable_y,
462 },
463 );
464 if let Some(input) = policy_input {
465 self.touch_handler.set_pan_policy_input(input);
466 }
467 }
468
469 if let Err(error) = self.embedder_to_constellation_sender.send(
470 EmbedderToConstellationMessage::ForwardInputEvent(self.id, event, hit_test_result),
471 ) {
472 warn!("Sending event to constellation failed ({error:?}).");
473 false
474 } else {
475 true
476 }
477 }
478
479 pub(crate) fn notify_input_event(
480 &mut self,
481 render_api: &RenderApi,
482 repaint_reason: &Cell<RepaintReason>,
483 event_and_id: InputEventAndId,
484 ) -> bool {
485 if let InputEvent::Touch(touch_event) = event_and_id.event {
486 return self.on_touch_event(render_api, repaint_reason, touch_event, event_and_id.id);
487 }
488
489 if let InputEvent::Wheel(wheel_event) = event_and_id.event {
490 self.pending_wheel_events
491 .insert(event_and_id.id, wheel_event);
492 }
493
494 self.dispatch_input_event_with_hit_testing(render_api, event_and_id)
495 }
496
497 fn send_touch_event(
498 &mut self,
499 render_api: &RenderApi,
500 event: TouchEvent,
501 id: InputEventId,
502 ) -> bool {
503 let cancelable = event.is_cancelable();
504 let event_type = event.event_type;
505
506 let input_event_and_id = InputEventAndId {
507 event: InputEvent::Touch(event),
508 id,
509 };
510
511 let result = self.dispatch_input_event_with_hit_testing(render_api, input_event_and_id);
512
513 if cancelable && result {
517 self.touch_handler
518 .add_pending_touch_input_event(id, event.touch_id, event_type);
519 }
520
521 result
522 }
523
524 pub(crate) fn on_touch_event(
525 &mut self,
526 render_api: &RenderApi,
527 repaint_reason: &Cell<RepaintReason>,
528 event: TouchEvent,
529 id: InputEventId,
530 ) -> bool {
531 let result = match event.event_type {
532 TouchEventType::Down => self.on_touch_down(render_api, event, id),
533 TouchEventType::Move => self.on_touch_move(render_api, event, id),
534 TouchEventType::Up => self.on_touch_up(render_api, event, id),
535 TouchEventType::Cancel => self.on_touch_cancel(render_api, event, id),
536 };
537
538 self.touch_handler
539 .add_touch_move_refresh_observer_if_necessary(
540 self.refresh_driver.clone(),
541 repaint_reason,
542 );
543 result
544 }
545
546 fn on_touch_down(
547 &mut self,
548 render_api: &RenderApi,
549 event: TouchEvent,
550 id: InputEventId,
551 ) -> bool {
552 let point = event
553 .point
554 .as_device_point(self.device_pixels_per_page_pixel());
555 self.touch_handler.on_touch_down(event.touch_id, point);
556 self.send_touch_event(render_api, event, id)
557 }
558
559 fn on_touch_move(
560 &mut self,
561 render_api: &RenderApi,
562 mut event: TouchEvent,
563 id: InputEventId,
564 ) -> bool {
565 let point = event
566 .point
567 .as_device_point(self.device_pixels_per_page_pixel());
568 let action = self.touch_handler.on_touch_move(
569 event.touch_id,
570 point,
571 self.device_pixels_per_page_pixel_not_including_pinch_zoom()
572 .get(),
573 );
574 if let Some(action) = action {
575 if self
578 .touch_handler
579 .move_allowed(self.touch_handler.current_sequence_id)
580 {
581 event.disable_cancelable();
583 self.pending_scroll_zoom_events.push(action);
584 }
585 }
586 let mut reached_constellation = false;
587 if !self.touch_handler.is_handling_touch_move_for_touch_id(
591 self.touch_handler.current_sequence_id,
592 event.touch_id,
593 ) {
594 reached_constellation = self.send_touch_event(render_api, event, id);
595 if reached_constellation && event.is_cancelable() {
596 self.touch_handler.set_handling_touch_move_for_touch_id(
597 self.touch_handler.current_sequence_id,
598 event.touch_id,
599 TouchIdMoveTracking::Track,
600 );
601 }
602 }
603 reached_constellation
604 }
605
606 fn on_touch_up(
607 &mut self,
608 render_api: &RenderApi,
609 mut event: TouchEvent,
610 id: InputEventId,
611 ) -> bool {
612 let point = event
613 .point
614 .as_device_point(self.device_pixels_per_page_pixel());
615 if self.touch_handler.on_touch_up(event.touch_id, point) {
617 event.can_synthesize_mouse_events = true;
618 }
619 self.send_touch_event(render_api, event, id)
620 }
621
622 fn on_touch_cancel(
623 &mut self,
624 render_api: &RenderApi,
625 event: TouchEvent,
626 id: InputEventId,
627 ) -> bool {
628 let point = event
629 .point
630 .as_device_point(self.device_pixels_per_page_pixel());
631 self.touch_handler.on_touch_cancel(event.touch_id, point);
632 self.send_touch_event(render_api, event, id)
633 }
634
635 fn on_touch_event_processed(
636 &mut self,
637 pending_touch_input_event: PendingTouchInputEvent,
638 result: InputEventResult,
639 ) {
640 let PendingTouchInputEvent {
641 sequence_id,
642 event_type,
643 touch_id,
644 } = pending_touch_input_event;
645
646 if result.intersects(InputEventResult::DefaultPrevented | InputEventResult::Consumed) {
649 debug!(
650 "Touch event {:?} in sequence {:?} prevented or consumed",
651 event_type, sequence_id
652 );
653 match event_type {
654 TouchEventType::Down => {
655 self.touch_handler.prevent_click(sequence_id);
657 self.touch_handler.prevent_move(sequence_id);
658 self.touch_handler
659 .remove_pending_touch_move_actions(sequence_id);
660 },
661 TouchEventType::Move => {
662 if let Some(info) = self.touch_handler.get_touch_sequence_mut(sequence_id) {
664 info.prevent_move = TouchMoveAllowed::Prevented;
665 if let TouchSequenceState::PendingFling { .. } = info.state {
666 info.state = TouchSequenceState::Finished;
667 }
668 self.touch_handler.set_handling_touch_move_for_touch_id(
669 self.touch_handler.current_sequence_id,
670 touch_id,
671 TouchIdMoveTracking::Remove,
672 );
673 self.touch_handler
674 .remove_pending_touch_move_actions(sequence_id);
675 }
676 },
677 TouchEventType::Up => {
678 let Some(info) = &mut self.touch_handler.get_touch_sequence_mut(sequence_id)
682 else {
683 return;
688 };
689 match info.state {
690 TouchSequenceState::PendingClick => {
691 info.state = TouchSequenceState::Finished;
692 self.touch_handler.remove_touch_sequence(sequence_id);
693 },
694 TouchSequenceState::Flinging { .. } => {
695 },
697 TouchSequenceState::Finished => {
698 self.touch_handler.remove_touch_sequence(sequence_id);
699 },
700 TouchSequenceState::Touching |
701 TouchSequenceState::Panning { .. } |
702 TouchSequenceState::Pinching |
703 TouchSequenceState::MultiTouch |
704 TouchSequenceState::PendingFling { .. } => {
705 },
710 }
711 },
712 TouchEventType::Cancel => {
713 self.touch_handler
716 .remove_pending_touch_move_actions(sequence_id);
717 self.touch_handler.try_remove_touch_sequence(sequence_id);
718 },
719 }
720 } else {
721 debug!(
722 "Touch event {:?} in sequence {:?} allowed",
723 event_type, sequence_id
724 );
725 match event_type {
726 TouchEventType::Down => {},
727 TouchEventType::Move => {
728 self.pending_scroll_zoom_events.extend(
729 self.touch_handler
730 .take_pending_touch_move_actions(sequence_id),
731 );
732 self.touch_handler.set_handling_touch_move_for_touch_id(
733 self.touch_handler.current_sequence_id,
734 touch_id,
735 TouchIdMoveTracking::Remove,
736 );
737 if let Some(info) = self.touch_handler.get_touch_sequence_mut(sequence_id) &&
738 info.prevent_move == TouchMoveAllowed::Pending
739 {
740 info.prevent_move = TouchMoveAllowed::Allowed;
741 if let TouchSequenceState::PendingFling { velocity, point } = info.state {
742 info.state = TouchSequenceState::Flinging { velocity, point }
743 }
744 }
745 },
746 TouchEventType::Up => {
747 let Some(info) = self.touch_handler.get_touch_sequence_mut(sequence_id) else {
748 return;
750 };
751 match info.state {
752 TouchSequenceState::PendingClick => {
753 info.state = TouchSequenceState::Finished;
754 self.touch_handler.remove_touch_sequence(sequence_id);
755 },
756 TouchSequenceState::Flinging { .. } => {
757 },
759 TouchSequenceState::Finished => {
760 self.touch_handler.remove_touch_sequence(sequence_id);
761 },
762 TouchSequenceState::Panning { .. } |
763 TouchSequenceState::Pinching |
764 TouchSequenceState::PendingFling { .. } => {
765 },
770 TouchSequenceState::MultiTouch | TouchSequenceState::Touching => {
771 },
773 }
774 },
775 TouchEventType::Cancel => {
776 self.touch_handler
777 .remove_pending_touch_move_actions(sequence_id);
778 self.touch_handler.try_remove_touch_sequence(sequence_id);
779 },
780 }
781 }
782 }
783
784 pub(crate) fn notify_scroll_event(&mut self, scroll: Scroll, point: WebViewPoint) {
785 let point = point.as_device_point(self.device_pixels_per_page_pixel());
786 self.on_scroll_window_event(scroll, point, ScrollType::InputEvents);
787 }
788
789 fn on_scroll_window_event(
790 &mut self,
791 scroll: Scroll,
792 cursor: DevicePoint,
793 scroll_type: ScrollType,
794 ) {
795 self.pending_scroll_zoom_events
796 .push(ScrollZoomEvent::Scroll(ScrollEvent {
797 scroll,
798 point: cursor,
799 scroll_type,
800 }));
801 }
802
803 pub(crate) fn process_pending_scroll_and_pinch_zoom_events(
810 &mut self,
811 render_api: &RenderApi,
812 ) -> (PinchZoomResult, Option<ScrollResult>) {
813 if self.pending_scroll_zoom_events.is_empty() {
814 return (PinchZoomResult::DidNotPinchZoom, None);
815 }
816
817 let mut combined_scroll_event: Option<ScrollEvent> = None;
820 let mut new_pinch_zoom = self.pinch_zoom;
821 let device_pixels_per_page_pixel = self.device_pixels_per_page_pixel();
822
823 for scroll_event in self.pending_scroll_zoom_events.drain(..) {
824 match scroll_event {
825 ScrollZoomEvent::PinchZoom(magnification, center) => {
826 let new_factor = self
827 .viewport_description
828 .clamp_zoom(self.pinch_zoom.zoom_factor().0 * magnification);
829 new_pinch_zoom.set_zoom(new_factor, center);
830 },
831 ScrollZoomEvent::Scroll(scroll_event_info) => {
832 let combined_event = match combined_scroll_event.as_mut() {
833 None => {
834 combined_scroll_event = Some(scroll_event_info);
835 continue;
836 },
837 Some(combined_event) => combined_event,
838 };
839
840 match (combined_event.scroll, scroll_event_info.scroll) {
841 (Scroll::Delta(old_delta), Scroll::Delta(new_delta)) => {
842 let old_delta =
843 old_delta.as_device_vector(device_pixels_per_page_pixel);
844 let new_delta =
845 new_delta.as_device_vector(device_pixels_per_page_pixel);
846 combined_event.scroll = Scroll::Delta((old_delta + new_delta).into());
847 },
848 (Scroll::Start, _) | (Scroll::End, _) => {
849 break;
851 },
852 (_, Scroll::Start) | (_, Scroll::End) => {
853 *combined_event = scroll_event_info;
856 break;
857 },
858 }
859 },
860 }
861 }
862
863 if let Some(combined_scroll_event) = combined_scroll_event.as_mut() {
867 new_pinch_zoom.pan(
868 &mut combined_scroll_event.scroll,
869 self.device_pixels_per_page_pixel(),
870 )
871 }
872
873 let scroll_result = combined_scroll_event.and_then(|combined_event| {
874 self.scroll_node_at_device_point(
875 render_api,
876 combined_event.point.to_f32(),
877 combined_event.scroll,
878 combined_event.scroll_type,
879 )
880 });
881 if let Some(ref scroll_result) = scroll_result {
882 self.send_scroll_positions_to_layout_for_pipeline(
883 scroll_result.hit_test_result.pipeline_id,
884 scroll_result.external_scroll_id,
885 );
886 } else {
887 self.touch_handler.stop_fling_if_needed();
888 }
889
890 let pinch_zoom_result = self.set_pinch_zoom(new_pinch_zoom);
892 if pinch_zoom_result == PinchZoomResult::DidPinchZoom {
893 self.send_pinch_zoom_infos_to_script();
894 self.webview.notify_viewport_updated();
898 }
899
900 (pinch_zoom_result, scroll_result)
901 }
902
903 fn scroll_node_at_device_point(
908 &mut self,
909 render_api: &RenderApi,
910 cursor: DevicePoint,
911 scroll: Scroll,
912 scroll_type: ScrollType,
913 ) -> Option<ScrollResult> {
914 let scroll_location = match scroll {
915 Scroll::Delta(delta) => {
916 let device_pixels_per_page = self.device_pixels_per_page_pixel();
917 let calculate_delta =
918 delta.as_device_vector(device_pixels_per_page) / device_pixels_per_page;
919 ScrollLocation::Delta(calculate_delta.cast_unit())
920 },
921 Scroll::Start => ScrollLocation::Start,
922 Scroll::End => ScrollLocation::End,
923 };
924
925 let hit_test_results: Vec<_> = self
926 .touch_handler
927 .get_hit_test_result_cache_value()
928 .map(|result| vec![result])
929 .unwrap_or_else(|| self.hit_test(render_api, cursor));
930
931 let mut previous_pipeline_id = None;
935 for hit_test_result in hit_test_results {
936 let pipeline_details = self.pipelines.get_mut(&hit_test_result.pipeline_id)?;
937 if previous_pipeline_id.replace(hit_test_result.pipeline_id) !=
938 Some(hit_test_result.pipeline_id)
939 {
940 let scroll_result = pipeline_details.scroll_tree.scroll_node_or_ancestor(
941 hit_test_result.external_scroll_id,
942 scroll_location,
943 scroll_type,
944 );
945 if let Some((external_scroll_id, offset)) = scroll_result {
946 self.touch_handler.set_hit_test_result_cache_value(
952 hit_test_result.clone(),
953 self.device_pixels_per_page_pixel(),
954 );
955 return Some(ScrollResult {
956 hit_test_result,
957 external_scroll_id,
958 offset,
959 });
960 }
961 }
962 }
963 None
964 }
965
966 pub(crate) fn scroll_viewport_by_delta(
970 &mut self,
971 delta: LayoutVector2D,
972 ) -> (PinchZoomResult, Vec<ScrollResult>) {
973 let device_pixels_per_page_pixel = self.device_pixels_per_page_pixel();
974 let delta_in_device_pixels = delta.cast_unit() * device_pixels_per_page_pixel;
975 let remaining = self.pinch_zoom.pan_with_device_scroll(
976 Scroll::Delta(delta_in_device_pixels.into()),
977 device_pixels_per_page_pixel,
978 );
979
980 let pinch_zoom_result = match remaining == delta_in_device_pixels {
981 true => PinchZoomResult::DidNotPinchZoom,
982 false => PinchZoomResult::DidPinchZoom,
983 };
984 if remaining == Vector2D::zero() {
985 return (pinch_zoom_result, vec![]);
986 }
987
988 let Some(root_pipeline_id) = self.root_pipeline_id else {
989 return (pinch_zoom_result, vec![]);
990 };
991 let Some(root_pipeline) = self.pipelines.get_mut(&root_pipeline_id) else {
992 return (pinch_zoom_result, vec![]);
993 };
994
995 let remaining = remaining / device_pixels_per_page_pixel;
996 let Some((external_scroll_id, offset)) = root_pipeline.scroll_tree.scroll_node_or_ancestor(
997 ExternalScrollId(0, root_pipeline_id.into()),
998 ScrollLocation::Delta(remaining.cast_unit()),
999 ScrollType::InputEvents,
1001 ) else {
1002 return (pinch_zoom_result, vec![]);
1003 };
1004
1005 let hit_test_result = PaintHitTestResult {
1006 pipeline_id: root_pipeline_id,
1007 point_in_viewport: Default::default(),
1010 external_scroll_id,
1011 };
1012
1013 self.send_scroll_positions_to_layout_for_pipeline(root_pipeline_id, external_scroll_id);
1014
1015 if pinch_zoom_result == PinchZoomResult::DidPinchZoom {
1016 self.send_pinch_zoom_infos_to_script();
1017 }
1018
1019 let scroll_result = ScrollResult {
1020 hit_test_result,
1021 external_scroll_id,
1022 offset,
1023 };
1024 (pinch_zoom_result, vec![scroll_result])
1025 }
1026
1027 fn send_pinch_zoom_infos_to_script(&self) {
1029 let Some(pipeline_id) = self.root_pipeline_id else {
1031 return;
1032 };
1033
1034 let pinch_zoom_infos = self.pinch_zoom.get_pinch_zoom_infos_for_script(
1035 self.device_pixels_per_page_pixel_not_including_pinch_zoom(),
1036 );
1037
1038 let _ = self.embedder_to_constellation_sender.send(
1039 EmbedderToConstellationMessage::UpdatePinchZoomInfos(pipeline_id, pinch_zoom_infos),
1040 );
1041 }
1042
1043 pub(crate) fn pinch_zoom(&self) -> PinchZoom {
1044 self.pinch_zoom
1045 }
1046
1047 fn set_pinch_zoom(&mut self, requested_pinch_zoom: PinchZoom) -> PinchZoomResult {
1048 if requested_pinch_zoom == self.pinch_zoom {
1049 return PinchZoomResult::DidNotPinchZoom;
1050 }
1051
1052 self.pinch_zoom = requested_pinch_zoom;
1053 PinchZoomResult::DidPinchZoom
1054 }
1055
1056 pub(crate) fn pinch_zoom_property_binding_key(&self) -> PropertyBindingKey<LayoutTransform> {
1057 self.pinch_zoom_property_binding_key
1058 }
1059
1060 pub(crate) fn set_page_zoom(
1062 &mut self,
1063 new_page_zoom: Scale<f32, CSSPixel, DeviceIndependentPixel>,
1064 ) -> bool {
1065 let new_page_zoom = new_page_zoom.clamp(MIN_PAGE_ZOOM, MAX_PAGE_ZOOM);
1066 let old_zoom = std::mem::replace(&mut self.page_zoom, new_page_zoom);
1067 if old_zoom == self.page_zoom {
1068 return false;
1069 }
1070 self.send_window_size_message();
1071 true
1072 }
1073
1074 pub(crate) fn device_pixels_per_page_pixel(&self) -> Scale<f32, CSSPixel, DevicePixel> {
1080 let viewport_scale = self
1081 .root_pipeline_id
1082 .and_then(|pipeline_id| self.pipelines.get(&pipeline_id))
1083 .and_then(|pipeline| pipeline.viewport_scale)
1084 .unwrap_or_else(|| self.page_zoom * self.hidpi_scale_factor);
1085 viewport_scale * self.pinch_zoom.zoom_factor()
1086 }
1087
1088 pub(crate) fn device_pixels_per_page_pixel_not_including_pinch_zoom(
1093 &self,
1094 ) -> Scale<f32, CSSPixel, DevicePixel> {
1095 self.page_zoom * self.hidpi_scale_factor
1096 }
1097
1098 pub(crate) fn adjust_pinch_zoom(&mut self, magnification: f32, center: DevicePoint) {
1100 if magnification == 1.0 {
1101 return;
1102 }
1103
1104 self.pending_scroll_zoom_events
1105 .push(ScrollZoomEvent::PinchZoom(magnification, center));
1106 }
1107
1108 fn send_window_size_message(&self) {
1109 let device_pixel_ratio = self.device_pixels_per_page_pixel_not_including_pinch_zoom();
1112 let layout_viewport = self.rect.size().to_f32() /
1115 (device_pixel_ratio * Scale::new(self.viewport_description.initial_scale.get()));
1116 let _ = self.embedder_to_constellation_sender.send(
1117 EmbedderToConstellationMessage::ChangeViewportDetails(
1118 self.id,
1119 ViewportDetails {
1120 hidpi_scale_factor: device_pixel_ratio,
1121 size: layout_viewport,
1122 device_size: self.screen_size,
1123 },
1124 WindowSizeType::Resize,
1125 ),
1126 );
1127 }
1128
1129 pub(crate) fn set_hidpi_scale_factor(
1131 &mut self,
1132 new_scale: Scale<f32, DeviceIndependentPixel, DevicePixel>,
1133 ) -> bool {
1134 let old_scale_factor = std::mem::replace(&mut self.hidpi_scale_factor, new_scale);
1135 if self.hidpi_scale_factor == old_scale_factor {
1136 return false;
1137 }
1138
1139 self.send_window_size_message();
1140 true
1141 }
1142
1143 pub(crate) fn set_screen_size(&mut self, new_size: Size2D<f32, DevicePixel>) -> bool {
1145 if self.screen_size == new_size {
1146 return false;
1147 }
1148 self.screen_size = new_size;
1149
1150 self.send_window_size_message();
1151 true
1152 }
1153
1154 pub(crate) fn set_rect(&mut self, new_rect: DeviceRect) -> bool {
1156 let old_rect = std::mem::replace(&mut self.rect, new_rect);
1157 if old_rect.size() != self.rect.size() {
1158 self.send_window_size_message();
1159 self.pinch_zoom.resize_unscaled_viewport(new_rect);
1160 self.send_pinch_zoom_infos_to_script();
1161 }
1162 old_rect != self.rect
1163 }
1164
1165 pub fn set_viewport_description(&mut self, viewport_description: ViewportDescription) {
1166 self.viewport_description = viewport_description;
1167 self.send_window_size_message();
1168 self.adjust_pinch_zoom(
1169 self.viewport_description.initial_scale.get(),
1170 DevicePoint::origin(),
1171 );
1172 }
1173
1174 pub(crate) fn scroll_trees_memory_usage(
1175 &self,
1176 ops: &mut malloc_size_of::MallocSizeOfOps,
1177 ) -> usize {
1178 self.pipelines
1179 .values()
1180 .map(|pipeline| pipeline.scroll_tree.size_of(ops))
1181 .sum::<usize>()
1182 }
1183
1184 pub(crate) fn notify_input_event_handled(
1185 &mut self,
1186 repaint_reason: &Cell<RepaintReason>,
1187 id: InputEventId,
1188 result: InputEventResult,
1189 ) {
1190 if let Some(pending_touch_input_event) =
1191 self.touch_handler.take_pending_touch_input_event(id)
1192 {
1193 self.on_touch_event_processed(pending_touch_input_event, result);
1194 self.touch_handler
1195 .add_touch_move_refresh_observer_if_necessary(
1196 self.refresh_driver.clone(),
1197 repaint_reason,
1198 );
1199 }
1200
1201 if let Some(wheel_event) = self.pending_wheel_events.remove(&id) &&
1202 !result.contains(InputEventResult::DefaultPrevented)
1203 {
1204 let scroll_delta =
1206 DeviceVector2D::new(-wheel_event.delta.x as f32, -wheel_event.delta.y as f32);
1207 self.notify_scroll_event(Scroll::Delta(scroll_delta.into()), wheel_event.point);
1208 }
1209 }
1210
1211 pub(crate) fn root_transform(&self) -> LayoutTransform {
1214 let pinch_zoom_transform = self.pinch_zoom.transform().to_untyped();
1215 let device_pixels_per_page_pixel_not_including_pinch_zoom = self
1216 .device_pixels_per_page_pixel_not_including_pinch_zoom()
1217 .get();
1218
1219 LayoutTransform::scale(
1220 device_pixels_per_page_pixel_not_including_pinch_zoom,
1221 device_pixels_per_page_pixel_not_including_pinch_zoom,
1222 1.0,
1223 )
1224 .then(&LayoutTransform::from_untyped(
1225 &pinch_zoom_transform.to_3d(),
1226 ))
1227 }
1228}
1229
1230#[derive(Clone, Copy, Debug, PartialEq)]
1231pub struct UnknownWebView(pub WebViewId);