Rewild Your Web
web
browser
dweb
1--- original
2+++ modified
3@@ -7,8 +7,8 @@
4
5 use base::id::{BrowsingContextId, PipelineId, WebViewId};
6 use constellation_traits::{
7- IFrameLoadInfo, IFrameLoadInfoWithData, JsEvalResult, LoadData, LoadOrigin,
8- NavigationHistoryBehavior, ScriptToConstellationMessage,
9+ EmbeddedWebViewCreationRequest, IFrameLoadInfo, IFrameLoadInfoWithData, JsEvalResult, LoadData,
10+ LoadOrigin, NavigationHistoryBehavior, ScriptToConstellationMessage,
11 };
12 use content_security_policy::sandboxing_directive::{
13 SandboxingFlagSet, parse_a_sandboxing_directive,
14@@ -16,6 +16,7 @@
15 use dom_struct::dom_struct;
16 use embedder_traits::ViewportDetails;
17 use html5ever::{LocalName, Prefix, local_name, ns};
18+use ipc_channel::ipc;
19 use js::rust::HandleObject;
20 use net_traits::ReferrerPolicy;
21 use net_traits::request::Destination;
22@@ -22,30 +23,35 @@
23 use profile_traits::ipc as ProfiledIpc;
24 use script_traits::{NewPipelineInfo, UpdatePipelineIdReason};
25 use servo_url::ServoUrl;
26+use style::Atom;
27 use style::attr::{AttrValue, LengthOrPercentageOrAuto};
28-use stylo_atoms::Atom;
29
30 use crate::document_loader::{LoadBlocker, LoadType};
31 use crate::dom::attr::Attr;
32 use crate::dom::bindings::cell::DomRefCell;
33+use crate::dom::bindings::codegen::Bindings::EmbeddedWebViewBinding::ScreenshotOptions;
34 use crate::dom::bindings::codegen::Bindings::HTMLIFrameElementBinding::HTMLIFrameElementMethods;
35 use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
36 use crate::dom::bindings::codegen::UnionTypes::TrustedHTMLOrString;
37 use crate::dom::bindings::error::Fallible;
38 use crate::dom::bindings::inheritance::Castable;
39+use crate::dom::bindings::num::Finite;
40 use crate::dom::bindings::reflector::DomGlobal;
41 use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom};
42 use crate::dom::bindings::str::{DOMString, USVString};
43+use crate::dom::console::Console;
44 use crate::dom::document::Document;
45 use crate::dom::domtokenlist::DOMTokenList;
46 use crate::dom::element::{
47 AttributeMutation, Element, LayoutElementHelpers, reflect_referrer_policy_attribute,
48 };
49+use crate::dom::embedder::Embedder;
50 use crate::dom::eventtarget::EventTarget;
51 use crate::dom::globalscope::GlobalScope;
52 use crate::dom::html::htmlelement::HTMLElement;
53 use crate::dom::node::{BindContext, Node, NodeDamage, NodeTraits, UnbindContext};
54 use crate::dom::performance::performanceresourcetiming::InitiatorType;
55+use crate::dom::promise::Promise;
56 use crate::dom::trustedhtml::TrustedHTML;
57 use crate::dom::virtualmethods::VirtualMethods;
58 use crate::dom::windowproxy::WindowProxy;
59@@ -66,6 +72,12 @@
60 NotFirstTime,
61 }
62
63+// Re-export PendingDialogSender from the embedded webview module
64+pub(crate) use super::htmlembeddedwebview::PendingDialogSender;
65+// Type alias for pending permission senders
66+pub(crate) type PendingPermissionSender =
67+ base::generic_channel::GenericSender<embedder_traits::AllowOrDeny>;
68+
69 #[dom_struct]
70 pub(crate) struct HTMLIFrameElement {
71 htmlelement: HTMLElement,
72@@ -97,6 +109,30 @@
73 /// an empty iframe is attached. In that case, we shouldn't fire a
74 /// subsequent asynchronous load event.
75 already_fired_synchronous_load_event: Cell<bool>,
76+ /// Whether this iframe is in "embed" mode (hosting a top-level webview).
77+ is_embedded_webview: Cell<bool>,
78+ /// The embedded webview ID when in embed mode.
79+ #[no_trace]
80+ embedded_webview_id: Cell<Option<WebViewId>>,
81+ /// History tracking for embedded webviews - the list of URLs in the session history.
82+ #[no_trace]
83+ embedded_history: DomRefCell<Vec<ServoUrl>>,
84+ /// The current index in the embedded webview history.
85+ embedded_history_index: Cell<usize>,
86+ /// Pending dialog sender for embedded webviews.
87+ /// When an embedded webview calls alert/confirm/prompt, the IPC sender is stored here
88+ /// so the parent shell can respond directly via respondToAlert/Confirm/Prompt.
89+ #[ignore_malloc_size_of = "Channels are hard"]
90+ #[no_trace]
91+ pending_dialog: DomRefCell<Option<PendingDialogSender>>,
92+ /// Pending permission sender for embedded webviews.
93+ /// When an embedded webview requests a permission, the IPC sender is stored here
94+ /// so the parent shell can respond directly via respondToPermissionPrompt.
95+ #[ignore_malloc_size_of = "Channels are hard"]
96+ #[no_trace]
97+ pending_permission_sender: DomRefCell<Option<PendingPermissionSender>>,
98+ /// Current page zoom level for embedded webviews (default 1.0 = 100%).
99+ page_zoom: Cell<f64>,
100 }
101
102 impl HTMLIFrameElement {
103@@ -255,6 +291,8 @@
104 viewport_details,
105 user_content_manager_id: None,
106 theme: window.theme(),
107+ is_embedded_webview: false,
108+ hide_focus: false,
109 };
110
111 self.pipeline_id.set(Some(new_pipeline_id));
112@@ -484,6 +522,147 @@
113 );
114 }
115
116+ /// Create an embedded webview for this iframe when the "embed" attribute is present.
117+ /// This creates a new top-level WebView instead of a nested browsing context.
118+ fn create_embedded_webview(&self) {
119+ let Some(url) = self.shared_attribute_processing_steps_for_iframe_and_frame_elements()
120+ else {
121+ error!("Can't create embedded webview without url");
122+ return;
123+ };
124+ let document = self.owner_document();
125+ let window = self.owner_window();
126+
127+ // Check if the parent document's origin is allowed to create embedded webviews
128+ let parent_url = document.url();
129+ if !Embedder::is_allowed_to_embed_for_url(&parent_url) {
130+ let message = format!(
131+ "Embedded webview creation blocked: the 'embed' attribute on iframes is only \
132+ allowed for privileged origins. Current origin '{}' is not allowed.",
133+ parent_url.origin().ascii_serialization()
134+ );
135+ // Log error to web console so developers can see why it failed
136+ Console::internal_warn(window.as_global_scope(), DOMString::from(message.clone()));
137+ warn!("{}", message);
138+
139+ // Fall back to non-functional state (iframe won't navigate)
140+ self.is_embedded_webview.set(false);
141+ return;
142+ }
143+
144+ let pipeline_id = window.pipeline_id();
145+ let parent_webview_id = window.window_proxy().webview_id();
146+
147+ // Create load data for the embedded webview
148+ let mut load_data = LoadData::new(
149+ LoadOrigin::Script(document.origin().snapshot()),
150+ url,
151+ Some(document.base_url()),
152+ Some(pipeline_id),
153+ window.as_global_scope().get_referrer(),
154+ document.get_referrer_policy(),
155+ Some(window.as_global_scope().is_secure_context()),
156+ Some(document.insecure_requests_policy()),
157+ document.has_trustworthy_ancestor_or_current_origin(),
158+ self.sandboxing_flag_set(),
159+ );
160+ load_data.destination = Destination::IFrame;
161+ load_data.policy_container = Some(window.as_global_scope().policy_container());
162+
163+ // Clone load_data for spawning the pipeline later
164+ let load_data_for_spawn = load_data.clone();
165+ let theme = window.theme();
166+
167+ // Get the iframe's size to use as the viewport for the embedded webview.
168+ // We use border_box which gives us the size in CSS pixels.
169+ let hidpi_scale_factor = window.device_pixel_ratio();
170+ let viewport_details = self
171+ .upcast::<Node>()
172+ .border_box()
173+ .map(|border_box| ViewportDetails {
174+ size: euclid::Size2D::new(
175+ border_box.size.width.to_f32_px(),
176+ border_box.size.height.to_f32_px(),
177+ ),
178+ hidpi_scale_factor,
179+ page_zoom_for_rendering: None,
180+ })
181+ .unwrap_or_else(|| ViewportDetails {
182+ hidpi_scale_factor,
183+ ..Default::default()
184+ });
185+
186+ // Create an IPC channel for the response
187+ let (response_sender, response_receiver) =
188+ ipc::channel().expect("Failed to create IPC channel for embedded webview");
189+
190+ let hide_focus = self.has_hide_focus();
191+ let request = EmbeddedWebViewCreationRequest {
192+ load_data,
193+ parent_pipeline_id: pipeline_id,
194+ parent_webview_id,
195+ viewport_details,
196+ theme,
197+ hide_focus,
198+ response_sender,
199+ };
200+
201+ // Send the request to the constellation
202+ let global = window.as_global_scope();
203+ let msg = ScriptToConstellationMessage::CreateEmbeddedWebView(request);
204+ global.script_to_constellation_chan().send(msg).unwrap();
205+
206+ // Block waiting for the response
207+ match response_receiver.recv() {
208+ Ok(Some(response)) => {
209+ debug!(
210+ "Embedded webview created: webview_id={:?}, browsing_context_id={:?}, pipeline_id={:?}",
211+ response.new_webview_id,
212+ response.new_browsing_context_id,
213+ response.new_pipeline_id
214+ );
215+ // Store the embedded webview state
216+ self.is_embedded_webview.set(true);
217+ self.embedded_webview_id.set(Some(response.new_webview_id));
218+ self.browsing_context_id
219+ .set(Some(response.new_browsing_context_id));
220+ self.pipeline_id.set(Some(response.new_pipeline_id));
221+ self.webview_id.set(Some(response.new_webview_id));
222+
223+ // Spawn the pipeline in the script thread
224+ // Embedded webviews are top-level, so parent_info is None
225+ let new_pipeline_info = NewPipelineInfo {
226+ parent_info: None,
227+ new_pipeline_id: response.new_pipeline_id,
228+ browsing_context_id: response.new_browsing_context_id,
229+ webview_id: response.new_webview_id,
230+ opener: None,
231+ load_data: load_data_for_spawn,
232+ viewport_details,
233+ user_content_manager_id: None,
234+ theme,
235+ is_embedded_webview: true,
236+ hide_focus,
237+ };
238+
239+ with_script_thread(|script_thread| {
240+ script_thread.spawn_pipeline(new_pipeline_info);
241+ });
242+ },
243+ Ok(None) => {
244+ warn!("Embedded webview creation was rejected by embedder");
245+ // Fall back to not being an embedded webview (just a static iframe)
246+ self.is_embedded_webview.set(false);
247+ self.embedded_webview_id.set(None);
248+ },
249+ Err(e) => {
250+ warn!("Failed to receive embedded webview response: {:?}", e);
251+ self.is_embedded_webview.set(false);
252+ self.embedded_webview_id.set(None);
253+ },
254+ }
255+ }
256+
257 fn destroy_nested_browsing_context(&self) {
258 self.pipeline_id.set(None);
259 self.pending_pipeline_id.set(None);
260@@ -544,6 +723,13 @@
261 script_window_proxies: ScriptThread::window_proxies(),
262 pending_navigation: Default::default(),
263 already_fired_synchronous_load_event: Default::default(),
264+ is_embedded_webview: Cell::new(false),
265+ embedded_webview_id: Cell::new(None),
266+ embedded_history: DomRefCell::new(Vec::new()),
267+ embedded_history_index: Cell::new(0),
268+ pending_dialog: DomRefCell::new(None),
269+ pending_permission_sender: DomRefCell::new(None),
270+ page_zoom: Cell::new(1.0),
271 }
272 }
273
274@@ -579,6 +765,149 @@
275 self.webview_id.get()
276 }
277
278+ /// Check whether this iframe has the "embed" attribute set.
279+ fn is_embed_mode(&self) -> bool {
280+ self.upcast::<Element>()
281+ .has_attribute(&local_name!("embed"))
282+ }
283+
284+ /// Check whether this iframe has the "hidefocus" attribute set.
285+ /// When set, the embedded webview should never receive focus.
286+ fn has_hide_focus(&self) -> bool {
287+ self.upcast::<Element>()
288+ .has_attribute(&local_name!("hidefocus"))
289+ }
290+
291+ /// Check if this iframe should adopt a pre-created embedded webview.
292+ /// This is set via the `adopt-webview-id`, `adopt-browsing-context-id`,
293+ /// and `adopt-pipeline-id` attributes, which are used when the constellation
294+ /// has already created an embedded webview (e.g., from window.open in an embedded context).
295+ fn get_adopt_ids(&self) -> Option<(WebViewId, BrowsingContextId, PipelineId)> {
296+ let element = self.upcast::<Element>();
297+
298+ // All three attributes must be present
299+ let webview_id_str = element.get_string_attribute(&LocalName::from("adopt-webview-id"));
300+ let browsing_context_id_str =
301+ element.get_string_attribute(&LocalName::from("adopt-browsing-context-id"));
302+ let pipeline_id_str = element.get_string_attribute(&LocalName::from("adopt-pipeline-id"));
303+
304+ if webview_id_str.is_empty() ||
305+ browsing_context_id_str.is_empty() ||
306+ pipeline_id_str.is_empty()
307+ {
308+ return None;
309+ }
310+
311+ // Parse the IDs (they are in the format "TypeName(namespace,index)")
312+ let browsing_context_id = BrowsingContextId::from_string(&browsing_context_id_str.str())?;
313+ let pipeline_id = PipelineId::from_string(&pipeline_id_str.str())?;
314+
315+ // WebViewId is "(painter_id, browsing_context_id)" - we derive it from the browsing_context_id
316+ // The WebViewId is constructed from the same BrowsingContextId
317+ let webview_id = WebViewId::from_string(&webview_id_str.str())?;
318+
319+ Some((webview_id, browsing_context_id, pipeline_id))
320+ }
321+
322+ /// Adopt a pre-created embedded webview. This is called when the iframe has
323+ /// adopt attributes set, indicating that constellation has already created
324+ /// the webview and we just need to associate it with this iframe.
325+ fn adopt_embedded_webview(
326+ &self,
327+ webview_id: WebViewId,
328+ browsing_context_id: BrowsingContextId,
329+ pipeline_id: PipelineId,
330+ can_gc: CanGc,
331+ ) {
332+ debug!(
333+ "<iframe> adopting embedded webview: webview_id={:?}, browsing_context_id={:?}, pipeline_id={:?}",
334+ webview_id, browsing_context_id, pipeline_id
335+ );
336+
337+ // Store the embedded webview state
338+ self.is_embedded_webview.set(true);
339+ self.embedded_webview_id.set(Some(webview_id));
340+ self.browsing_context_id.set(Some(browsing_context_id));
341+ self.pipeline_id.set(Some(pipeline_id));
342+ self.webview_id.set(Some(webview_id));
343+
344+ // The pipeline is already spawned by the constellation, so we don't need to
345+ // call spawn_pipeline here. The webview is already running.
346+
347+ // Remove the adopt attributes now that we've used them
348+ let element = self.upcast::<Element>();
349+ element.remove_attribute(&ns!(), &LocalName::from("adopt-webview-id"), can_gc);
350+ element.remove_attribute(
351+ &ns!(),
352+ &LocalName::from("adopt-browsing-context-id"),
353+ can_gc,
354+ );
355+ element.remove_attribute(&ns!(), &LocalName::from("adopt-pipeline-id"), can_gc);
356+ }
357+
358+ /// Get the effective webview ID, taking into account embedded webview mode.
359+ /// Returns the embedded webview ID if in embed mode, otherwise the parent webview ID.
360+ #[inline]
361+ pub(crate) fn embedded_webview_id(&self) -> Option<WebViewId> {
362+ if self.is_embedded_webview.get() {
363+ self.embedded_webview_id.get()
364+ } else {
365+ None
366+ }
367+ }
368+
369+ /// Returns true if this iframe is hosting an embedded webview (created with "embed" attribute).
370+ /// Embedded webviews have their own top-level WebViewId and window.parent === window.self.
371+ #[inline]
372+ pub(crate) fn is_embedded_webview(&self) -> bool {
373+ self.is_embedded_webview.get()
374+ }
375+
376+ /// Set the embedded history entries and current index.
377+ /// Called when the constellation sends history change notifications.
378+ pub(crate) fn set_embedded_history(&self, entries: Vec<ServoUrl>, index: usize) {
379+ *self.embedded_history.borrow_mut() = entries;
380+ self.embedded_history_index.set(index);
381+ }
382+
383+ /// Get the embedded history state as (can_go_back, can_go_forward).
384+ pub(crate) fn embedded_history_state(&self) -> (bool, bool) {
385+ let can_go_back = self.embedded_history_index.get() > 0;
386+ let history = self.embedded_history.borrow();
387+ let can_go_forward = history.len() > self.embedded_history_index.get() + 1;
388+ (can_go_back, can_go_forward)
389+ }
390+
391+ /// Set the pending dialog sender for embedded webview dialog responses.
392+ pub(crate) fn set_pending_dialog(&self, sender: Option<PendingDialogSender>) {
393+ *self.pending_dialog.borrow_mut() = sender;
394+ }
395+
396+ /// Take the pending dialog sender, removing it from storage.
397+ pub(crate) fn take_pending_dialog(&self) -> Option<PendingDialogSender> {
398+ self.pending_dialog.borrow_mut().take()
399+ }
400+
401+ /// Set the pending permission sender for embedded webview permission prompt responses.
402+ pub(crate) fn set_pending_permission_sender(&self, sender: Option<PendingPermissionSender>) {
403+ *self.pending_permission_sender.borrow_mut() = sender;
404+ }
405+
406+ /// Take the pending permission sender, removing it from storage.
407+ pub(crate) fn take_pending_permission_sender(&self) -> Option<PendingPermissionSender> {
408+ self.pending_permission_sender.borrow_mut().take()
409+ }
410+
411+ /// Get the current page zoom level.
412+ pub(crate) fn get_page_zoom(&self) -> f64 {
413+ self.page_zoom.get()
414+ }
415+
416+ /// Set the page zoom level.
417+ pub(crate) fn set_page_zoom(&self, zoom: f64) {
418+ self.page_zoom.set(zoom);
419+ }
420+
421 #[inline]
422 pub(crate) fn sandboxing_flag_set(&self) -> SandboxingFlagSet {
423 self.sandboxing_flag_set
424@@ -917,6 +1246,85 @@
425 // This is specified as reflecting the name content attribute of the
426 // element, not the name of the child browsing context.
427 make_getter!(Name, "name");
428+
429+ // Servo extension: Embedded WebView methods
430+ // These delegate to helper methods in htmlembeddedwebview.rs
431+
432+ fn Load(&self, url: USVString) -> Fallible<()> {
433+ self.embedded_load(url)
434+ }
435+
436+ fn Reload(&self) -> Fallible<()> {
437+ self.embedded_reload()
438+ }
439+
440+ fn GetPageZoom(&self) -> Fallible<Finite<f64>> {
441+ self.embedded_page_zoom().map(Finite::wrap)
442+ }
443+
444+ fn SetPageZoom(&self, zoom: Finite<f64>) -> Fallible<()> {
445+ self.embedded_set_page_zoom(*zoom)
446+ }
447+
448+ fn CanGoBack(&self) -> Fallible<bool> {
449+ self.embedded_can_go_back()
450+ }
451+
452+ fn GoBack(&self) -> Fallible<DOMString> {
453+ self.embedded_go_back()
454+ }
455+
456+ fn CanGoForward(&self) -> Fallible<bool> {
457+ self.embedded_can_go_forward()
458+ }
459+
460+ fn GoForward(&self) -> Fallible<DOMString> {
461+ self.embedded_go_forward()
462+ }
463+
464+ fn TakeScreenshot(&self, options: &ScreenshotOptions) -> Fallible<Rc<Promise>> {
465+ self.embedded_take_screenshot(options)
466+ }
467+
468+ fn RespondToSelectControl(&self, control_id: DOMString, selected_index: i32) -> Fallible<()> {
469+ self.embedded_respond_to_select_control(control_id, selected_index)
470+ }
471+
472+ fn RespondToColorPicker(
473+ &self,
474+ control_id: DOMString,
475+ color: Option<DOMString>,
476+ ) -> Fallible<()> {
477+ self.embedded_respond_to_color_picker(control_id, color)
478+ }
479+
480+ fn RespondToContextMenu(
481+ &self,
482+ control_id: DOMString,
483+ action_id: Option<DOMString>,
484+ ) -> Fallible<()> {
485+ self.embedded_respond_to_context_menu(control_id, action_id)
486+ }
487+
488+ fn CancelEmbedderControl(&self, control_id: DOMString) -> Fallible<()> {
489+ self.embedded_cancel_embedder_control(control_id)
490+ }
491+
492+ fn RespondToAlert(&self, control_id: DOMString) -> Fallible<()> {
493+ self.embedded_respond_to_alert(control_id)
494+ }
495+
496+ fn RespondToConfirm(&self, control_id: DOMString, confirmed: bool) -> Fallible<()> {
497+ self.embedded_respond_to_confirm(control_id, confirmed)
498+ }
499+
500+ fn RespondToPrompt(&self, control_id: DOMString, value: Option<DOMString>) -> Fallible<()> {
501+ self.embedded_respond_to_prompt(control_id, value)
502+ }
503+
504+ fn RespondToPermissionPrompt(&self, control_id: DOMString, allowed: bool) -> Fallible<()> {
505+ self.embedded_respond_to_permission_prompt(control_id, allowed)
506+ }
507 }
508
509 impl VirtualMethods for HTMLIFrameElement {
510@@ -968,8 +1376,36 @@
511 // is in a document tree and has a browsing context, which is what causes
512 // the child browsing context to be created.
513 if self.upcast::<Node>().is_connected_with_browsing_context() {
514- debug!("iframe src set while in browsing context.");
515- self.process_the_iframe_attributes(ProcessingMode::NotFirstTime, can_gc);
516+ // For embedded webviews, navigate using the load() method instead of
517+ // processing iframe attributes (which is for regular nested iframes).
518+ if self.is_embedded_webview.get() {
519+ if let Some(webview_id) = self.embedded_webview_id.get() {
520+ let url = self
521+ .shared_attribute_processing_steps_for_iframe_and_frame_elements()
522+ .expect("Failed to get embedding iframe url");
523+ let window = self.owner_window();
524+ window
525+ .as_global_scope()
526+ .script_to_constellation_chan()
527+ .send(ScriptToConstellationMessage::EmbeddedWebViewLoad(
528+ webview_id, url,
529+ ))
530+ .unwrap();
531+ }
532+ } else {
533+ debug!("iframe src set while in browsing context.");
534+ self.process_the_iframe_attributes(ProcessingMode::NotFirstTime, can_gc);
535+ }
536+ }
537+ },
538+ local_name!("embed") => {
539+ // The embed attribute determines whether this iframe hosts an embedded webview.
540+ // Warn if it's changed after the iframe is already connected, as this is not supported.
541+ if self.upcast::<Node>().is_connected_with_browsing_context() {
542+ warn!(
543+ "The 'embed' attribute on iframe should not be changed after insertion. \
544+ The iframe mode (nested vs embedded webview) is determined at insertion time."
545+ );
546 }
547 },
548 _ => {},
549@@ -1013,6 +1449,23 @@
550
551 debug!("<iframe> running post connection steps");
552
553+ // Check if this iframe should adopt a pre-created embedded webview
554+ // (e.g., from window.open() in an embedded context)
555+ if let Some((webview_id, browsing_context_id, pipeline_id)) = self.get_adopt_ids() {
556+ debug!("<iframe> adopting pre-created embedded webview");
557+ self.adopt_embedded_webview(webview_id, browsing_context_id, pipeline_id, can_gc);
558+ return;
559+ }
560+
561+ // Check if this iframe has the "embed" attribute for embedded webview mode
562+ if self.is_embed_mode() {
563+ debug!("<iframe> in embed mode, creating embedded webview");
564+ self.create_embedded_webview();
565+ // For embedded webviews, we don't process sandbox or iframe attributes
566+ // the same way - the embedded webview handles its own security context
567+ return;
568+ }
569+
570 // Step 1. Create a new child navigable for insertedNode.
571 self.create_nested_browsing_context(can_gc);
572
573@@ -1035,8 +1488,22 @@
574 fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
575 self.super_type().unwrap().unbind_from_tree(context, can_gc);
576
577- // The iframe HTML element removing steps, given removedNode, are to destroy a child navigable given removedNode
578- self.destroy_child_navigable(can_gc);
579+ // If this is an embedded webview, notify the compositor to stop tracking its rect
580+ if self.is_embedded_webview.get() {
581+ if let Some(embedded_webview_id) = self.embedded_webview_id.get() {
582+ let window = self.owner_window();
583+ let global = window.as_global_scope();
584+ let msg = ScriptToConstellationMessage::RemoveEmbeddedWebView(embedded_webview_id);
585+ global.script_to_constellation_chan().send(msg).unwrap();
586+
587+ window
588+ .paint_api()
589+ .remove_embedded_webview(embedded_webview_id);
590+ }
591+ } else {
592+ // The iframe HTML element removing steps, given removedNode, are to destroy a child navigable given removedNode
593+ self.destroy_child_navigable(can_gc);
594+ }
595
596 self.owner_document().invalidate_iframes_collection();
597 }