diff --git a/patches/components/constellation/constellation.rs.patch b/patches/components/constellation/constellation.rs.patch index 53f1fdb..2a546b7 100644 --- a/patches/components/constellation/constellation.rs.patch +++ b/patches/components/constellation/constellation.rs.patch @@ -369,7 +369,23 @@ Request::RemoveProcess(index) => self.process_manager.remove(index), } } -@@ -1522,11 +1705,7 @@ +@@ -1450,6 +1633,15 @@ + EmbedderToConstellationMessage::ThemeChange(webview_id, theme) => { + self.handle_theme_change(webview_id, theme); + }, ++ EmbedderToConstellationMessage::WebViewScrollStateChanged { webview_id, state } => { ++ // Route a compositor-detected scroll gesture start / end to an embedded ++ // webview's parent document as a DOM event. Non-embedded (top-level) ++ // webviews are ignored (handle_embedded_webview_notification no-ops). ++ self.handle_embedded_webview_notification( ++ webview_id, ++ EmbeddedWebViewEventType::ScrollStateChanged(state), ++ ); ++ }, + EmbedderToConstellationMessage::TickAnimation(webview_ids) => { + self.handle_tick_animation(webview_ids) + }, +@@ -1522,11 +1714,7 @@ } }, EmbedderToConstellationMessage::PreferencesUpdated(updates) => { @@ -382,7 +398,7 @@ let _ = event_loop.send(ScriptThreadMessage::PreferencesUpdated( updates .iter() -@@ -1553,6 +1732,18 @@ +@@ -1553,6 +1741,18 @@ EmbedderToConstellationMessage::SetAccessibilityActive(webview_id, active) => { self.set_accessibility_active(webview_id, active); }, @@ -401,7 +417,7 @@ } } -@@ -1750,7 +1941,13 @@ +@@ -1750,7 +1950,13 @@ return warn!("Attempt to add channel name from an unexpected origin."); } self.broadcast_channels @@ -416,7 +432,7 @@ }, ScriptToConstellationMessage::RemoveBroadcastChannelNameInRouter( router_id, -@@ -1764,7 +1961,13 @@ +@@ -1764,7 +1970,13 @@ return warn!("Attempt to remove channel name from an unexpected origin."); } self.broadcast_channels @@ -431,7 +447,7 @@ }, ScriptToConstellationMessage::RemoveBroadcastChannelRouter(router_id, origin) => { if self -@@ -1776,6 +1979,12 @@ +@@ -1776,6 +1988,12 @@ self.broadcast_channels .remove_broadcast_channel_router(router_id); }, @@ -444,7 +460,7 @@ ScriptToConstellationMessage::ScheduleBroadcast(router_id, message) => { if self .check_origin_against_pipeline(&source_pipeline_id, &message.origin) -@@ -1785,8 +1994,15 @@ +@@ -1785,8 +2003,15 @@ "Attempt to schedule broadcast from an origin not matching the origin of the msg." ); } @@ -461,7 +477,7 @@ }, ScriptToConstellationMessage::PipelineExited => { self.handle_pipeline_exited(source_pipeline_id); -@@ -1806,6 +2022,12 @@ +@@ -1806,6 +2031,12 @@ ScriptToConstellationMessage::CreateAuxiliaryWebView(load_info) => { self.handle_script_new_auxiliary(load_info); }, @@ -474,7 +490,7 @@ ScriptToConstellationMessage::ChangeRunningAnimationsState(animation_state) => { self.handle_change_running_animations_state(source_pipeline_id, animation_state) }, -@@ -1857,7 +2079,7 @@ +@@ -1857,7 +2088,7 @@ ScriptToConstellationMessage::SetFinalUrl(final_url) => { // The script may have finished loading after we already started shutting down. if let Some(ref mut pipeline) = self.pipelines.get_mut(&source_pipeline_id) { @@ -483,7 +499,7 @@ } else { warn!("constellation got set final url message for dead pipeline"); } -@@ -2007,6 +2229,29 @@ +@@ -2007,6 +2238,29 @@ new_value, ); }, @@ -513,22 +529,21 @@ ScriptToConstellationMessage::MediaSessionEvent(pipeline_id, event) => { // Unlikely at this point, but we may receive events coming from // different media sessions, so we set the active media session based -@@ -2025,8 +2270,13 @@ +@@ -2025,7 +2279,12 @@ }; self.active_media_session = Some(pipeline_id); self.constellation_to_embedder_proxy.send( - ConstellationToEmbedderMsg::MediaSessionEvent(webview_id, event), + ConstellationToEmbedderMsg::MediaSessionEvent(webview_id, event.clone()), - ); ++ ); + // Also route to embedded webview parent iframe. + self.handle_embedded_webview_notification( + webview_id, + EmbeddedWebViewEventType::MediaSessionEvent(event), -+ ); + ); }, #[cfg(feature = "webgpu")] - ScriptToConstellationMessage::RequestAdapter(response_sender, options, ids) => self -@@ -2099,9 +2349,1018 @@ +@@ -2099,6 +2358,1015 @@ } }, }, @@ -1221,9 +1236,9 @@ + let _ = callback.send(None); + } + }, - } - } - ++ } ++ } ++ + fn handle_pairing_event(&mut self, event: PairingEvent) { + if let PairingEvent::MessageReceived { ref from, ref data } = event { + debug!("P2P message received from {from}, {} bytes", data.len()); @@ -1541,13 +1556,10 @@ + if self.embedder_error_listeners.contains(&event_loop.id()) { + let _ = event_loop.send(ScriptThreadMessage::DispatchPairingEvent(event.clone())); + } -+ } -+ } -+ - /// Check the origin of a message against that of the pipeline it came from. - /// Note: this is still limited as a security check, - /// see -@@ -2418,6 +3677,55 @@ + } + } + +@@ -2418,6 +3686,55 @@ TransferState::TransferInProgress(queue) => queue.push_back(task), TransferState::CompletionFailed(queue) => queue.push_back(task), TransferState::CompletionRequested(_, queue) => queue.push_back(task), @@ -1603,7 +1615,7 @@ } } -@@ -3204,6 +4512,101 @@ +@@ -3204,6 +4521,101 @@ ); } @@ -1705,7 +1717,7 @@ fn forward_input_event( &mut self, webview_id: WebViewId, -@@ -3223,6 +4626,66 @@ +@@ -3223,6 +4635,66 @@ let pressed_mouse_buttons = self.pressed_mouse_buttons; let active_keyboard_modifiers = self.active_keyboard_modifiers; @@ -1772,7 +1784,7 @@ let event_id = event.id; let Some(webview) = self.webviews.get_mut(&webview_id) else { warn!("Got input event for unknown WebViewId: {webview_id:?}"); -@@ -3325,6 +4788,40 @@ +@@ -3325,6 +4797,40 @@ /// fn handle_close_top_level_browsing_context(&mut self, webview_id: WebViewId) { debug!("{webview_id}: Closing"); @@ -1813,7 +1825,7 @@ let browsing_context_id = BrowsingContextId::from(webview_id); // Step 5. Remove traversable from the user agent's top-level traversable set. let browsing_context = -@@ -3601,8 +5098,27 @@ +@@ -3601,8 +5107,27 @@ opener_webview_id, opener_pipeline_id, response_sender, @@ -1841,7 +1853,7 @@ let Some((webview_id_sender, webview_id_receiver)) = generic_channel::channel() else { warn!("Failed to create channel"); let _ = response_sender.send(None); -@@ -3701,6 +5217,402 @@ +@@ -3701,6 +5226,402 @@ }); } @@ -2244,7 +2256,7 @@ #[servo_tracing::instrument(skip_all)] fn handle_refresh_cursor(&self, pipeline_id: PipelineId) { let Some(pipeline) = self.pipelines.get(&pipeline_id) else { -@@ -4250,7 +6162,7 @@ +@@ -4250,7 +6171,7 @@ }, }; @@ -2253,7 +2265,7 @@ match self.browsing_contexts.get_mut(&browsing_context_id) { Some(browsing_context) => { let old_pipeline_id = browsing_context.pipeline_id; -@@ -4259,6 +6171,7 @@ +@@ -4259,6 +6180,7 @@ old_pipeline_id, browsing_context.parent_pipeline_id, browsing_context.webview_id, @@ -2261,7 +2273,7 @@ ) }, None => { -@@ -4268,6 +6181,15 @@ +@@ -4268,6 +6190,15 @@ self.unload_document(old_pipeline_id); @@ -2277,7 +2289,7 @@ if let Some(new_pipeline) = self.pipelines.get(&new_pipeline_id) { if let Some(ref chan) = self.devtools_sender { let state = NavigationState::Start(new_pipeline.url.clone()); -@@ -4836,7 +6758,7 @@ +@@ -4836,7 +6767,7 @@ } #[servo_tracing::instrument(skip_all)] @@ -2286,7 +2298,7 @@ // Send a flat projection of the history to embedder. // The final vector is a concatenation of the URLs of the past // entries, the current entry and the future entries. -@@ -4948,9 +6870,22 @@ +@@ -4948,9 +6879,22 @@ self.constellation_to_embedder_proxy .send(ConstellationToEmbedderMsg::HistoryChanged( webview_id, @@ -2310,7 +2322,7 @@ } #[servo_tracing::instrument(skip_all)] -@@ -4969,7 +6904,7 @@ +@@ -4969,7 +6913,7 @@ webview.focused_browsing_context_id = change.browsing_context_id; } @@ -2319,7 +2331,7 @@ match self.browsing_contexts.get_mut(&change.browsing_context_id) { Some(browsing_context) => { debug!("Adding pipeline to existing browsing context."); -@@ -4976,11 +6911,15 @@ +@@ -4976,11 +6920,15 @@ let old_pipeline_id = browsing_context.pipeline_id; browsing_context.pipelines.insert(change.new_pipeline_id); browsing_context.update_current_entry(change.new_pipeline_id); @@ -2337,7 +2349,7 @@ }, }; -@@ -4988,6 +6927,18 @@ +@@ -4988,6 +6936,18 @@ self.unload_document(old_pipeline_id); } diff --git a/patches/components/constellation/tracing.rs.patch b/patches/components/constellation/tracing.rs.patch index f0ff595..9c9c9b7 100644 --- a/patches/components/constellation/tracing.rs.patch +++ b/patches/components/constellation/tracing.rs.patch @@ -1,6 +1,14 @@ --- original +++ modified -@@ -83,6 +83,7 @@ +@@ -55,6 +55,7 @@ + Self::TraverseHistory(..) => target!("TraverseHistory"), + Self::ChangeViewportDetails(..) => target!("ChangeViewportDetails"), + Self::ThemeChange(..) => target!("ThemeChange"), ++ Self::WebViewScrollStateChanged { .. } => target!("WebViewScrollStateChanged"), + Self::TickAnimation(..) => target!("TickAnimation"), + Self::WebDriverCommand(..) => target!("WebDriverCommand"), + Self::Reload(..) => target!("Reload"), +@@ -83,6 +84,7 @@ Self::UserContentManagerAction(..) => target!("UserContentManagerAction"), Self::UpdatePinchZoomInfos(..) => target!("UpdatePinchZoomInfos"), Self::SetAccessibilityActive(..) => target!("SetAccessibilityActive"), @@ -8,7 +16,7 @@ } } } -@@ -134,6 +135,10 @@ +@@ -134,6 +136,10 @@ Self::DisentanglePorts(..) => target!("DisentanglePorts"), Self::NewBroadcastChannelRouter(..) => target!("NewBroadcastChannelRouter"), Self::RemoveBroadcastChannelRouter(..) => target!("RemoveBroadcastChannelRouter"), @@ -19,7 +27,7 @@ Self::NewBroadcastChannelNameInRouter(..) => { target!("NewBroadcastChannelNameInRouter") }, -@@ -144,6 +149,7 @@ +@@ -144,6 +150,7 @@ Self::RegisterInterest(..) => target!("RegisterInterest"), Self::UnregisterInterest(..) => target!("UnregisterInterest"), Self::BroadcastStorageEvent(..) => target!("BroadcastStorageEvent"), @@ -27,7 +35,7 @@ Self::ChangeRunningAnimationsState(..) => target!("ChangeRunningAnimationsState"), Self::CreateCanvasPaintThread(..) => target!("CreateCanvasPaintThread"), Self::FocusAncestorBrowsingContextsForFocusingSteps(..) => { -@@ -168,6 +174,8 @@ +@@ -168,6 +175,8 @@ Self::ScriptLoadedURLInIFrame(..) => target!("ScriptLoadedURLInIFrame"), Self::ScriptNewIFrame(..) => target!("ScriptNewIFrame"), Self::CreateAuxiliaryWebView(..) => target!("ScriptNewAuxiliary"), @@ -36,7 +44,7 @@ Self::ActivateDocument => target!("ActivateDocument"), Self::SetDocumentState(..) => target!("SetDocumentState"), Self::SetFinalUrl(..) => target!("SetFinalUrl"), -@@ -192,6 +200,77 @@ +@@ -192,6 +201,77 @@ Self::TriggerGarbageCollection => target!("TriggerGarbageCollection"), Self::AcquireWakeLock(..) => target!("AcquireWakeLock"), Self::ReleaseWakeLock(..) => target!("ReleaseWakeLock"), diff --git a/patches/components/paint/touch.rs.patch b/patches/components/paint/touch.rs.patch new file mode 100644 index 0000000..84be0b3 --- /dev/null +++ b/patches/components/paint/touch.rs.patch @@ -0,0 +1,20 @@ +--- original ++++ modified +@@ -327,6 +327,17 @@ + self.touch_sequence_map.get(&self.current_sequence_id) + } + ++ /// Whether the current touch sequence is actively scrolling the page: a finger pan, or ++ /// the momentum fling that continues after touch-up. A tap, pinch, or finished sequence ++ /// is not scrolling. Used to notify the embedder of scroll start / end. ++ pub(crate) fn is_scrolling(&self) -> bool { ++ matches!( ++ self.try_get_current_touch_sequence() ++ .map(|sequence| sequence.state), ++ Some(Panning { .. } | PendingFling { .. } | Flinging { .. }) ++ ) ++ } ++ + fn try_get_current_touch_sequence_mut(&mut self) -> Option<&mut TouchSequenceInfo> { + self.touch_sequence_map.get_mut(&self.current_sequence_id) + } diff --git a/patches/components/paint/webview_renderer.rs.patch b/patches/components/paint/webview_renderer.rs.patch index fc12e5b..9c52bcb 100644 --- a/patches/components/paint/webview_renderer.rs.patch +++ b/patches/components/paint/webview_renderer.rs.patch @@ -1,5 +1,14 @@ --- original +++ modified +@@ -23,7 +23,7 @@ + use rustc_hash::FxHashMap; + use servo_base::id::{PipelineId, WebViewId}; + use servo_constellation_traits::{ +- EmbedderToConstellationMessage, ScrollStateUpdate, WindowSizeType, ++ EmbedderToConstellationMessage, ScrollState, ScrollStateUpdate, WindowSizeType, + }; + use servo_geometry::DeviceIndependentPixel; + use style_traits::CSSPixel; @@ -40,7 +40,7 @@ PendingTouchInputEvent, TouchHandler, TouchIdMoveTracking, TouchMoveAllowed, TouchSequenceState, }; @@ -39,7 +48,17 @@ /// A renderer for a libservo `WebView`. This is essentially the [`ServoRenderer`]'s interface to a /// libservo `WebView`, but the code here cannot depend on libservo in order to prevent circular /// dependencies, which is why we store a `dyn WebViewTrait` here instead of the `WebView` itself. -@@ -115,9 +122,11 @@ +@@ -99,6 +106,9 @@ + pending_wheel_events: FxHashMap, + /// Touch input state machine + touch_handler: TouchHandler, ++ /// Whether the touch handler was scrolling (pan or fling) at the last update, so we can ++ /// notify the constellation only on the transition (scroll start / end). ++ was_scrolling: bool, + /// "Desktop-style" zoom that resizes the viewport to fit the window. + pub page_zoom: Scale, + /// "Mobile-style" zoom that does not reflow the page. When there is no [`PinchZoom`] a +@@ -115,9 +125,11 @@ /// A [`ViewportDescription`] for this [`WebViewRenderer`], which contains the limitations /// and initial values for zoom derived from the `viewport` meta tag in web content. viewport_description: ViewportDescription, @@ -52,7 +71,15 @@ // // Data that is shared with the parent renderer. -@@ -158,6 +167,7 @@ +@@ -149,6 +161,7 @@ + rect, + pipelines: Default::default(), + touch_handler: TouchHandler::new(webview_id), ++ was_scrolling: false, + pending_scroll_zoom_events: Default::default(), + pending_wheel_events: Default::default(), + page_zoom: DEFAULT_PAGE_ZOOM, +@@ -158,6 +171,7 @@ animating: false, viewport_description: Default::default(), screen_size: viewport_details.device_size, @@ -60,7 +87,7 @@ embedder_to_constellation_sender, refresh_driver, webrender_document, -@@ -193,6 +203,16 @@ +@@ -193,6 +207,16 @@ new_value != old_value } @@ -77,7 +104,50 @@ /// Returns the [`PipelineDetails`] for the given [`PipelineId`], creating it if needed. pub(crate) fn ensure_pipeline_details( &mut self, -@@ -388,16 +408,14 @@ +@@ -366,7 +390,10 @@ + /// frame tick. Returns `true` if we should continue observing frames (the fling is ongoing) + /// or `false` if we should stop observing frames (the fling has finished). + pub(crate) fn update_touch_handling_at_new_frame_start(&mut self) -> bool { +- let Some(fling_action) = self.touch_handler.notify_new_frame_start() else { ++ let fling_action = self.touch_handler.notify_new_frame_start(); ++ // A fling that just settled (Flinging -> Finished) is a scroll-end transition. ++ self.notify_scroll_state_if_changed(); ++ let Some(fling_action) = fling_action else { + return false; + }; + +@@ -377,6 +404,30 @@ + true + } + ++ /// Notify the constellation when this webview's touch scroll gesture starts or ends, so ++ /// an embedded page's parent document can react (e.g. hide chrome). Called after each ++ /// touch event and each fling frame; sends only on an actual start / end transition. ++ fn notify_scroll_state_if_changed(&mut self) { ++ let scrolling = self.touch_handler.is_scrolling(); ++ if scrolling == self.was_scrolling { ++ return; ++ } ++ self.was_scrolling = scrolling; ++ let state = if scrolling { ++ ScrollState::Start ++ } else { ++ ScrollState::End ++ }; ++ if let Err(error) = self.embedder_to_constellation_sender.send( ++ EmbedderToConstellationMessage::WebViewScrollStateChanged { ++ webview_id: self.id, ++ state, ++ }, ++ ) { ++ warn!("Sending scroll state change to constellation failed ({error:?})."); ++ } ++ } ++ + fn dispatch_input_event_with_hit_testing( + &mut self, + render_api: &RenderApi, +@@ -388,16 +439,14 @@ .map(|point| point.as_device_point(self.device_pixels_per_page_pixel())); let hit_test_result = match event_point { Some(point) => { @@ -99,7 +169,15 @@ }, None => None, }; -@@ -722,7 +740,7 @@ +@@ -476,6 +525,7 @@ + self.refresh_driver.clone(), + repaint_reason, + ); ++ self.notify_scroll_state_if_changed(); + result + } + +@@ -722,7 +772,7 @@ ); self.dispatch_input_event_with_hit_testing( render_api, @@ -108,7 +186,7 @@ MouseButtonAction::Down, button, point.into(), -@@ -731,7 +749,7 @@ +@@ -731,7 +781,7 @@ ); self.dispatch_input_event_with_hit_testing( render_api, @@ -117,7 +195,7 @@ MouseButtonAction::Up, button, point.into(), -@@ -753,18 +771,23 @@ +@@ -753,18 +803,23 @@ })); } @@ -147,7 +225,7 @@ } // Batch up all scroll events and changes to pinch zoom into a single change, or -@@ -813,101 +836,136 @@ +@@ -813,101 +868,136 @@ } } @@ -195,6 +273,11 @@ + pinch_zoom_result, + pending_scroll: combined_scroll_event, + } ++ } ++ ++ /// Whether this renderer has a scroll tree for `pipeline_id`. ++ pub(crate) fn owns_pipeline(&self, pipeline_id: PipelineId) -> bool { ++ self.pipelines.contains_key(&pipeline_id) } - /// Perform a hit test at the given [`DevicePoint`] and apply the [`Scroll`] @@ -208,11 +291,6 @@ - scroll: Scroll, - ) -> Option { - let scroll_location = match scroll { -+ /// Whether this renderer has a scroll tree for `pipeline_id`. -+ pub(crate) fn owns_pipeline(&self, pipeline_id: PipelineId) -> bool { -+ self.pipelines.contains_key(&pipeline_id) -+ } -+ + /// Convert a [`Scroll`] into a [`ScrollLocation`] using this renderer's own scale. + fn scroll_location_for(&self, scroll: Scroll) -> ScrollLocation { + match scroll { @@ -258,12 +336,7 @@ + offset, + }) + } - -- let hit_test_results: Vec<_> = self -- .touch_handler -- .get_hit_test_result_cache_value() -- .map(|result| vec![result]) -- .unwrap_or_else(|| self.hit_test(render_api, cursor)); ++ + /// Scroll this renderer's root pipeline's root scroll node (the document viewport). Used + /// as a fallback when a scroll's cursor does not resolve to an owned scroll node, which + /// happens for an embedded webview: it receives wheel events in its own local @@ -288,7 +361,12 @@ + offset, + }) + } -+ + +- let hit_test_results: Vec<_> = self +- .touch_handler +- .get_hit_test_result_cache_value() +- .map(|result| vec![result]) +- .unwrap_or_else(|| self.hit_test(render_api, cursor)); + /// Scroll any scrollable node in this renderer's root pipeline (depth-first). Used to + /// chain an embedded webview's unconsumed scroll to its parent, whose scrollable + /// container (e.g. a horizontal panel strip) is not necessarily the root scroll node and @@ -306,6 +384,18 @@ + offset, + }) + } ++ ++ /// The hit-test result pinned while touch-panning, so a fling keeps scrolling the ++ /// same node across a touch sequence. ++ pub(crate) fn cached_scroll_hit_test(&self) -> Option { ++ self.touch_handler.get_hit_test_result_cache_value() ++ } ++ ++ /// Pin the scrolled node's hit-test result for the current touch/pan sequence. ++ pub(crate) fn cache_scroll_hit_test(&mut self, hit_test_result: PaintHitTestResult) { ++ self.touch_handler ++ .set_hit_test_result_cache_value(hit_test_result, self.device_pixels_per_page_pixel()); ++ } - // Iterate through all hit test results, processing only the first node of each pipeline. - // This is needed to propagate the scroll events from a pipeline representing an iframe to @@ -340,25 +430,13 @@ - } - } - None -+ /// The hit-test result pinned while touch-panning, so a fling keeps scrolling the -+ /// same node across a touch sequence. -+ pub(crate) fn cached_scroll_hit_test(&self) -> Option { -+ self.touch_handler.get_hit_test_result_cache_value() -+ } -+ -+ /// Pin the scrolled node's hit-test result for the current touch/pan sequence. -+ pub(crate) fn cache_scroll_hit_test(&mut self, hit_test_result: PaintHitTestResult) { -+ self.touch_handler -+ .set_hit_test_result_cache_value(hit_test_result, self.device_pixels_per_page_pixel()); -+ } -+ + /// Stop any in-progress fling (used when a scroll finds nothing to scroll). + pub(crate) fn stop_fling(&mut self) { + self.touch_handler.stop_fling_if_needed(); } /// Scroll the viewport (root pipeline, root scroll node) of this WebView, but first -@@ -949,14 +1007,6 @@ +@@ -949,14 +1039,6 @@ return (pinch_zoom_result, vec![]); }; @@ -373,7 +451,7 @@ self.send_scroll_positions_to_layout_for_pipeline(root_pipeline_id, external_scroll_id); if pinch_zoom_result == PinchZoomResult::DidPinchZoom { -@@ -964,7 +1014,6 @@ +@@ -964,7 +1046,6 @@ } let scroll_result = ScrollResult { @@ -381,7 +459,7 @@ external_scroll_id, offset, }; -@@ -1035,6 +1084,12 @@ +@@ -1035,6 +1116,12 @@ self.page_zoom * self.hidpi_scale_factor } @@ -394,7 +472,7 @@ /// Adjust the pinch zoom of the [`WebView`] by the given zoom delta. pub(crate) fn adjust_pinch_zoom(&mut self, magnification: f32, center: DevicePoint) { if magnification == 1.0 { -@@ -1046,8 +1101,17 @@ +@@ -1046,8 +1133,17 @@ } fn send_window_size_message(&self) { @@ -414,7 +492,7 @@ let device_pixel_ratio = self.device_pixels_per_page_pixel_not_including_pinch_zoom(); // From : // This is the viewport you get after processing the viewport tag. -@@ -1060,6 +1124,7 @@ +@@ -1060,6 +1156,7 @@ hidpi_scale_factor: device_pixel_ratio, size: layout_viewport, device_size: self.screen_size, @@ -422,7 +500,7 @@ }, WindowSizeType::Resize, ), -@@ -1143,8 +1208,21 @@ +@@ -1143,8 +1240,21 @@ !result.contains(InputEventResult::DefaultPrevented) { // A scroll delta for a wheel event is the inverse of the wheel delta. diff --git a/patches/components/script/dom/html/htmlwebviewelement.rs.patch b/patches/components/script/dom/html/htmlwebviewelement.rs.patch index fea7894..81e0779 100644 --- a/patches/components/script/dom/html/htmlwebviewelement.rs.patch +++ b/patches/components/script/dom/html/htmlwebviewelement.rs.patch @@ -1,6 +1,6 @@ --- original +++ modified -@@ -0,0 +1,1736 @@ +@@ -0,0 +1,1745 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ @@ -42,7 +42,7 @@ +use servo_constellation_traits::{ + BlobImpl, EmbeddedWebViewCreationRequest, EmbeddedWebViewEventType, IFrameLoadInfo, + IFrameLoadInfoWithData, LoadData, LoadOrigin, NavigationHistoryBehavior, -+ ScriptToConstellationMessage, TargetSnapshotParams, TraversalDirection, ++ ScriptToConstellationMessage, ScrollState, TargetSnapshotParams, TraversalDirection, +}; +use servo_url::ServoUrl; +use style::attr::AttrValue; @@ -502,6 +502,12 @@ + EmbeddedWebViewEventType::SimpleDialogShow(_) => Atom::from("embeddialogshow"), + EmbeddedWebViewEventType::NotificationShow(_) => Atom::from("embednotificationshow"), + EmbeddedWebViewEventType::MediaSessionEvent(_) => Atom::from("embedmediasessionevent"), ++ EmbeddedWebViewEventType::ScrollStateChanged(ScrollState::Start) => { ++ Atom::from("embedscrollstart") ++ }, ++ EmbeddedWebViewEventType::ScrollStateChanged(ScrollState::End) => { ++ Atom::from("embedscrollend") ++ }, + }; + + rooted!(&in(cx) let mut detail = UndefinedValue()); @@ -558,6 +564,9 @@ + EmbeddedWebViewEventType::InputReceived => { + // detail stays as undefined for input received event + }, ++ EmbeddedWebViewEventType::ScrollStateChanged(_) => { ++ // detail stays undefined; start vs end is conveyed by the event name. ++ }, + EmbeddedWebViewEventType::EmbedderControlShow { id, rect, request } => { + // Create a dictionary-based detail object with control information + let control_type = match &request { diff --git a/patches/components/shared/constellation/lib.rs.patch b/patches/components/shared/constellation/lib.rs.patch index aa5b3e8..daf936d 100644 --- a/patches/components/shared/constellation/lib.rs.patch +++ b/patches/components/shared/constellation/lib.rs.patch @@ -19,7 +19,7 @@ }; pub use from_script_message::*; use malloc_size_of_derive::MallocSizeOf; -@@ -30,15 +32,189 @@ +@@ -30,15 +32,202 @@ use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; use servo_base::cross_process_instant::CrossProcessInstant; @@ -35,6 +35,17 @@ use webrender_api::{ExternalScrollId, ImageKey}; +/// Event types that are dispatched from embedded webviews to their parent iframe elements. ++/// The phase of a touch scroll gesture, reported so a parent document / embedder can react ++/// (e.g. hide chrome while the page is scrolling). Distinct from `Scroll::Start`/`End`, which ++/// are page-position jumps (scroll-to-top / -bottom), not gesture begin / end. ++#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] ++pub enum ScrollState { ++ /// A scroll gesture began: a finger pan, or the momentum fling after touch-up. ++ Start, ++ /// The scroll gesture and any momentum finished. ++ End, ++} ++ +/// These are used to notify the parent document about changes in the embedded webview's state. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum EmbeddedWebViewEventType { @@ -63,6 +74,8 @@ + ThemeColorChanged(String), + /// The embedded webview received input (mouse/touch). + InputReceived, ++ /// The embedded webview's touch scroll gesture started or ended (momentum included). ++ ScrollStateChanged(ScrollState), + /// An embedder control (select, color picker, etc.) should be shown. + EmbedderControlShow { + /// Unique ID for response routing. @@ -211,7 +224,23 @@ /// Messages to the Constellation from the embedding layer, whether from `ServoRenderer` or /// from `libservo` itself. #[derive(IntoStaticStr)] -@@ -116,6 +292,9 @@ +@@ -55,6 +244,15 @@ + ChangeViewportDetails(WebViewId, ViewportDetails, WindowSizeType), + /// Inform the constellation of a theme change. + ThemeChange(WebViewId, Theme), ++ /// A webview's touch scroll gesture started or ended, detected by the compositor's ++ /// touch handler. For an embedded webview this is routed to its parent document as a ++ /// DOM event (embedscrollstart / embedscrollend). ++ WebViewScrollStateChanged { ++ /// The webview whose scroll gesture changed phase. ++ webview_id: WebViewId, ++ /// Whether the scroll started or ended. ++ state: ScrollState, ++ }, + /// Requests that the constellation instruct script/layout to try to layout again and tick + /// animations. + TickAnimation(Vec), +@@ -116,6 +314,9 @@ UpdatePinchZoomInfos(PipelineId, PinchZoomInfos), /// Activate or deactivate accessibility features for the given `WebView`. SetAccessibilityActive(WebViewId, bool), diff --git a/ui/system/mobile/pebble.css b/ui/system/mobile/pebble.css index f0613f6..e0e392e 100644 --- a/ui/system/mobile/pebble.css +++ b/ui/system/mobile/pebble.css @@ -31,6 +31,20 @@ display: block; font-family: var(--font-family-base); color: var(--color-text); + /* Only used by the scroll-hidden slide-away below; the pebble's own transforms + stay on .pebble. */ + transition: + transform 0.24s var(--pebble-easing), + opacity 0.24s var(--pebble-easing); +} + +/* While the active page is being scrolled (touch pan + momentum), slide the + ambient pebble down out of the way so it doesn't cover content. Scoped to the + ambient (resting) state so an open glance / command menu is never hidden. + Toggled by pebble.js from the webview-scroll-start / -end events. */ +:host([data-state="ambient"][data-scroll-hidden]) { + transform: translateY(96px); + opacity: 0; } /* Fullscreen gesture catcher: transparent overlay that captures pointer diff --git a/ui/system/mobile/pebble.js b/ui/system/mobile/pebble.js index fa2eefb..2662088 100644 --- a/ui/system/mobile/pebble.js +++ b/ui/system/mobile/pebble.js @@ -137,6 +137,12 @@ export class MobilePebble extends LitElement { this._boundDocPointerDown = this._onDocumentPointerDown.bind(this); this._boundDocKeyDown = this._onDocumentKeyDown.bind(this); + // Hide the ambient pebble while the active page is being scrolled (touch pan + + // momentum), driven by webview-scroll-start / -end bubbling up from . + this._boundViewScrollStart = () => + this.toggleAttribute("data-scroll-hidden", true); + this._boundViewScrollEnd = () => + this.toggleAttribute("data-scroll-hidden", false); this._initSearchController(); } @@ -145,6 +151,11 @@ export class MobilePebble extends LitElement { super.connectedCallback(); document.addEventListener("pointerdown", this._boundDocPointerDown, true); document.addEventListener("keydown", this._boundDocKeyDown); + document.addEventListener( + "webview-scroll-start", + this._boundViewScrollStart, + ); + document.addEventListener("webview-scroll-end", this._boundViewScrollEnd); } disconnectedCallback() { @@ -155,6 +166,14 @@ export class MobilePebble extends LitElement { true, ); document.removeEventListener("keydown", this._boundDocKeyDown); + document.removeEventListener( + "webview-scroll-start", + this._boundViewScrollStart, + ); + document.removeEventListener( + "webview-scroll-end", + this._boundViewScrollEnd, + ); } updated(changedProperties) { diff --git a/ui/system/web_view.js b/ui/system/web_view.js index ed24d6d..6b5c04b 100644 --- a/ui/system/web_view.js +++ b/ui/system/web_view.js @@ -427,6 +427,30 @@ export class WebView extends LitElement { ); } + // The compositor detected the page's touch scroll starting / ending (momentum + // included). Surface it to the shell so mobile chrome (the pebble) can get out + // of the way while scrolling. Fired as embedscrollstart / embedscrollend by the + // element (constellation ScrollStateChanged). + onscrollstart() { + this.dispatchEvent( + new CustomEvent("webview-scroll-start", { + bubbles: true, + composed: true, + detail: { webviewId: this.webviewId }, + }), + ); + } + + onscrollend() { + this.dispatchEvent( + new CustomEvent("webview-scroll-end", { + bubbles: true, + composed: true, + detail: { webviewId: this.webviewId }, + }), + ); + } + handleDialogConfirm(inputValue = null) { this.ensureIframe(); const dialog = this.currentDialog; @@ -1340,6 +1364,8 @@ export class WebView extends LitElement { @embednotificationshow=${this.onnotificationshow} @embedloadstatuschange=${this.onloadstatuschange} @embedmediasessionevent=${this.onmediasessionevent} + @embedscrollstart=${this.onscrollstart} + @embedscrollend=${this.onscrollend} @embedclosed=${this.close} > ${this.renderDialog()} ${this.renderPermissionPrompt()}