Rewild Your Web
at main 241 lines 10 kB view raw
1--- original 2+++ modified 3@@ -15,7 +15,9 @@ 4 5 use bitflags::bitflags; 6 use chrono::Local; 7-use constellation_traits::{NavigationHistoryBehavior, ScriptToConstellationMessage}; 8+use constellation_traits::{ 9+ EmbeddedWebViewEventType, NavigationHistoryBehavior, ScriptToConstellationMessage, 10+}; 11 use content_security_policy::sandboxing_directive::SandboxingFlagSet; 12 use content_security_policy::{CspList, Policy as CspPolicy, PolicyDisposition}; 13 use cookie::Cookie; 14@@ -27,6 +29,7 @@ 15 Image, LoadStatus, 16 }; 17 use encoding_rs::{Encoding, UTF_8}; 18+use euclid::Size2D; 19 use fonts::WebFontDocumentContext; 20 use html5ever::{LocalName, Namespace, QualName, local_name, ns}; 21 use hyper_serde::Serde; 22@@ -55,7 +58,7 @@ 23 use script_traits::{DocumentActivity, ProgressiveWebMetricType}; 24 use servo_arc::Arc; 25 use servo_base::cross_process_instant::CrossProcessInstant; 26-use servo_base::generic_channel::GenericSend; 27+use servo_base::generic_channel::{GenericSend, GenericSharedMemory}; 28 use servo_base::id::WebViewId; 29 use servo_base::{Epoch, generic_channel}; 30 use servo_config::pref; 31@@ -635,6 +638,9 @@ 32 #[no_trace] 33 favicon: RefCell<Option<Image>>, 34 35+ /// The cached theme color for that document. 36+ theme_color: RefCell<Option<String>>, 37+ 38 /// All websockets created that are associated with this document. 39 websockets: DOMTracker<WebSocket>, 40 41@@ -882,6 +888,12 @@ 42 43 // Set the document's activity level, reflow if necessary, and suspend or resume timers. 44 self.activity.set(activity); 45+ 46+ // When document becomes inactive, hide all embedder controls (keyboard, etc.) 47+ if activity == DocumentActivity::Inactive { 48+ self.embedder_controls().hide_all_controls(); 49+ } 50+ 51 let media = ServoMedia::get(); 52 let pipeline_id = self.window().pipeline_id(); 53 let client_context_id = 54@@ -895,6 +907,7 @@ 55 56 self.title_changed(); 57 self.notify_embedder_favicon(); 58+ self.notify_embedder_theme_color(); 59 self.dirty_all_nodes(); 60 self.window().resume(CanGc::from_cx(cx)); 61 media.resume(&client_context_id); 62@@ -1307,6 +1320,9 @@ 63 LoadStatus::Started, 64 )); 65 self.send_to_embedder(EmbedderMsg::Status(self.webview_id(), None)); 66+ self.notify_embedded_webview_parent( 67+ EmbeddedWebViewEventType::LoadStatusChanged(LoadStatus::Started), 68+ ); 69 } 70 }, 71 DocumentReadyState::Complete => { 72@@ -1315,6 +1331,9 @@ 73 self.webview_id(), 74 LoadStatus::Complete, 75 )); 76+ self.notify_embedded_webview_parent( 77+ EmbeddedWebViewEventType::LoadStatusChanged(LoadStatus::Complete), 78+ ); 79 } 80 update_with_current_instant(&self.dom_complete); 81 }, 82@@ -1721,7 +1740,13 @@ 83 let window = self.window(); 84 if window.is_top_level() { 85 let title = self.title().map(String::from); 86- self.send_to_embedder(EmbedderMsg::ChangePageTitle(self.webview_id(), title)); 87+ self.send_to_embedder(EmbedderMsg::ChangePageTitle( 88+ self.webview_id(), 89+ title.clone(), 90+ )); 91+ // Also notify parent iframe if this is an embedded webview. 92+ // The constellation will filter and only forward if this is actually an embedded webview. 93+ self.notify_embedded_webview_parent(EmbeddedWebViewEventType::TitleChanged(title)); 94 } 95 } 96 97@@ -1730,6 +1755,18 @@ 98 window.send_to_embedder(msg); 99 } 100 101+ /// Sends a notification to the parent iframe element if this document is in an embedded webview. 102+ /// The constellation checks if the webview is embedded and forwards the event to the parent pipeline. 103+ pub(crate) fn notify_embedded_webview_parent(&self, event: EmbeddedWebViewEventType) { 104+ let window = self.window(); 105+ // Only top-level windows can be embedded webviews 106+ if window.is_top_level() { 107+ window.send_to_constellation( 108+ ScriptToConstellationMessage::EmbeddedWebViewNotification(event), 109+ ); 110+ } 111+ } 112+ 113 pub(crate) fn dirty_all_nodes(&self) { 114 let root = match self.GetDocumentElement() { 115 Some(root) => root, 116@@ -3315,9 +3352,59 @@ 117 current_rendering_epoch, 118 ); 119 120+ // After reflow, update embedded webview rects for input event routing 121+ self.update_embedded_webview_rects(); 122+ 123 (phases, statistics) 124 } 125 126+ /// Update the rects of embedded webviews for input event routing. 127+ /// This sends the current position/size of each embedded webview iframe 128+ /// to the compositor so it can route input events to the correct webview. 129+ /// Also updates visibility state when iframes have display:none. 130+ fn update_embedded_webview_rects(&self) { 131+ let parent_webview_id = self.webview_id(); 132+ let paint_api = self.window().paint_api(); 133+ let device_pixel_ratio = self.window().device_pixel_ratio().get(); 134+ 135+ // Collect embedded webview iframes first to avoid holding the borrow 136+ // during border_box() calls which can trigger reflow and need iframes_mut(). 137+ let embedded_iframes: Vec<(DomRoot<HTMLIFrameElement>, WebViewId)> = self 138+ .iframes() 139+ .iter() 140+ .filter(|iframe| iframe.is_embedded_webview()) 141+ .filter_map(|iframe| iframe.embedded_webview_id().map(|id| (iframe, id))) 142+ .collect(); 143+ 144+ for (iframe, embedded_webview_id) in embedded_iframes { 145+ // Get the iframe's border box (in CSS pixels relative to the initial containing block) 146+ // This is equivalent to getBoundingClientRect() which is viewport-relative. 147+ // If the iframe has display:none, border_box() returns None. 148+ let Some(border_box) = iframe.upcast::<Node>().border_box() else { 149+ // Iframe is not visible (display:none), notify compositor to hide it 150+ paint_api.set_embedded_webview_hidden(embedded_webview_id, parent_webview_id, true); 151+ continue; 152+ }; 153+ 154+ // Convert to device pixels 155+ // Note: border_box coordinates are viewport-relative (like getBoundingClientRect) 156+ let rect = webrender_api::units::DeviceRect::from_origin_and_size( 157+ webrender_api::units::DevicePoint::new( 158+ border_box.origin.x.to_f32_px() * device_pixel_ratio, 159+ border_box.origin.y.to_f32_px() * device_pixel_ratio, 160+ ), 161+ webrender_api::units::DeviceSize::new( 162+ border_box.size.width.to_f32_px() * device_pixel_ratio, 163+ border_box.size.height.to_f32_px() * device_pixel_ratio, 164+ ), 165+ ); 166+ 167+ // Iframe is visible, notify compositor to show it and update its rect 168+ paint_api.set_embedded_webview_hidden(embedded_webview_id, parent_webview_id, false); 169+ paint_api.update_embedded_webview_rect(embedded_webview_id, parent_webview_id, rect); 170+ } 171+ } 172+ 173 pub(crate) fn handle_no_longer_waiting_on_asynchronous_image_updates(&self) { 174 self.waiting_on_canvas_image_updates.set(false); 175 } 176@@ -4060,6 +4147,7 @@ 177 active_sandboxing_flag_set: Cell::new(SandboxingFlagSet::empty()), 178 creation_sandboxing_flag_set: Cell::new(creation_sandboxing_flag_set), 179 favicon: RefCell::new(None), 180+ theme_color: RefCell::new(None), 181 websockets: DOMTracker::new(), 182 details_name_groups: Default::default(), 183 protocol_handler_automation_mode: Default::default(), 184@@ -5174,6 +5262,36 @@ 185 186 pub(crate) fn notify_embedder_favicon(&self) { 187 if let Some(ref image) = *self.favicon.borrow() { 188+ // Encode the raw pixel data as PNG for the embedded webview event 189+ let pixel_format = match image.format { 190+ embedder_traits::PixelFormat::RGBA8 => pixels::SnapshotPixelFormat::RGBA, 191+ embedder_traits::PixelFormat::BGRA8 => pixels::SnapshotPixelFormat::BGRA, 192+ _ => pixels::SnapshotPixelFormat::RGBA, // Fallback 193+ }; 194+ 195+ let mut snapshot = pixels::Snapshot::from_vec( 196+ Size2D::new(image.width, image.height), 197+ pixel_format, 198+ pixels::SnapshotAlphaMode::Transparent { 199+ premultiplied: false, 200+ }, 201+ image.data().to_vec(), 202+ ); 203+ 204+ let mut png_bytes = Vec::new(); 205+ if snapshot 206+ .encode_for_mime_type(&pixels::EncodedImageType::Png, None, &mut png_bytes) 207+ .is_ok() 208+ { 209+ // Notify parent iframe about favicon change (for embedded webviews) 210+ self.notify_embedded_webview_parent(EmbeddedWebViewEventType::FaviconChanged { 211+ bytes: GenericSharedMemory::from_bytes(&png_bytes), 212+ width: image.width, 213+ height: image.height, 214+ }); 215+ } 216+ 217+ // Notify embedder 218 self.send_to_embedder(EmbedderMsg::NewFavicon(self.webview_id(), image.clone())); 219 } 220 } 221@@ -5233,6 +5351,20 @@ 222 pub(crate) fn set_css_styling_flag(&self, value: bool) { 223 self.css_styling_flag.set(value) 224 } 225+ 226+ pub(crate) fn notify_embedder_theme_color(&self) { 227+ if let Some(ref theme_color) = *self.theme_color.borrow() { 228+ // Notify parent iframe about theme color change (for embedded webviews) 229+ self.notify_embedded_webview_parent(EmbeddedWebViewEventType::ThemeColorChanged( 230+ theme_color.clone(), 231+ )); 232+ } 233+ } 234+ 235+ pub(crate) fn set_theme_color(&self, theme_color: String) { 236+ *self.theme_color.borrow_mut() = Some(theme_color); 237+ self.notify_embedder_theme_color(); 238+ } 239 } 240 241 impl DocumentMethods<crate::DomTypeHolder> for Document {