···11--- original
22+++ modified
33-@@ -37,7 +37,7 @@
33+@@ -36,7 +36,7 @@
44 use crate::refresh_driver::BaseRefreshDriver;
55 use crate::touch::{PendingTouchInputEvent, TouchHandler, TouchMoveAllowed, TouchSequenceState};
66···99 pub(crate) struct ScrollEvent {
1010 /// Scroll by this offset, or to Start or End
1111 pub scroll: Scroll,
1212-@@ -74,6 +74,18 @@
1212+@@ -73,6 +73,18 @@
1313 DidNotPinchZoom,
1414 }
1515···2828 /// A renderer for a libservo `WebView`. This is essentially the [`ServoRenderer`]'s interface to a
2929 /// libservo `WebView`, but the code here cannot depend on libservo in order to prevent circular
3030 /// dependencies, which is why we store a `dyn WebViewTrait` here instead of the `WebView` itself.
3131-@@ -115,6 +127,10 @@
3131+@@ -114,6 +126,10 @@
3232 /// and initial values for zoom derived from the `viewport` meta tag in web content.
3333 viewport_description: Option<ViewportDescription>,
3434···3939 //
4040 // Data that is shared with the parent renderer.
4141 //
4242-@@ -153,6 +169,7 @@
4242+@@ -152,6 +168,7 @@
4343 hidden: false,
4444 animating: false,
4545 viewport_description: None,
···4747 embedder_to_constellation_sender,
4848 refresh_driver,
4949 webrender_document,
5050-@@ -188,6 +205,16 @@
5050+@@ -187,6 +204,16 @@
5151 new_value != old_value
5252 }
5353···6464 /// Returns the [`PipelineDetails`] for the given [`PipelineId`], creating it if needed.
6565 pub(crate) fn ensure_pipeline_details(
6666 &mut self,
6767-@@ -353,10 +380,9 @@
6767+@@ -362,10 +389,9 @@
6868 _ => None,
6969 }
7070 .or_else(|| self.hit_test(render_api, point).into_iter().nth(0));
···7878 hit_test_result
7979 },
8080 None => None,
8181-@@ -680,6 +706,89 @@
8181+@@ -689,6 +715,88 @@
8282 self.on_scroll_window_event(scroll, point);
8383 }
8484···155155+ external_scroll_id,
156156+ };
157157+
158158-+ self.send_scroll_positions_to_layout_for_pipeline(root_pipeline_id);
159159-+ self.dispatch_scroll_event(external_scroll_id, hit_test_result.clone());
158158++ self.send_scroll_positions_to_layout_for_pipeline(root_pipeline_id, external_scroll_id);
160159+
161160+ Some(ScrollResult {
162161+ hit_test_result,
···168167 fn on_scroll_window_event(&mut self, scroll: Scroll, cursor: DevicePoint) {
169168 self.pending_scroll_zoom_events
170169 .push(ScrollZoomEvent::Scroll(ScrollEvent {
171171-@@ -689,18 +798,25 @@
170170+@@ -698,18 +806,25 @@
172171 }));
173172 }
174173···199198 }
200199201200 // Batch up all scroll events and changes to pinch zoom into a single change, or
202202-@@ -754,15 +870,24 @@
201201+@@ -763,15 +878,24 @@
203202 }
204203 }
205204···230229231230 let scroll_result = combined_scroll_event.and_then(|combined_event| {
232231 self.scroll_node_at_device_point(
233233-@@ -771,6 +896,21 @@
232232+@@ -780,6 +904,21 @@
234233 combined_event.scroll,
235234 )
236235 });
···252251 if let Some(ref scroll_result) = scroll_result {
253252 self.send_scroll_positions_to_layout_for_pipeline(
254253 scroll_result.hit_test_result.pipeline_id,
255255-@@ -789,7 +929,11 @@
254254+@@ -795,7 +934,11 @@
256255 self.send_pinch_zoom_infos_to_script();
257256 }
258257···265264 }
266265267266 /// Perform a hit test at the given [`DevicePoint`] and apply the [`Scroll`]
268268-@@ -796,7 +940,7 @@
267267+@@ -802,7 +945,7 @@
269268 /// scrolling to the applicable scroll node under that point. If a scroll was
270269 /// performed, returns the hit test result contains [`PipelineId`] of the node
271270 /// scrolled, the id, and the final scroll delta.
···274273 &mut self,
275274 render_api: &RenderApi,
276275 cursor: DevicePoint,
277277-@@ -824,7 +968,10 @@
276276+@@ -830,7 +973,10 @@
278277 // its ancestor pipelines.
279278 let mut previous_pipeline_id = None;
280279 for hit_test_result in hit_test_results {
···286285 if previous_pipeline_id.replace(hit_test_result.pipeline_id) !=
287286 Some(hit_test_result.pipeline_id)
288287 {
289289-@@ -851,7 +998,11 @@
288288+@@ -857,7 +1003,11 @@
290289 }
291290 }
292291 }
···299298 }
300299301300 /// Scroll the viewport (root pipeline, root scroll node) of this WebView, but first
302302-@@ -1007,20 +1158,45 @@
301301+@@ -996,20 +1146,45 @@
303302 }
304303305304 fn send_window_size_message(&self) {
···357356 }
358357359358 /// Set the `hidpi_scale_factor` for this renderer, returning `true` if the value actually changed.
360360-@@ -1086,8 +1262,21 @@
359359+@@ -1075,8 +1250,21 @@
361360 if let Some(wheel_event) = self.pending_wheel_events.remove(&id) {
362361 if !result.contains(InputEventResult::DefaultPrevented) {
363362 // A scroll delta for a wheel event is the inverse of the wheel delta.
···1919 use js::context::JSContext;
2020 use js::rust::HandleObject;
2121 use net_traits::ReferrerPolicy;
2222-@@ -23,30 +24,35 @@
2323- use profile_traits::ipc as ProfiledIpc;
2222+@@ -24,30 +25,35 @@
2323+ use script_bindings::script_runtime::temp_cx;
2424 use script_traits::{NewPipelineInfo, UpdatePipelineIdReason};
2525 use servo_url::ServoUrl;
2626+use style::Atom;
···5656 use crate::dom::trustedhtml::TrustedHTML;
5757 use crate::dom::virtualmethods::VirtualMethods;
5858 use crate::dom::windowproxy::WindowProxy;
5959-@@ -67,6 +73,12 @@
6060- NotFirstTime,
5959+@@ -76,6 +82,12 @@
6060+ SrcDoc,
6161 }
62626363+// Re-export PendingDialogSender from the embedded webview module
···6969 #[dom_struct]
7070 pub(crate) struct HTMLIFrameElement {
7171 htmlelement: HTMLElement,
7272-@@ -98,6 +110,30 @@
7272+@@ -112,6 +124,30 @@
7373 /// an empty iframe is attached. In that case, we shouldn't fire a
7474 /// subsequent asynchronous load event.
7575 already_fired_synchronous_load_event: Cell<bool>,
···100100 }
101101102102 impl HTMLIFrameElement {
103103-@@ -256,6 +292,8 @@
103103+@@ -265,6 +301,8 @@
104104 viewport_details,
105105 user_content_manager_id: None,
106106 theme: window.theme(),
···109109 };
110110111111 self.pipeline_id.set(Some(new_pipeline_id));
112112-@@ -485,6 +523,147 @@
112112+@@ -560,6 +598,147 @@
113113 );
114114 }
115115···257257 fn destroy_nested_browsing_context(&self) {
258258 self.pipeline_id.set(None);
259259 self.pending_pipeline_id.set(None);
260260-@@ -545,6 +724,13 @@
261261- script_window_proxies: ScriptThread::window_proxies(),
260260+@@ -622,6 +801,13 @@
261261+ lazy_load_resumption_steps: Default::default(),
262262 pending_navigation: Default::default(),
263263 already_fired_synchronous_load_event: Default::default(),
264264+ is_embedded_webview: Cell::new(false),
···271271 }
272272 }
273273274274-@@ -580,6 +766,157 @@
274274+@@ -657,7 +843,158 @@
275275 self.webview_id.get()
276276 }
277277···376376+
377377+ /// Returns true if this iframe is hosting an embedded webview (created with "embed" attribute).
378378+ /// Embedded webviews have their own top-level WebViewId and window.parent === window.self.
379379-+ #[inline]
379379+ #[inline]
380380+ pub(crate) fn is_embedded_webview(&self) -> bool {
381381+ self.is_embedded_webview.get()
382382+ }
···426426+ self.page_zoom.set(zoom);
427427+ }
428428+
429429- #[inline]
429429++ #[inline]
430430 pub(crate) fn sandboxing_flag_set(&self) -> SandboxingFlagSet {
431431 self.sandboxing_flag_set
432432-@@ -918,6 +1255,85 @@
433433- // This is specified as reflecting the name content attribute of the
434434- // element, not the name of the child browsing context.
435435- make_getter!(Name, "name");
432432+ .get()
433433+@@ -1014,6 +1351,85 @@
434434+435435+ // https://html.spec.whatwg.org/multipage/#attr-iframe-loading
436436+ make_setter!(SetLoading, "loading");
436437+
437438+ // Servo extension: Embedded WebView methods
438439+ // These delegate to helper methods in htmlembeddedwebview.rs
···515516 }
516517517518 impl VirtualMethods for HTMLIFrameElement {
518518-@@ -969,8 +1385,36 @@
519519+@@ -1069,10 +1485,38 @@
519520 // is in a document tree and has a browsing context, which is what causes
520521 // the child browsing context to be created.
521522 if self.upcast::<Node>().is_connected_with_browsing_context() {
522523- debug!("iframe src set while in browsing context.");
523523-- self.process_the_iframe_attributes(ProcessingMode::NotFirstTime, can_gc);
524524+- self.process_the_iframe_attributes(ProcessingMode::NotFirstTime, cx);
524525+ // For embedded webviews, navigate using the load() method instead of
525526+ // processing iframe attributes (which is for regular nested iframes).
526527+ if self.is_embedded_webview.get() {
···539540+ }
540541+ } else {
541542+ debug!("iframe src set while in browsing context.");
542542-+ self.process_the_iframe_attributes(ProcessingMode::NotFirstTime, can_gc);
543543++ self.process_the_iframe_attributes(ProcessingMode::NotFirstTime, cx);
543544+ }
544544-+ }
545545-+ },
545545+ }
546546+ },
546547+ local_name!("embed") => {
547548+ // The embed attribute determines whether this iframe hosts an embedded webview.
548549+ // Warn if it's changed after the iframe is already connected, as this is not supported.
···551552+ "The 'embed' attribute on iframe should not be changed after insertion. \
552553+ The iframe mode (nested vs embedded webview) is determined at insertion time."
553554+ );
554554- }
555555- },
556556- _ => {},
557557-@@ -1014,6 +1458,23 @@
555555++ }
556556++ },
557557+ local_name!("loading") => {
558558+ // https://html.spec.whatwg.org/multipage/#attr-iframe-loading
559559+ // > When the loading attribute's state is changed to the Eager state, the user agent must run these steps:
560560+@@ -1135,6 +1579,23 @@
558561559562 debug!("<iframe> running post connection steps");
560563···576579+ }
577580+
578581 // Step 1. Create a new child navigable for insertedNode.
579579- self.create_nested_browsing_context(CanGc::from_cx(cx));
582582+ self.create_nested_browsing_context(cx);
580583581581-@@ -1036,8 +1497,22 @@
584584+@@ -1158,11 +1619,25 @@
582585 fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
583586 self.super_type().unwrap().unbind_from_tree(context, can_gc);
584587585585-- // The iframe HTML element removing steps, given removedNode, are to destroy a child navigable given removedNode
586586-- self.destroy_child_navigable(can_gc);
588588+- // TODO: https://github.com/servo/servo/issues/42837
589589+- let mut cx = unsafe { temp_cx() };
587590+ // If this is an embedded webview, notify the compositor to stop tracking its rect
588591+ if self.is_embedded_webview.get() {
589592+ if let Some(embedded_webview_id) = self.embedded_webview_id.get() {
···597600+ .remove_embedded_webview(embedded_webview_id);
598601+ }
599602+ } else {
603603++ // TODO: https://github.com/servo/servo/issues/42837
604604++ let mut cx = unsafe { temp_cx() };
605605+606606+- // The iframe HTML element removing steps, given removedNode, are to destroy a child navigable given removedNode
607607+- self.destroy_child_navigable(&mut cx);
600608+ // The iframe HTML element removing steps, given removedNode, are to destroy a child navigable given removedNode
601601-+ self.destroy_child_navigable(can_gc);
609609++ self.destroy_child_navigable(&mut cx);
602610+ }
603611604612 self.owner_document().invalidate_iframes_collection();
+8-8
patches/components/script/dom/window.rs.patch
···99 };
1010 use euclid::default::Rect as UntypedRect;
1111 use euclid::{Point2D, Rect, Scale, Size2D, Vector2D};
1212-@@ -1144,12 +1144,22 @@
1212+@@ -1148,12 +1148,22 @@
13131414 let (sender, receiver) =
1515 ProfiledGenericChannel::channel(self.global().time_profiler_chan().clone()).unwrap();
···3333 receiver.recv().unwrap_or_else(|_| {
3434 // If the receiver is closed, we assume the dialog was cancelled.
3535 debug!("Alert dialog was cancelled or failed to show.");
3636-@@ -1177,13 +1187,22 @@
3636+@@ -1181,13 +1191,22 @@
3737 // the user to respond with a positive or negative response.
3838 let (sender, receiver) =
3939 ProfiledGenericChannel::channel(self.global().time_profiler_chan().clone()).unwrap();
···5757 // Step 5: Let userPromptHandler be WebDriver BiDi user prompt opened with this,
5858 // "confirm", and message.
5959 //
6060-@@ -1228,6 +1247,7 @@
6060+@@ -1232,6 +1251,7 @@
6161 // defaulted to the value given by default.
6262 let (sender, receiver) =
6363 ProfiledGenericChannel::channel(self.global().time_profiler_chan().clone()).unwrap();
···6565 let dialog = SimpleDialogRequest::Prompt {
6666 id: self.Document().embedder_controls().next_control_id(),
6767 message: message.to_string(),
6868-@@ -1234,8 +1254,16 @@
6868+@@ -1238,8 +1258,16 @@
6969 default: default.to_string(),
7070 response_sender: sender,
7171 };
···8383 // Step 6: Let userPromptHandler be WebDriver BiDi user prompt opened with this,
8484 // "prompt", and message.
8585 // TODO: Add support for WebDriver BiDi.
8686-@@ -3028,9 +3056,33 @@
8686+@@ -3030,9 +3058,33 @@
8787 &self,
8888 input_event: &ConstellationInputEvent,
8989 ) -> Option<HitTestResult> {
···120120 }
121121122122 #[expect(unsafe_code)]
123123-@@ -3049,8 +3101,25 @@
123123+@@ -3051,8 +3103,25 @@
124124 // SAFETY: This is safe because `Window::query_elements_from_point` has ensured that
125125 // layout has run and any OpaqueNodes that no longer refer to real nodes are gone.
126126 let address = UntrustedNodeAddress(result.node.0 as *const c_void);
···147147 cursor: result.cursor,
148148 point_in_node: result.point_in_target,
149149 point_in_frame,
150150-@@ -3731,6 +3800,8 @@
150150+@@ -3733,6 +3802,8 @@
151151 player_context: WindowGLContext,
152152 #[cfg(feature = "webgpu")] gpu_id_hub: Arc<IdentityHub>,
153153 inherited_secure_context: Option<bool>,
···156156 theme: Theme,
157157 weak_script_thread: Weak<ScriptThread>,
158158 ) -> DomRoot<Self> {
159159-@@ -3758,6 +3829,8 @@
159159+@@ -3759,6 +3830,8 @@
160160 gpu_id_hub,
161161 inherited_secure_context,
162162 unminify_js,