Rewild Your Web
at main 359 lines 15 kB view raw
1--- original 2+++ modified 3@@ -35,15 +35,15 @@ 4 use base::cross_process_instant::CrossProcessInstant; 5 use base::generic_channel; 6 use base::id::{ 7- BrowsingContextId, HistoryStateId, PipelineId, PipelineNamespace, ScriptEventLoopId, 8- TEST_WEBVIEW_ID, WebViewId, 9+ BrowsingContextId, HistoryStateId, MessagePortId, PipelineId, PipelineNamespace, 10+ ScriptEventLoopId, TEST_WEBVIEW_ID, WebViewId, 11 }; 12 use canvas_traits::webgl::WebGLPipeline; 13 use chrono::{DateTime, Local}; 14 use constellation_traits::{ 15- JsEvalResult, LoadData, LoadOrigin, NavigationHistoryBehavior, ScreenshotReadinessResponse, 16- ScriptToConstellationChan, ScriptToConstellationMessage, ScrollStateUpdate, 17- StructuredSerializedData, TraversalDirection, WindowSizeType, 18+ EmbeddedWebViewEventType, JsEvalResult, LoadData, LoadOrigin, NavigationHistoryBehavior, 19+ ScreenshotReadinessResponse, ScriptToConstellationChan, ScriptToConstellationMessage, 20+ ScrollStateUpdate, StructuredSerializedData, TraversalDirection, WindowSizeType, 21 }; 22 use crossbeam_channel::unbounded; 23 use data_url::mime::Mime; 24@@ -55,7 +55,7 @@ 25 use embedder_traits::{ 26 EmbedderControlId, EmbedderControlResponse, EmbedderMsg, FocusSequenceNumber, 27 InputEventOutcome, JavaScriptEvaluationError, JavaScriptEvaluationId, MediaSessionActionType, 28- Theme, ViewportDetails, WebDriverScriptCommand, 29+ ServoErrorType, Theme, ViewportDetails, WebDriverScriptCommand, 30 }; 31 use encoding_rs::Encoding; 32 use fonts::{FontContext, SystemFontServiceProxy}; 33@@ -92,6 +92,7 @@ 34 UpdatePipelineIdReason, 35 }; 36 use servo_arc::Arc as ServoArc; 37+use servo_config::pref_util::PrefValue; 38 use servo_config::{opts, pref, prefs}; 39 use servo_url::{ImmutableOrigin, MutableOrigin, OriginSnapshot, ServoUrl}; 40 use storage_traits::StorageThreads; 41@@ -115,6 +116,8 @@ 42 use crate::dom::bindings::codegen::Bindings::DocumentBinding::{ 43 DocumentMethods, DocumentReadyState, 44 }; 45+use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; 46+use crate::dom::bindings::codegen::Bindings::MessagePortBinding::MessagePortMethods; 47 use crate::dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorMethods; 48 use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; 49 use crate::dom::bindings::conversions::{ 50@@ -134,9 +137,13 @@ 51 RenderingUpdateReason, 52 }; 53 use crate::dom::element::Element; 54+use crate::dom::event::Event; 55+use crate::dom::eventtarget::EventTarget; 56 use crate::dom::globalscope::GlobalScope; 57 use crate::dom::html::htmliframeelement::{HTMLIFrameElement, IframeContext}; 58+use crate::dom::messageport::MessagePort; 59 use crate::dom::node::{Node, NodeTraits}; 60+use crate::dom::peerstreamevent::PeerStreamEvent; 61 use crate::dom::servoparser::{ParserContext, ServoParser}; 62 use crate::dom::types::DebuggerGlobalScope; 63 #[cfg(feature = "webgpu")] 64@@ -1943,11 +1950,22 @@ 65 self.handle_refresh_cursor(pipeline_id); 66 }, 67 ScriptThreadMessage::PreferencesUpdated(updates) => { 68- let mut current_preferences = prefs::get().clone(); 69- for (name, value) in updates { 70- current_preferences.set_value(&name, value); 71+ // Only update core preferences (those without namespace separator) 72+ // Embedder preferences are already stored in the embedder prefs registry 73+ let core_updates: Vec<_> = updates 74+ .iter() 75+ .filter(|(name, _)| !name.contains('.')) 76+ .collect(); 77+ if !core_updates.is_empty() { 78+ let mut current_preferences = prefs::get().clone(); 79+ for (name, value) in core_updates { 80+ current_preferences.set_value(name, value.clone()); 81+ } 82+ prefs::set(current_preferences); 83 } 84- prefs::set(current_preferences); 85+ 86+ // Dispatch preferencechanged events to all Embedder instances 87+ self.dispatch_preference_changed_to_embedders(&updates, CanGc::from_cx(cx)); 88 }, 89 ScriptThreadMessage::ForwardKeyboardScroll(pipeline_id, scroll) => { 90 if let Some(document) = self.documents.borrow().find_document(pipeline_id) { 91@@ -1988,6 +2006,35 @@ 92 ScriptThreadMessage::TriggerGarbageCollection => unsafe { 93 JS_GC(*GlobalScope::get_cx(), GCReason::API); 94 }, 95+ ScriptThreadMessage::DispatchEmbeddedWebViewEvent { 96+ target, 97+ parent, 98+ event, 99+ } => { 100+ self.handle_embedded_webview_event(parent, target, event, CanGc::from_cx(cx)); 101+ }, 102+ ScriptThreadMessage::DispatchServoError(error_type, message) => { 103+ self.handle_dispatch_servo_error(error_type, message, CanGc::from_cx(cx)); 104+ }, 105+ ScriptThreadMessage::DispatchPairingEvent(event) => { 106+ self.handle_dispatch_pairing_event(event, CanGc::from_cx(cx)); 107+ }, 108+ ScriptThreadMessage::DispatchPeerStream( 109+ peer_id, 110+ remote_port_id_bytes, 111+ stream_id, 112+ from_peer, 113+ target_url, 114+ ) => { 115+ self.handle_dispatch_peer_stream( 116+ peer_id, 117+ remote_port_id_bytes, 118+ stream_id, 119+ from_peer, 120+ target_url, 121+ CanGc::from_cx(cx), 122+ ); 123+ }, 124 } 125 } 126 127@@ -3059,6 +3106,9 @@ 128 .documents 129 .borrow() 130 .find_iframe(parent_pipeline_id, browsing_context_id); 131+ let is_embedded_webview = frame_element 132+ .as_ref() 133+ .is_some_and(|iframe| iframe.is_embedded_webview()); 134 if let Some(frame_element) = frame_element { 135 frame_element.update_pipeline_id(new_pipeline_id, reason, cx); 136 } 137@@ -3078,6 +3128,7 @@ 138 // is no need to pass along existing opener information that 139 // will be discarded. 140 None, 141+ is_embedded_webview, 142 ); 143 } 144 } 145@@ -3354,6 +3405,157 @@ 146 } 147 } 148 149+ /// Handle an embedded webview event that should be dispatched on an iframe element. 150+ fn handle_embedded_webview_event( 151+ &self, 152+ parent_id: PipelineId, 153+ browsing_context_id: BrowsingContextId, 154+ event: EmbeddedWebViewEventType, 155+ can_gc: CanGc, 156+ ) { 157+ let iframe = self 158+ .documents 159+ .borrow() 160+ .find_iframe(parent_id, browsing_context_id); 161+ match iframe { 162+ Some(iframe) => iframe.dispatch_embedded_webview_event(event, can_gc), 163+ None => warn!( 164+ "Embedded webview event sent to closed pipeline {}.", 165+ parent_id 166+ ), 167+ } 168+ } 169+ 170+ /// Handle a servo error by dispatching servoerror events to all navigator.embedder instances. 171+ fn handle_dispatch_servo_error( 172+ &self, 173+ error_type: ServoErrorType, 174+ message: String, 175+ can_gc: CanGc, 176+ ) { 177+ // Dispatch servoerror event to windows that already have an Embedder created 178+ for (_, document) in self.documents.borrow().iter() { 179+ if let Some(embedder) = document.window().Navigator().get_embedder() { 180+ // Enter the realm of the embedder's global before dispatching JS events 181+ let _ac = enter_realm(&*embedder); 182+ embedder.dispatch_servo_error(&error_type, &message, can_gc); 183+ } 184+ } 185+ } 186+ 187+ /// Handle a pairing event by dispatching to all navigator.embedder.pairing instances. 188+ fn handle_dispatch_pairing_event( 189+ &self, 190+ event: constellation_traits::PairingEvent, 191+ can_gc: CanGc, 192+ ) { 193+ for (_, document) in self.documents.borrow().iter() { 194+ if let Some(embedder) = document.window().Navigator().get_embedder() { 195+ if let Some(pairing) = embedder.get_pairing() { 196+ let _ac = enter_realm(&*pairing); 197+ pairing.dispatch_pairing_event(&event, can_gc); 198+ } 199+ } 200+ } 201+ } 202+ 203+ /// Handle an incoming peer stream: create a local MessagePort and fire "peerstream" on Window. 204+ /// Only fires on documents whose URL matches the target_url specified by the sender. 205+ /// If preventDefault() is called on the event, deny the offer. 206+ fn handle_dispatch_peer_stream( 207+ &self, 208+ peer_id: String, 209+ remote_port_id_bytes: Vec<u8>, 210+ stream_id: String, 211+ from_peer: String, 212+ target_url: String, 213+ can_gc: CanGc, 214+ ) { 215+ log::debug!( 216+ "handle_dispatch_peer_stream: peer_id={peer_id}, stream_id={stream_id}, target_url={target_url}" 217+ ); 218+ let Ok(remote_port_id) = postcard::from_bytes::<MessagePortId>(&remote_port_id_bytes) 219+ else { 220+ log::warn!("Failed to deserialize remote port ID in DispatchPeerStream"); 221+ return; 222+ }; 223+ 224+ // Find the first document whose URL matches the target. 225+ let mut accepted = false; 226+ let mut responding_global = None; 227+ let doc_count = self.documents.borrow().iter().count(); 228+ log::debug!("Searching {doc_count} documents for URL match with {target_url}"); 229+ for (_, document) in self.documents.borrow().iter() { 230+ let window = document.window(); 231+ let global = window.upcast::<crate::dom::globalscope::GlobalScope>(); 232+ let doc_url = global.get_url(); 233+ log::debug!( 234+ "Document url={} vs target={} match={}", 235+ doc_url.as_str(), 236+ target_url, 237+ doc_url.as_str() == target_url 238+ ); 239+ if doc_url.as_str() != target_url { 240+ continue; 241+ } 242+ let _ac = enter_realm(window); 243+ 244+ log::debug!("URL matched! Creating local port and firing peerstream event"); 245+ 246+ // Create a new local port and set its entanglement to the remote port. 247+ let local_port = MessagePort::new(global, can_gc); 248+ global.track_message_port(&local_port, None); 249+ global.set_port_entanglement(*local_port.message_port_id(), remote_port_id); 250+ global.start_message_port(local_port.message_port_id(), can_gc); 251+ 252+ // Tell the constellation to set bidirectional entanglement. 253+ let _ = global.script_to_constellation_chan().send( 254+ ScriptToConstellationMessage::EntanglePorts( 255+ *local_port.message_port_id(), 256+ remote_port_id, 257+ ), 258+ ); 259+ 260+ // Fire the cancelable "peerstream" event on Window. 261+ let event = PeerStreamEvent::new( 262+ global, 263+ Atom::from("peerstream"), 264+ false, 265+ true, // cancelable 266+ &local_port, 267+ peer_id.clone(), 268+ can_gc, 269+ ); 270+ event 271+ .upcast::<Event>() 272+ .fire(window.upcast::<EventTarget>(), can_gc); 273+ 274+ log::debug!( 275+ "peerstream event fired, defaultPrevented={}", 276+ event.upcast::<Event>().DefaultPrevented() 277+ ); 278+ 279+ if !event.upcast::<Event>().DefaultPrevented() { 280+ accepted = true; 281+ } else { 282+ // Clean up the port — the offer was denied. 283+ local_port.Close(can_gc); 284+ } 285+ responding_global = Some(global.script_to_constellation_chan().clone()); 286+ break; 287+ } 288+ 289+ // Report back to the constellation. 290+ if let Some(chan) = responding_global { 291+ log::debug!("Sending PeerStreamResponse: accepted={accepted}"); 292+ let _ = chan.send(ScriptToConstellationMessage::PeerStreamResponse( 293+ stream_id, from_peer, accepted, 294+ )); 295+ } else { 296+ log::warn!("No document matched target_url {target_url} — peerstream event not fired"); 297+ } 298+ } 299+ 300 fn ask_constellation_for_top_level_info( 301 &self, 302 sender_webview_id: WebViewId, 303@@ -3468,7 +3670,13 @@ 304 self.senders.pipeline_to_embedder_sender.clone(), 305 self.senders.constellation_sender.clone(), 306 incomplete.pipeline_id, 307- incomplete.parent_info, 308+ // For embedded webviews, don't pass parent_info to Window so that 309+ // is_top_level() returns true (they are top-level browsing contexts). 310+ if incomplete.is_embedded_webview { 311+ None 312+ } else { 313+ incomplete.parent_info 314+ }, 315 incomplete.viewport_details, 316 origin.clone(), 317 final_url.clone(), 318@@ -3490,6 +3698,8 @@ 319 #[cfg(feature = "webgpu")] 320 self.gpu_id_hub.clone(), 321 incomplete.load_data.inherited_secure_context, 322+ incomplete.is_embedded_webview, 323+ incomplete.hide_focus, 324 incomplete.theme, 325 self.this.clone(), 326 ); 327@@ -3513,6 +3723,7 @@ 328 incomplete.webview_id, 329 incomplete.parent_info, 330 incomplete.opener, 331+ incomplete.is_embedded_webview, 332 ); 333 if window_proxy.parent().is_some() { 334 // https://html.spec.whatwg.org/multipage/#navigating-across-documents:delaying-load-events-mode-2 335@@ -4286,6 +4497,24 @@ 336 document.event_handler().handle_refresh_cursor(); 337 } 338 339+ /// Dispatch preferencechanged events to all Embedder instances in this script thread. 340+ fn dispatch_preference_changed_to_embedders( 341+ &self, 342+ changes: &[(String, PrefValue)], 343+ can_gc: CanGc, 344+ ) { 345+ // Dispatch preferencechanged event to windows that already have an Embedder created 346+ for (_, document) in self.documents.borrow().iter() { 347+ if let Some(embedder) = document.window().Navigator().get_embedder() { 348+ // Enter the realm of the embedder's global before dispatching JS events 349+ let _ac = enter_realm(&*embedder); 350+ for (name, value) in changes { 351+ embedder.dispatch_preference_changed(name, value, can_gc); 352+ } 353+ } 354+ } 355+ } 356+ 357 pub(crate) fn is_servo_privileged(url: ServoUrl) -> bool { 358 with_script_thread(|script_thread| script_thread.privileged_urls.contains(&url)) 359 }