forked from
me.webbeef.org/browser.html
Rewild Your Web
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(