Rewild Your Web
web browser dweb
at main 597 lines 26 kB view raw
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 }