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