--- original +++ modified @@ -35,15 +35,15 @@ use base::cross_process_instant::CrossProcessInstant; use base::generic_channel; use base::id::{ - BrowsingContextId, HistoryStateId, PipelineId, PipelineNamespace, ScriptEventLoopId, - TEST_WEBVIEW_ID, WebViewId, + BrowsingContextId, HistoryStateId, MessagePortId, PipelineId, PipelineNamespace, + ScriptEventLoopId, TEST_WEBVIEW_ID, WebViewId, }; use canvas_traits::webgl::WebGLPipeline; use chrono::{DateTime, Local}; use constellation_traits::{ - JsEvalResult, LoadData, LoadOrigin, NavigationHistoryBehavior, ScreenshotReadinessResponse, - ScriptToConstellationChan, ScriptToConstellationMessage, ScrollStateUpdate, - StructuredSerializedData, TraversalDirection, WindowSizeType, + EmbeddedWebViewEventType, JsEvalResult, LoadData, LoadOrigin, NavigationHistoryBehavior, + ScreenshotReadinessResponse, ScriptToConstellationChan, ScriptToConstellationMessage, + ScrollStateUpdate, StructuredSerializedData, TraversalDirection, WindowSizeType, }; use crossbeam_channel::unbounded; use data_url::mime::Mime; @@ -55,7 +55,7 @@ use embedder_traits::{ EmbedderControlId, EmbedderControlResponse, EmbedderMsg, FocusSequenceNumber, InputEventOutcome, JavaScriptEvaluationError, JavaScriptEvaluationId, MediaSessionActionType, - Theme, ViewportDetails, WebDriverScriptCommand, + ServoErrorType, Theme, ViewportDetails, WebDriverScriptCommand, }; use encoding_rs::Encoding; use fonts::{FontContext, SystemFontServiceProxy}; @@ -92,6 +92,7 @@ UpdatePipelineIdReason, }; use servo_arc::Arc as ServoArc; +use servo_config::pref_util::PrefValue; use servo_config::{opts, pref, prefs}; use servo_url::{ImmutableOrigin, MutableOrigin, OriginSnapshot, ServoUrl}; use storage_traits::StorageThreads; @@ -115,6 +116,8 @@ use crate::dom::bindings::codegen::Bindings::DocumentBinding::{ DocumentMethods, DocumentReadyState, }; +use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::MessagePortBinding::MessagePortMethods; use crate::dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorMethods; use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; use crate::dom::bindings::conversions::{ @@ -134,9 +137,13 @@ RenderingUpdateReason, }; use crate::dom::element::Element; +use crate::dom::event::Event; +use crate::dom::eventtarget::EventTarget; use crate::dom::globalscope::GlobalScope; use crate::dom::html::htmliframeelement::{HTMLIFrameElement, IframeContext}; +use crate::dom::messageport::MessagePort; use crate::dom::node::{Node, NodeTraits}; +use crate::dom::peerstreamevent::PeerStreamEvent; use crate::dom::servoparser::{ParserContext, ServoParser}; use crate::dom::types::DebuggerGlobalScope; #[cfg(feature = "webgpu")] @@ -1943,11 +1950,22 @@ self.handle_refresh_cursor(pipeline_id); }, ScriptThreadMessage::PreferencesUpdated(updates) => { - let mut current_preferences = prefs::get().clone(); - for (name, value) in updates { - current_preferences.set_value(&name, value); + // Only update core preferences (those without namespace separator) + // Embedder preferences are already stored in the embedder prefs registry + let core_updates: Vec<_> = updates + .iter() + .filter(|(name, _)| !name.contains('.')) + .collect(); + if !core_updates.is_empty() { + let mut current_preferences = prefs::get().clone(); + for (name, value) in core_updates { + current_preferences.set_value(name, value.clone()); + } + prefs::set(current_preferences); } - prefs::set(current_preferences); + + // Dispatch preferencechanged events to all Embedder instances + self.dispatch_preference_changed_to_embedders(&updates, CanGc::from_cx(cx)); }, ScriptThreadMessage::ForwardKeyboardScroll(pipeline_id, scroll) => { if let Some(document) = self.documents.borrow().find_document(pipeline_id) { @@ -1988,6 +2006,35 @@ ScriptThreadMessage::TriggerGarbageCollection => unsafe { JS_GC(*GlobalScope::get_cx(), GCReason::API); }, + ScriptThreadMessage::DispatchEmbeddedWebViewEvent { + target, + parent, + event, + } => { + self.handle_embedded_webview_event(parent, target, event, CanGc::from_cx(cx)); + }, + ScriptThreadMessage::DispatchServoError(error_type, message) => { + self.handle_dispatch_servo_error(error_type, message, CanGc::from_cx(cx)); + }, + ScriptThreadMessage::DispatchPairingEvent(event) => { + self.handle_dispatch_pairing_event(event, CanGc::from_cx(cx)); + }, + ScriptThreadMessage::DispatchPeerStream( + peer_id, + remote_port_id_bytes, + stream_id, + from_peer, + target_url, + ) => { + self.handle_dispatch_peer_stream( + peer_id, + remote_port_id_bytes, + stream_id, + from_peer, + target_url, + CanGc::from_cx(cx), + ); + }, } } @@ -3059,6 +3106,9 @@ .documents .borrow() .find_iframe(parent_pipeline_id, browsing_context_id); + let is_embedded_webview = frame_element + .as_ref() + .is_some_and(|iframe| iframe.is_embedded_webview()); if let Some(frame_element) = frame_element { frame_element.update_pipeline_id(new_pipeline_id, reason, cx); } @@ -3078,6 +3128,7 @@ // is no need to pass along existing opener information that // will be discarded. None, + is_embedded_webview, ); } } @@ -3354,6 +3405,157 @@ } } + /// Handle an embedded webview event that should be dispatched on an iframe element. + fn handle_embedded_webview_event( + &self, + parent_id: PipelineId, + browsing_context_id: BrowsingContextId, + event: EmbeddedWebViewEventType, + can_gc: CanGc, + ) { + let iframe = self + .documents + .borrow() + .find_iframe(parent_id, browsing_context_id); + match iframe { + Some(iframe) => iframe.dispatch_embedded_webview_event(event, can_gc), + None => warn!( + "Embedded webview event sent to closed pipeline {}.", + parent_id + ), + } + } + + /// Handle a servo error by dispatching servoerror events to all navigator.embedder instances. + fn handle_dispatch_servo_error( + &self, + error_type: ServoErrorType, + message: String, + can_gc: CanGc, + ) { + // Dispatch servoerror event to windows that already have an Embedder created + for (_, document) in self.documents.borrow().iter() { + if let Some(embedder) = document.window().Navigator().get_embedder() { + // Enter the realm of the embedder's global before dispatching JS events + let _ac = enter_realm(&*embedder); + embedder.dispatch_servo_error(&error_type, &message, can_gc); + } + } + } + + /// Handle a pairing event by dispatching to all navigator.embedder.pairing instances. + fn handle_dispatch_pairing_event( + &self, + event: constellation_traits::PairingEvent, + can_gc: CanGc, + ) { + for (_, document) in self.documents.borrow().iter() { + if let Some(embedder) = document.window().Navigator().get_embedder() { + if let Some(pairing) = embedder.get_pairing() { + let _ac = enter_realm(&*pairing); + pairing.dispatch_pairing_event(&event, can_gc); + } + } + } + } + + /// Handle an incoming peer stream: create a local MessagePort and fire "peerstream" on Window. + /// Only fires on documents whose URL matches the target_url specified by the sender. + /// If preventDefault() is called on the event, deny the offer. + fn handle_dispatch_peer_stream( + &self, + peer_id: String, + remote_port_id_bytes: Vec, + stream_id: String, + from_peer: String, + target_url: String, + can_gc: CanGc, + ) { + log::debug!( + "handle_dispatch_peer_stream: peer_id={peer_id}, stream_id={stream_id}, target_url={target_url}" + ); + let Ok(remote_port_id) = postcard::from_bytes::(&remote_port_id_bytes) + else { + log::warn!("Failed to deserialize remote port ID in DispatchPeerStream"); + return; + }; + + // Find the first document whose URL matches the target. + let mut accepted = false; + let mut responding_global = None; + let doc_count = self.documents.borrow().iter().count(); + log::debug!("Searching {doc_count} documents for URL match with {target_url}"); + for (_, document) in self.documents.borrow().iter() { + let window = document.window(); + let global = window.upcast::(); + let doc_url = global.get_url(); + log::debug!( + "Document url={} vs target={} match={}", + doc_url.as_str(), + target_url, + doc_url.as_str() == target_url + ); + if doc_url.as_str() != target_url { + continue; + } + let _ac = enter_realm(window); + + log::debug!("URL matched! Creating local port and firing peerstream event"); + + // Create a new local port and set its entanglement to the remote port. + let local_port = MessagePort::new(global, can_gc); + global.track_message_port(&local_port, None); + global.set_port_entanglement(*local_port.message_port_id(), remote_port_id); + global.start_message_port(local_port.message_port_id(), can_gc); + + // Tell the constellation to set bidirectional entanglement. + let _ = global.script_to_constellation_chan().send( + ScriptToConstellationMessage::EntanglePorts( + *local_port.message_port_id(), + remote_port_id, + ), + ); + + // Fire the cancelable "peerstream" event on Window. + let event = PeerStreamEvent::new( + global, + Atom::from("peerstream"), + false, + true, // cancelable + &local_port, + peer_id.clone(), + can_gc, + ); + event + .upcast::() + .fire(window.upcast::(), can_gc); + + log::debug!( + "peerstream event fired, defaultPrevented={}", + event.upcast::().DefaultPrevented() + ); + + if !event.upcast::().DefaultPrevented() { + accepted = true; + } else { + // Clean up the port — the offer was denied. + local_port.Close(can_gc); + } + responding_global = Some(global.script_to_constellation_chan().clone()); + break; + } + + // Report back to the constellation. + if let Some(chan) = responding_global { + log::debug!("Sending PeerStreamResponse: accepted={accepted}"); + let _ = chan.send(ScriptToConstellationMessage::PeerStreamResponse( + stream_id, from_peer, accepted, + )); + } else { + log::warn!("No document matched target_url {target_url} — peerstream event not fired"); + } + } + fn ask_constellation_for_top_level_info( &self, sender_webview_id: WebViewId, @@ -3468,7 +3670,13 @@ self.senders.pipeline_to_embedder_sender.clone(), self.senders.constellation_sender.clone(), incomplete.pipeline_id, - incomplete.parent_info, + // For embedded webviews, don't pass parent_info to Window so that + // is_top_level() returns true (they are top-level browsing contexts). + if incomplete.is_embedded_webview { + None + } else { + incomplete.parent_info + }, incomplete.viewport_details, origin.clone(), final_url.clone(), @@ -3490,6 +3698,8 @@ #[cfg(feature = "webgpu")] self.gpu_id_hub.clone(), incomplete.load_data.inherited_secure_context, + incomplete.is_embedded_webview, + incomplete.hide_focus, incomplete.theme, self.this.clone(), ); @@ -3513,6 +3723,7 @@ incomplete.webview_id, incomplete.parent_info, incomplete.opener, + incomplete.is_embedded_webview, ); if window_proxy.parent().is_some() { // https://html.spec.whatwg.org/multipage/#navigating-across-documents:delaying-load-events-mode-2 @@ -4286,6 +4497,24 @@ document.event_handler().handle_refresh_cursor(); } + /// Dispatch preferencechanged events to all Embedder instances in this script thread. + fn dispatch_preference_changed_to_embedders( + &self, + changes: &[(String, PrefValue)], + can_gc: CanGc, + ) { + // Dispatch preferencechanged event to windows that already have an Embedder created + for (_, document) in self.documents.borrow().iter() { + if let Some(embedder) = document.window().Navigator().get_embedder() { + // Enter the realm of the embedder's global before dispatching JS events + let _ac = enter_realm(&*embedder); + for (name, value) in changes { + embedder.dispatch_preference_changed(name, value, can_gc); + } + } + } + } + pub(crate) fn is_servo_privileged(url: ServoUrl) -> bool { with_script_thread(|script_thread| script_thread.privileged_urls.contains(&url)) }