Rewild Your Web
at main 283 lines 12 kB view raw
1--- original 2+++ modified 3@@ -3,6 +3,7 @@ 4 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 5 6 use std::cell::Cell; 7+use std::collections::BTreeMap; 8 use std::collections::hash_map::Entry; 9 use std::rc::Rc; 10 use std::sync::Arc; 11@@ -80,7 +81,14 @@ 12 pub(crate) painter_id: PainterId, 13 14 /// Our [`WebViewRenderer`]s, one for every `WebView`. 15- pub(crate) webview_renderers: FxHashMap<WebViewId, WebViewRenderer>, 16+ /// Using BTreeMap to ensure deterministic iteration order by WebViewId, 17+ /// which is important for proper z-ordering in the display list (parents before children). 18+ pub(crate) webview_renderers: BTreeMap<WebViewId, WebViewRenderer>, 19+ 20+ /// Set of WebViewIds that are embedded webviews. These should not be rendered 21+ /// as top-level iframes in the root display list, as they are already referenced 22+ /// by their parent's display list through IFrameFragment. 23+ pub(crate) embedded_webview_ids: FxHashSet<WebViewId>, 24 25 /// Tracks whether or not the view needs to be repainted. 26 pub(crate) needs_repaint: Cell<RepaintReason>, 27@@ -258,6 +266,7 @@ 28 painter_id, 29 embedder_to_constellation_sender, 30 webview_renderers: Default::default(), 31+ embedded_webview_ids: Default::default(), 32 rendering_context, 33 needs_repaint: Cell::default(), 34 pending_frames: Default::default(), 35@@ -278,7 +287,12 @@ 36 painter 37 } 38 39- pub(crate) fn perform_updates(&mut self) { 40+ /// Process pending scroll and zoom events for all webview renderers. 41+ /// Returns a list of (webview_id, unconsumed_scroll) tuples for scroll events 42+ /// that were not consumed by embedded webviews and should be forwarded to parents. 43+ pub(crate) fn perform_updates( 44+ &mut self, 45+ ) -> Vec<(WebViewId, crate::webview_renderer::ScrollEvent)> { 46 // The WebXR thread may make a different context current 47 if let Err(err) = self.rendering_context.make_current() { 48 warn!("Failed to make the rendering context current: {:?}", err); 49@@ -285,18 +299,23 @@ 50 } 51 52 let mut need_zoom = false; 53- let scroll_offset_updates: Vec<_> = self 54- .webview_renderers 55- .values_mut() 56- .filter_map(|webview_renderer| { 57- let (zoom, scroll_result) = webview_renderer 58- .process_pending_scroll_and_pinch_zoom_events(&self.webrender_api); 59- need_zoom = need_zoom || (zoom == PinchZoomResult::DidPinchZoom); 60- scroll_result 61- }) 62- .collect(); 63+ let mut unconsumed_scrolls = Vec::new(); 64+ let mut scroll_offset_updates = Vec::new(); 65+ 66+ for (webview_id, webview_renderer) in self.webview_renderers.iter_mut() { 67+ let result = 68+ webview_renderer.process_pending_scroll_and_pinch_zoom_events(&self.webrender_api); 69+ need_zoom = need_zoom || (result.pinch_zoom_result == PinchZoomResult::DidPinchZoom); 70+ if let Some(scroll_result) = result.scroll_result { 71+ scroll_offset_updates.push(scroll_result); 72+ } 73+ if let Some(unconsumed_scroll) = result.unconsumed_scroll { 74+ unconsumed_scrolls.push((*webview_id, unconsumed_scroll)); 75+ } 76+ } 77 78 self.send_zoom_and_scroll_offset_updates(need_zoom, scroll_offset_updates); 79+ unconsumed_scrolls 80 } 81 82 #[track_caller] 83@@ -590,7 +609,16 @@ 84 85 let root_clip_id = builder.define_clip_rect(root_reference_frame, viewport_rect); 86 let clip_chain_id = builder.define_clip_chain(None, [root_clip_id]); 87+ 88+ // Iterate over webview_renderers in order. BTreeMap ensures deterministic ordering 89+ // by WebViewId, so parents (e.g., (0,1)) come before children (e.g., (0,2)). 90+ // This ensures children are rendered on top for proper hit testing. 91 for webview_renderer in self.webview_renderers.values() { 92+ // Skip embedded webviews - they are rendered as part of their parent's 93+ // display list through IFrameFragment, not as top-level iframes. 94+ if self.embedded_webview_ids.contains(&webview_renderer.id) { 95+ continue; 96+ } 97 if webview_renderer.hidden() { 98 continue; 99 } 100@@ -651,7 +679,7 @@ 101 /// Set the root pipeline for our WebRender scene to a display list that consists of an iframe 102 /// for each visible top-level browsing context, applying a transformation on the root for 103 /// pinch zoom, page zoom, and HiDPI scaling. 104- fn send_root_pipeline_display_list(&mut self) { 105+ pub(crate) fn send_root_pipeline_display_list(&mut self) { 106 let mut transaction = Transaction::new(); 107 self.send_root_pipeline_display_list_in_transaction(&mut transaction); 108 self.generate_frame(&mut transaction, RenderReasons::SCENE); 109@@ -717,6 +745,21 @@ 110 self.send_transaction(transaction); 111 } 112 113+ /// Send a single scroll result to WebRender. This is used when forwarding 114+ /// unconsumed scroll events from embedded webviews to their parent. 115+ pub(crate) fn send_scroll_result_to_webrender(&mut self, scroll_result: ScrollResult) { 116+ let mut transaction = Transaction::new(); 117+ transaction.set_scroll_offsets( 118+ scroll_result.external_scroll_id, 119+ vec![SampledScrollOffset { 120+ offset: scroll_result.offset, 121+ generation: 0, 122+ }], 123+ ); 124+ self.generate_frame(&mut transaction, RenderReasons::APZ); 125+ self.send_transaction(transaction); 126+ } 127+ 128 pub(crate) fn toggle_webrender_debug(&mut self, option: WebRenderDebugOption) { 129 let Some(renderer) = self.webrender_renderer.as_mut() else { 130 return; 131@@ -787,6 +830,26 @@ 132 self.send_root_pipeline_display_list(); 133 } 134 135+ /// Mark a webview as an embedded webview. Embedded webviews are not rendered 136+ /// as top-level iframes in the root display list, as they are already referenced 137+ /// by their parent's display list through IFrameFragment. 138+ pub(crate) fn register_embedded_webview(&mut self, embedded_webview_id: WebViewId) { 139+ self.embedded_webview_ids.insert(embedded_webview_id); 140+ // Also set the flag on the webview renderer so it handles zoom correctly 141+ if let Some(webview_renderer) = self.webview_renderers.get_mut(&embedded_webview_id) { 142+ webview_renderer.set_is_embedded_webview(true); 143+ } 144+ } 145+ 146+ /// Remove a webview from the embedded webview set. 147+ pub(crate) fn unregister_embedded_webview(&mut self, embedded_webview_id: WebViewId) { 148+ self.embedded_webview_ids.remove(&embedded_webview_id); 149+ // Also clear the flag on the webview renderer 150+ if let Some(webview_renderer) = self.webview_renderers.get_mut(&embedded_webview_id) { 151+ webview_renderer.set_is_embedded_webview(false); 152+ } 153+ } 154+ 155 pub(crate) fn set_throttled( 156 &mut self, 157 webview_id: WebViewId, 158@@ -1180,15 +1243,23 @@ 159 webview: Box<dyn WebViewTrait>, 160 viewport_details: ViewportDetails, 161 ) { 162- self.webview_renderers 163- .entry(webview.id()) 164- .or_insert(WebViewRenderer::new( 165+ let webview_id = webview.id(); 166+ let is_embedded = self.embedded_webview_ids.contains(&webview_id); 167+ self.webview_renderers.entry(webview_id).or_insert_with(|| { 168+ let mut renderer = WebViewRenderer::new( 169 webview, 170 viewport_details, 171 self.embedder_to_constellation_sender.clone(), 172 self.refresh_driver.clone(), 173 self.webrender_document, 174- )); 175+ ); 176+ // If this webview was already registered as embedded before being created, 177+ // set the flag now 178+ if is_embedded { 179+ renderer.set_is_embedded_webview(true); 180+ } 181+ renderer 182+ }); 183 } 184 185 pub(crate) fn remove_webview(&mut self, webview_id: WebViewId) { 186@@ -1275,28 +1346,29 @@ 187 } 188 189 pub(crate) fn notify_input_event(&mut self, webview_id: WebViewId, event: InputEventAndId) { 190- if let Some(webview_renderer) = self.webview_renderers.get_mut(&webview_id) { 191- match &event.event { 192- InputEvent::MouseMove(event) => { 193- // We only track the last mouse move position for non-touch events. 194- if !event.is_compatibility_event_for_touch { 195- let event_point = event 196- .point 197- .as_device_point(webview_renderer.device_pixels_per_page_pixel()); 198- self.last_mouse_move_position = Some(event_point); 199- } 200- }, 201- InputEvent::MouseLeftViewport(_) => { 202- self.last_mouse_move_position = None; 203- }, 204- _ => { 205- // Disable LCP calculation on any other input event except mouse moves. 206- self.lcp_calculator.disable_for_webview(webview_id); 207- }, 208- } 209+ let Some(webview_renderer) = self.webview_renderers.get_mut(&webview_id) else { 210+ return; 211+ }; 212+ match &event.event { 213+ InputEvent::MouseMove(event) => { 214+ // We only track the last mouse move position for non-touch events. 215+ if !event.is_compatibility_event_for_touch { 216+ let event_point = event 217+ .point 218+ .as_device_point(webview_renderer.device_pixels_per_page_pixel()); 219+ self.last_mouse_move_position = Some(event_point); 220+ } 221+ }, 222+ InputEvent::MouseLeftViewport(_) => { 223+ self.last_mouse_move_position = None; 224+ }, 225+ _ => { 226+ // Disable LCP calculation on any other input event except mouse moves. 227+ self.lcp_calculator.disable_for_webview(webview_id); 228+ }, 229+ } 230 231- webview_renderer.notify_input_event(&self.webrender_api, &self.needs_repaint, event); 232- } 233+ webview_renderer.notify_input_event(&self.webrender_api, &self.needs_repaint, event); 234 } 235 236 pub(crate) fn notify_scroll_event( 237@@ -1320,6 +1392,38 @@ 238 self.lcp_calculator.enabled_for_webview(webview_id) 239 } 240 241+ /// Attempt to scroll at the given point. Returns true if scroll was consumed. 242+ /// This is used for embedded webviews to check if the scroll should bubble up to the parent. 243+ pub(crate) fn try_scroll_at_point( 244+ &mut self, 245+ webview_id: WebViewId, 246+ scroll: Scroll, 247+ point: WebViewPoint, 248+ ) -> bool { 249+ let Some(webview_renderer) = self.webview_renderers.get_mut(&webview_id) else { 250+ return false; 251+ }; 252+ let device_point = point.as_device_point(webview_renderer.device_pixels_per_page_pixel()); 253+ webview_renderer 254+ .scroll_node_at_device_point(&self.webrender_api, device_point, scroll) 255+ .is_some() 256+ } 257+ 258+ /// Try to scroll any scrollable node in the webview and send the result to WebRender. 259+ /// This is used for bubbling scroll events from embedded iframes when hit-testing fails. 260+ pub(crate) fn try_scroll_any_and_send_to_webrender( 261+ &mut self, 262+ webview_id: WebViewId, 263+ scroll: Scroll, 264+ ) { 265+ let Some(webview_renderer) = self.webview_renderers.get_mut(&webview_id) else { 266+ return; 267+ }; 268+ if let Some(scroll_result) = webview_renderer.try_scroll_any(scroll) { 269+ self.send_scroll_result_to_webrender(scroll_result); 270+ } 271+ } 272+ 273 pub(crate) fn pinch_zoom( 274 &mut self, 275 webview_id: WebViewId, 276@@ -1366,7 +1470,6 @@ 277 result: InputEventResult, 278 ) { 279 let Some(webview_renderer) = self.webview_renderers.get_mut(&webview_id) else { 280- warn!("Handled input event for unknown webview: {webview_id}"); 281 return; 282 }; 283 webview_renderer.notify_input_event_handled(