···2727 };
2828 use euclid::Size2D;
2929 use euclid::default::Size2D as UntypedSize2D;
3030-@@ -510,6 +511,19 @@
3030+@@ -510,6 +511,22 @@
31313232 /// Whether accessibility trees are being built and sent to the underlying platform.
3333 pub(crate) accessibility_active: bool,
···4444+ /// Set of script event loop IDs that have registered embedder error listeners.
4545+ /// Only these event loops will receive DispatchServoError messages.
4646+ embedder_error_listeners: FxHashSet<ScriptEventLoopId>,
4747++
4848++ /// The P2P pairing service.
4949++ pairing: crate::pairing::PairingService,
4750 }
48514952 /// State needed to construct a constellation.
5050-@@ -729,6 +743,9 @@
5353+@@ -729,6 +746,10 @@
5154 screenshot_readiness_requests: Vec::new(),
5255 user_contents_for_manager_id: Default::default(),
5356 accessibility_active: false,
5457+ embedded_webview_to_iframe: FxHashMap::default(),
5558+ active_ime_webview: None,
5659+ embedder_error_listeners: Default::default(),
6060++ pairing: crate::pairing::PairingService::new(),
5761 };
58625963 constellation.run();
6060-@@ -754,6 +771,18 @@
6464+@@ -754,6 +775,18 @@
6165 fn clean_up_finished_script_event_loops(&mut self) {
6266 self.event_loop_join_handles
6367 .retain(|join_handle| !join_handle.is_finished());
···7680 self.event_loops
7781 .retain(|event_loop| event_loop.upgrade().is_some());
7882 }
7979-@@ -1045,6 +1074,11 @@
8383+@@ -1045,6 +1078,11 @@
8084 .get(&webview_id)
8185 .and_then(|webview| webview.user_content_manager_id);
8286···8892 let new_pipeline_info = NewPipelineInfo {
8993 parent_info: parent_pipeline_id,
9094 new_pipeline_id,
9191-@@ -1055,6 +1089,13 @@
9595+@@ -1055,6 +1093,13 @@
9296 viewport_details: initial_viewport_details,
9397 user_content_manager_id,
9498 theme,
···102106 };
103107 let pipeline = match Pipeline::spawn(new_pipeline_info, event_loop, self, throttled) {
104108 Ok(pipeline) => pipeline,
105105-@@ -1534,11 +1575,7 @@
109109+@@ -1229,6 +1274,7 @@
110110+ BackgroundHangMonitor(HangMonitorAlert),
111111+ Embedder(EmbedderToConstellationMessage),
112112+ FromSWManager(SWManagerMsg),
113113++ PairingEvent(constellation_traits::PairingEvent),
114114+ RemoveProcess(usize),
115115+ }
116116+ // Get one incoming request.
117117+@@ -1249,6 +1295,15 @@
118118+ sel.recv(&self.embedder_to_constellation_receiver);
119119+ sel.recv(&self.swmanager_receiver);
120120+121121++ // Conditionally register the pairing event receiver (index 5 if present).
122122++ let has_pairing_receiver = if let Some(receiver) = self.pairing.event_receiver() {
123123++ sel.recv(receiver);
124124++ true
125125++ } else {
126126++ false
127127++ };
128128++ let process_base_index = if has_pairing_receiver { 6 } else { 5 };
129129++
130130+ self.process_manager.register(&mut sel);
131131+132132+ let request = {
133133+@@ -1277,9 +1332,13 @@
134134+ .recv(&self.swmanager_receiver)
135135+ .expect("Unexpected SW channel panic in constellation")
136136+ .map(Request::FromSWManager),
137137++ 5 if has_pairing_receiver => Ok(Request::PairingEvent(
138138++ oper.recv(self.pairing.event_receiver().expect("checked above"))
139139++ .expect("Unexpected pairing event channel panic in constellation"),
140140++ )),
141141+ _ => {
142142+ // This can only be a error reading on a closed lifeline receiver.
143143+- let process_index = index - 5;
144144++ let process_index = index - process_base_index;
145145+ let _ = oper.recv(self.process_manager.receiver_at(process_index));
146146+ Ok(Request::RemoveProcess(process_index))
147147+ },
148148+@@ -1305,6 +1364,9 @@
149149+ Request::FromSWManager(message) => {
150150+ self.handle_request_from_swmanager(message);
151151+ },
152152++ Request::PairingEvent(event) => {
153153++ self.handle_pairing_event(event);
154154++ },
155155+ Request::RemoveProcess(index) => self.process_manager.remove(index),
156156+ }
157157+ }
158158+@@ -1534,11 +1596,7 @@
106159 }
107160 },
108161 EmbedderToConstellationMessage::PreferencesUpdated(updates) => {
···115168 let _ = event_loop.send(ScriptThreadMessage::PreferencesUpdated(
116169 updates
117170 .iter()
118118-@@ -1565,6 +1602,18 @@
171171+@@ -1565,6 +1623,18 @@
119172 EmbedderToConstellationMessage::SetAccessibilityActive(active) => {
120173 self.set_accessibility_active(active);
121174 },
···134187 }
135188 }
136189137137-@@ -1788,6 +1837,12 @@
190190+@@ -1788,6 +1858,12 @@
138191 self.broadcast_channels
139192 .remove_broadcast_channel_router(router_id);
140193 },
···147200 ScriptToConstellationMessage::ScheduleBroadcast(router_id, message) => {
148201 if self
149202 .check_origin_against_pipeline(&source_pipeline_id, &message.origin)
150150-@@ -1818,6 +1873,12 @@
203203+@@ -1818,6 +1894,12 @@
151204 ScriptToConstellationMessage::CreateAuxiliaryWebView(load_info) => {
152205 self.handle_script_new_auxiliary(load_info);
153206 },
···160213 ScriptToConstellationMessage::ChangeRunningAnimationsState(animation_state) => {
161214 self.handle_change_running_animations_state(source_pipeline_id, animation_state)
162215 },
163163-@@ -1984,6 +2045,23 @@
216216+@@ -1984,6 +2066,23 @@
164217 new_value,
165218 );
166219 },
···184237 ScriptToConstellationMessage::MediaSessionEvent(pipeline_id, event) => {
185238 // Unlikely at this point, but we may receive events coming from
186239 // different media sessions, so we set the active media session based
187187-@@ -2057,6 +2135,129 @@
240240+@@ -2057,9 +2156,155 @@
188241 let _ = event_loop.send(ScriptThreadMessage::TriggerGarbageCollection);
189242 }
190243 },
···311364+ self.active_ime_webview = None;
312365+ }
313366+ },
367367++ ScriptToConstellationMessage::PairingStart(callback) => {
368368++ self.pairing.start(callback);
369369++ },
370370++ ScriptToConstellationMessage::PairingStop(callback) => {
371371++ self.pairing.stop(callback);
372372++ },
373373++ ScriptToConstellationMessage::PairingGetLocal(callback) => {
374374++ self.pairing.get_local(callback);
375375++ },
376376++ ScriptToConstellationMessage::PairingGetPeers(callback) => {
377377++ self.pairing.get_peers(callback);
378378++ },
379379++ ScriptToConstellationMessage::PairingSetName(name, callback) => {
380380++ self.pairing.set_name(name, callback);
381381++ },
314382 }
315383 }
316384317317-@@ -3174,6 +3375,13 @@
385385++ fn handle_pairing_event(&self, event: constellation_traits::PairingEvent) {
386386++ for event_loop in self.event_loops() {
387387++ if self.embedder_error_listeners.contains(&event_loop.id()) {
388388++ let _ = event_loop.send(ScriptThreadMessage::DispatchPairingEvent(event.clone()));
389389++ }
390390++ }
391391++ }
392392++
393393+ /// Check the origin of a message against that of the pipeline it came from.
394394+ /// Note: this is still limited as a security check,
395395+ /// see <https://github.com/servo/servo/issues/11722>
396396+@@ -3174,6 +3419,13 @@
318397 /// <https://html.spec.whatwg.org/multipage/#destroy-a-top-level-traversable>
319398 fn handle_close_top_level_browsing_context(&mut self, webview_id: WebViewId) {
320399 debug!("{webview_id}: Closing");
···328407 let browsing_context_id = BrowsingContextId::from(webview_id);
329408 // Step 5. Remove traversable from the user agent's top-level traversable set.
330409 let browsing_context =
331331-@@ -3450,8 +3658,27 @@
410410+@@ -3450,8 +3702,27 @@
332411 opener_webview_id,
333412 opener_pipeline_id,
334413 response_sender,
···356435 let Some((webview_id_sender, webview_id_receiver)) = generic_channel::channel() else {
357436 warn!("Failed to create channel");
358437 let _ = response_sender.send(None);
359359-@@ -3550,6 +3777,361 @@
438438+@@ -3550,6 +3821,361 @@
360439 });
361440 }
362441···718797 #[servo_tracing::instrument(skip_all)]
719798 fn handle_refresh_cursor(&self, pipeline_id: PipelineId) {
720799 let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
721721-@@ -4676,7 +5258,7 @@
800800+@@ -4676,7 +5302,7 @@
722801 }
723802724803 #[servo_tracing::instrument(skip_all)]
···727806 // Send a flat projection of the history to embedder.
728807 // The final vector is a concatenation of the URLs of the past
729808 // entries, the current entry and the future entries.
730730-@@ -4779,9 +5361,23 @@
809809+@@ -4779,9 +5405,23 @@
731810 );
732811 self.embedder_proxy.send(EmbedderMsg::HistoryChanged(
733812 webview_id,
+10
patches/components/constellation/lib.rs.patch
···11+--- original
22++++ modified
33+@@ -13,6 +13,7 @@
44+ mod constellation_webview;
55+ mod event_loop;
66+ mod logging;
77++mod pairing;
88+ mod pipeline;
99+ mod process_manager;
1010+ mod sandboxing;
+259
patches/components/constellation/pairing.rs.patch
···11+--- original
22++++ modified
33+@@ -0,0 +1,256 @@
44++// SPDX-License-Identifier: AGPL-3.0-or-later
55++
66++//! P2P pairing service integration with the constellation.
77++//!
88++//! This module encapsulates the lifecycle of the `PairingManager` from `beaver_p2p`
99++//! and bridges its async events into the constellation's crossbeam-based event loop.
1010++//! The endpoint's secret key and display name are persisted to the config directory.
1111++
1212++use std::path::PathBuf;
1313++use std::sync::Arc;
1414++
1515++use base::generic_channel::GenericCallback;
1616++use beaver_p2p::{EndpointStatus, PairingManager, PeerEvent};
1717++use constellation_traits::{LocalPeerInfo, PairingEvent, PeerInfo, PeerStatus};
1818++use crossbeam_channel;
1919++use iroh::address_lookup::DiscoveryEvent;
2020++use log::{info, warn};
2121++use tokio::sync::Mutex;
2222++
2323++pub(crate) struct PairingService {
2424++ manager: Arc<Mutex<Option<PairingManager>>>,
2525++ local_info: Arc<Mutex<Option<LocalPeerInfo>>>,
2626++ event_receiver: Option<crossbeam_channel::Receiver<PairingEvent>>,
2727++}
2828++
2929++impl PairingService {
3030++ pub(crate) fn new() -> Self {
3131++ Self {
3232++ manager: Default::default(),
3333++ local_info: Default::default(),
3434++ event_receiver: None,
3535++ }
3636++ }
3737++
3838++ /// Returns the event receiver for use in the constellation's Select loop.
3939++ pub(crate) fn event_receiver(&self) -> Option<&crossbeam_channel::Receiver<PairingEvent>> {
4040++ self.event_receiver.as_ref()
4141++ }
4242++
4343++ /// Start the pairing service. Creates the PairingManager on the tokio runtime
4444++ /// and sets up a bridge task to forward PeerEvents to a crossbeam channel.
4545++ /// The secret key and display name are persisted to the config directory.
4646++ pub(crate) fn start(&mut self, callback: GenericCallback<Result<(), String>>) {
4747++ let manager = self.manager.clone();
4848++ let local_info = self.local_info.clone();
4949++ let (cb_sender, cb_receiver) = crossbeam_channel::unbounded();
5050++ self.event_receiver = Some(cb_receiver);
5151++
5252++ net::async_runtime::spawn_task(async move {
5353++ let mut guard = manager.lock().await;
5454++ if guard.is_some() {
5555++ let _ = callback.send(Ok(()));
5656++ return;
5757++ }
5858++
5959++ let config_dir = servo_config::opts::get().config_dir.clone();
6060++
6161++ // Load or generate the persistent secret key.
6262++ let key = load_or_create_key(config_dir.as_ref()).await;
6363++
6464++ // Load or generate the persistent display name.
6565++ let name = load_or_create_name(config_dir.as_ref()).await;
6666++ info!("Pairing service starting as \"{name}\"");
6767++
6868++ // Store local endpoint info (id is derived from the secret key).
6969++ *local_info.lock().await = Some(LocalPeerInfo {
7070++ id: key.public().to_string(),
7171++ name: name.clone(),
7272++ });
7373++
7474++ let (event_sender, event_receiver) = std::sync::mpsc::channel();
7575++ let new_manager = PairingManager::create(event_sender, &name, key).await;
7676++ *guard = Some(new_manager);
7777++
7878++ // Bridge: forward PeerEvents to the crossbeam channel as PairingEvents.
7979++ // This loop exits naturally when PairingManager::stop() drops the sender,
8080++ // causing recv() to return Err.
8181++ tokio::task::spawn_blocking(move || {
8282++ while let Ok(event) = event_receiver.recv() {
8383++ if let Some(pairing_event) = to_pairing_event(&event) {
8484++ if cb_sender.send(pairing_event).is_err() {
8585++ break;
8686++ }
8787++ }
8888++ }
8989++ });
9090++
9191++ let _ = callback.send(Ok(()));
9292++ });
9393++ }
9494++
9595++ /// Stop the pairing service. Clears the event receiver first (so the constellation
9696++ /// stops selecting on it), then stops the PairingManager (which drops the mpsc sender,
9797++ /// causing the bridge task to exit naturally).
9898++ pub(crate) fn stop(&mut self, callback: GenericCallback<Result<(), String>>) {
9999++ // Remove the receiver first so the constellation's Select loop no longer polls it.
100100++ self.event_receiver = None;
101101++
102102++ let manager = self.manager.clone();
103103++ let local_info = self.local_info.clone();
104104++ net::async_runtime::spawn_task(async move {
105105++ *local_info.lock().await = None;
106106++ let mut guard = manager.lock().await;
107107++ if let Some(mut mgr) = guard.take() {
108108++ mgr.stop().await;
109109++ }
110110++ let _ = callback.send(Ok(()));
111111++ });
112112++ }
113113++
114114++ /// Get the local endpoint info. Responds via callback since the info
115115++ /// may still be loading asynchronously on the tokio runtime.
116116++ pub(crate) fn get_local(&self, callback: GenericCallback<Result<LocalPeerInfo, String>>) {
117117++ let local_info = self.local_info.clone();
118118++ net::async_runtime::spawn_task(async move {
119119++ let guard = local_info.lock().await;
120120++ let result = match guard.as_ref() {
121121++ Some(info) => Ok(info.clone()),
122122++ None => Err("Pairing service not started".to_owned()),
123123++ };
124124++ let _ = callback.send(result);
125125++ });
126126++ }
127127++
128128++ /// Get the list of known peers from the PairingManager.
129129++ pub(crate) fn get_peers(&self, callback: GenericCallback<Result<Vec<PeerInfo>, String>>) {
130130++ let manager = self.manager.clone();
131131++ net::async_runtime::spawn_task(async move {
132132++ let guard = manager.lock().await;
133133++ let result = match guard.as_ref() {
134134++ Some(mgr) => {
135135++ let peers = mgr
136136++ .peers()
137137++ .await
138138++ .into_iter()
139139++ .map(|ep| PeerInfo {
140140++ id: ep.id.to_string(),
141141++ name: ep.name,
142142++ status: match ep.status {
143143++ EndpointStatus::Discovered => PeerStatus::Discovered,
144144++ EndpointStatus::PairedConnected => PeerStatus::PairedConnected,
145145++ EndpointStatus::PairedDisconnected => {
146146++ PeerStatus::PairedDisconnected
147147++ },
148148++ },
149149++ })
150150++ .collect();
151151++ Ok(peers)
152152++ },
153153++ None => Err("Pairing service not started".to_owned()),
154154++ };
155155++ let _ = callback.send(result);
156156++ });
157157++ }
158158++
159159++ /// Update the persisted display name. The caller should stop and restart
160160++ /// the service for the new name to take effect on mDNS.
161161++ pub(crate) fn set_name(&self, name: String, callback: GenericCallback<Result<(), String>>) {
162162++ let local_info = self.local_info.clone();
163163++ net::async_runtime::spawn_task(async move {
164164++ let config_dir = servo_config::opts::get().config_dir.clone();
165165++ if let Some(dir) = config_dir.as_ref() {
166166++ let name_path = dir.join("pairing-name.txt");
167167++ if let Err(e) = tokio::fs::create_dir_all(dir).await {
168168++ let _ = callback.send(Err(format!("Failed to create config dir: {e}")));
169169++ return;
170170++ }
171171++ if let Err(e) = tokio::fs::write(&name_path, &name).await {
172172++ let _ = callback.send(Err(format!("Failed to write name: {e}")));
173173++ return;
174174++ }
175175++ }
176176++ // Update the cached local info if the service is running.
177177++ if let Some(info) = local_info.lock().await.as_mut() {
178178++ info.name = name;
179179++ }
180180++ let _ = callback.send(Ok(()));
181181++ });
182182++ }
183183++}
184184++
185185++/// Load the secret key from the config directory, or generate and persist a new one.
186186++/// Uses `iroh-persist` which stores keys in OpenSSH PEM format.
187187++async fn load_or_create_key(config_dir: Option<&PathBuf>) -> iroh::SecretKey {
188188++ let mut retriever = iroh_persist::KeyRetriever::new("beaver");
189189++ if let Some(dir) = config_dir {
190190++ retriever = retriever.persist_at_some(dir.join("iroh-secret-key.pem"));
191191++ } else {
192192++ retriever = retriever.persist(true);
193193++ }
194194++ retriever.lenient().get().await
195195++}
196196++
197197++/// Load the display name from the config directory, or generate and persist a new one.
198198++/// The name is stored as a plain text file `pairing-name.txt`.
199199++async fn load_or_create_name(config_dir: Option<&PathBuf>) -> String {
200200++ if let Some(dir) = config_dir {
201201++ let name_path = dir.join("pairing-name.txt");
202202++ if let Ok(name) = tokio::fs::read_to_string(&name_path).await {
203203++ let name = name.trim().to_owned();
204204++ if !name.is_empty() {
205205++ return name;
206206++ }
207207++ }
208208++
209209++ let name = generate_name();
210210++
211211++ // Ensure directory exists and write the name.
212212++ if let Err(e) = tokio::fs::create_dir_all(dir).await {
213213++ warn!("Failed to create config dir for pairing name: {e}");
214214++ } else if let Err(e) = tokio::fs::write(&name_path, &name).await {
215215++ warn!("Failed to persist pairing name: {e}");
216216++ }
217217++
218218++ name
219219++ } else {
220220++ generate_name()
221221++ }
222222++}
223223++
224224++/// Generate a memorable display name using petname.
225225++fn generate_name() -> String {
226226++ petname::petname(3, "-").unwrap_or_else(|| format!("beaver-{}", rand::random::<u32>()))
227227++}
228228++
229229++/// Convert a beaver_p2p PeerEvent to a serializable PairingEvent.
230230++/// Returns None for events that don't map (e.g. Message).
231231++fn to_pairing_event(event: &PeerEvent) -> Option<PairingEvent> {
232232++ match event {
233233++ PeerEvent::Discovery(DiscoveryEvent::Discovered { endpoint_info, .. }) => {
234234++ let name = endpoint_info
235235++ .data
236236++ .user_data()
237237++ .map(|d| d.as_ref().to_owned())
238238++ .unwrap_or_else(|| "<unknown>".to_owned());
239239++ Some(PairingEvent::PeerDiscovered {
240240++ id: endpoint_info.endpoint_id.to_string(),
241241++ name,
242242++ })
243243++ },
244244++ PeerEvent::Discovery(DiscoveryEvent::Expired { endpoint_id }) => {
245245++ Some(PairingEvent::PeerExpired {
246246++ id: endpoint_id.to_string(),
247247++ })
248248++ },
249249++ PeerEvent::PairingRequest(id) => Some(PairingEvent::PairingRequest { id: id.to_string() }),
250250++ PeerEvent::PairingAccepted(id) => {
251251++ Some(PairingEvent::PairingAccepted { id: id.to_string() })
252252++ },
253253++ PeerEvent::PairingRejected(id) => {
254254++ Some(PairingEvent::PairingRejected { id: id.to_string() })
255255++ },
256256++ PeerEvent::PairingFailed(id) => Some(PairingEvent::PairingFailed { id: id.to_string() }),
257257++ PeerEvent::Message(..) => None,
258258++ }
259259++}
···1616 pub(crate) mod keyboardevent;
1717 pub(crate) mod location;
1818 pub(crate) mod media;
1919-@@ -349,6 +351,8 @@
1919+@@ -349,6 +351,9 @@
2020 pub(crate) mod pagetransitionevent;
2121 pub(crate) mod paintsize;
2222 pub(crate) mod paintworkletglobalscope;
2323+pub(crate) mod pairing;
2424++pub(crate) mod pairingpeerevent;
2425+pub(crate) mod peer;
2526 pub(crate) mod performance;
2627 pub(crate) use self::performance::*;
···119119 /// Mark a new document as active
120120 ActivateDocument,
121121 /// Set the document state for a pipeline (used by screenshot / reftests)
122122-@@ -726,6 +775,44 @@
122122+@@ -726,6 +775,54 @@
123123 RespondToScreenshotReadinessRequest(ScreenshotReadinessResponse),
124124 /// Request the constellation to force garbage collection in all `ScriptThread`'s.
125125 TriggerGarbageCollection,
···161161+ EmbeddedWebViewControlResponse(EmbedderControlId, EmbedderControlResponse),
162162+ /// Set page zoom for an embedded webview.
163163+ EmbeddedWebViewSetPageZoom(WebViewId, f32),
164164++ /// Request the constellation to start the P2P pairing service.
165165++ PairingStart(GenericCallback<Result<(), String>>),
166166++ /// Request the constellation to stop the P2P pairing service.
167167++ PairingStop(GenericCallback<Result<(), String>>),
168168++ /// Request the local endpoint info from the P2P pairing service.
169169++ PairingGetLocal(GenericCallback<Result<super::LocalPeerInfo, String>>),
170170++ /// Request the list of known peers from the P2P pairing service.
171171++ PairingGetPeers(GenericCallback<Result<Vec<super::PeerInfo>, String>>),
172172++ /// Update the display name of the local P2P endpoint and restart the service.
173173++ PairingSetName(String, GenericCallback<Result<(), String>>),
164174 }
165175166176 impl fmt::Debug for ScriptToConstellationMessage {
···2121 };
2222 pub use from_script_message::*;
2323 use malloc_size_of_derive::MallocSizeOf;
2424-@@ -36,9 +37,70 @@
2424+@@ -36,9 +37,138 @@
2525 use servo_url::{ImmutableOrigin, ServoUrl};
2626 pub use structured_data::*;
2727 use strum::IntoStaticStr;
···7979+ NotificationShow(Notification),
8080+}
8181+
8282++/// Information about the local P2P endpoint.
8383++#[derive(Clone, Debug, Deserialize, Serialize)]
8484++pub struct LocalPeerInfo {
8585++ /// The endpoint ID as a string.
8686++ pub id: String,
8787++ /// The display name.
8888++ pub name: String,
8989++}
9090++
9191++/// Information about a remote P2P peer.
9292++#[derive(Clone, Debug, Deserialize, Serialize)]
9393++pub struct PeerInfo {
9494++ /// The endpoint ID as a string.
9595++ pub id: String,
9696++ /// The display name.
9797++ pub name: String,
9898++ /// The peer's connection/pairing status.
9999++ pub status: PeerStatus,
100100++}
101101++
102102++/// The status of a remote peer.
103103++#[derive(Clone, Debug, Deserialize, Serialize)]
104104++pub enum PeerStatus {
105105++ /// Discovered but not paired.
106106++ Discovered,
107107++ /// Paired and currently connected.
108108++ PairedConnected,
109109++ /// Paired but currently disconnected.
110110++ PairedDisconnected,
111111++}
112112++
113113++/// Events from the P2P pairing service, using simple serializable types.
114114++#[derive(Clone, Debug, Deserialize, Serialize)]
115115++pub enum PairingEvent {
116116++ /// A new unpaired peer was discovered on the local network.
117117++ PeerDiscovered {
118118++ /// The endpoint ID as a string.
119119++ id: String,
120120++ /// The display name of the peer.
121121++ name: String,
122122++ },
123123++ /// A previously discovered peer is no longer visible.
124124++ PeerExpired {
125125++ /// The endpoint ID as a string.
126126++ id: String,
127127++ },
128128++ /// A remote peer is requesting to pair with us.
129129++ PairingRequest {
130130++ /// The endpoint ID as a string.
131131++ id: String,
132132++ },
133133++ /// A pairing request we sent was accepted.
134134++ PairingAccepted {
135135++ /// The endpoint ID as a string.
136136++ id: String,
137137++ },
138138++ /// A pairing request we sent was rejected.
139139++ PairingRejected {
140140++ /// The endpoint ID as a string.
141141++ id: String,
142142++ },
143143++ /// A pairing handshake failed.
144144++ PairingFailed {
145145++ /// The endpoint ID as a string.
146146++ id: String,
147147++ },
148148++}
149149++
82150+/// The type of simple dialog to show.
83151+#[derive(Clone, Debug, Deserialize, Serialize)]
84152+pub enum SimpleDialogType {
···93161 /// Messages to the Constellation from the embedding layer, whether from `ServoRenderer` or
94162 /// from `libservo` itself.
95163 #[derive(IntoStaticStr)]
9696-@@ -118,6 +180,9 @@
164164+@@ -118,6 +248,9 @@
97165 UpdatePinchZoomInfos(PipelineId, PinchZoomInfos),
98166 /// Activate or deactivate accessibility features.
99167 SetAccessibilityActive(bool),
+3-1
patches/components/shared/script/lib.rs.patch
···3535 }
36363737 /// When a pipeline is closed, should its browsing context be discarded too?
3838-@@ -312,6 +320,19 @@
3838+@@ -312,6 +320,21 @@
3939 SetAccessibilityActive(bool),
4040 /// Force a garbage collection in this script thread.
4141 TriggerGarbageCollection,
···5252+ },
5353+ /// Dispatch a `servoerror` event to all `navigator.embedder` instances in this script thread.
5454+ DispatchServoError(ServoErrorType, String),
5555++ /// Dispatch a pairing event to all `navigator.embedder.pairing` instances in this script thread.
5656++ DispatchPairingEvent(constellation_traits::PairingEvent),
5557 }
56585759 impl fmt::Debug for ScriptThreadMessage {