···3131 };
3232 use crate::constellation_webview::ConstellationWebView;
3333 use crate::event_loop::EventLoop;
3434-+use crate::pairing;
3434++use crate::pairing::{P2pMessage, PairingService};
3535 use crate::pipeline::Pipeline;
3636 use crate::process_manager::ProcessManager;
3737 use crate::serviceworker::ServiceWorkerUnprivilegedContent;
3838-@@ -510,6 +512,22 @@
3838+@@ -211,6 +213,9 @@
3939+ /// While a completion failed, another global requested to complete the transfer.
4040+ /// We are still buffering messages, and awaiting the return of the buffer from the global who failed.
4141+ CompletionRequested(MessagePortRouterId, VecDeque<PortMessageTask>),
4242++ /// The port is managed by a remote P2P peer.
4343++ /// Messages routed to this port are serialized and sent over the P2P link.
4444++ Remote(String),
4545+ }
4646+4747+ #[derive(Debug)]
4848+@@ -510,6 +515,22 @@
39494050 /// Whether accessibility trees are being built and sent to the underlying platform.
4151 pub(crate) accessibility_active: bool,
···5464+ embedder_error_listeners: FxHashSet<ScriptEventLoopId>,
5565+
5666+ /// The P2P pairing service.
5757-+ pairing: pairing::PairingService,
6767++ pairing: PairingService,
5868 }
59696070 /// State needed to construct a constellation.
6161-@@ -729,6 +747,10 @@
7171+@@ -729,6 +750,10 @@
6272 screenshot_readiness_requests: Vec::new(),
6373 user_contents_for_manager_id: Default::default(),
6474 accessibility_active: false,
6575+ embedded_webview_to_iframe: FxHashMap::default(),
6676+ active_ime_webview: None,
6777+ embedder_error_listeners: Default::default(),
6868-+ pairing: pairing::PairingService::new(),
7878++ pairing: PairingService::new(),
6979 };
70807181 constellation.run();
7272-@@ -754,6 +776,18 @@
8282+@@ -754,6 +779,18 @@
7383 fn clean_up_finished_script_event_loops(&mut self) {
7484 self.event_loop_join_handles
7585 .retain(|join_handle| !join_handle.is_finished());
···8898 self.event_loops
8999 .retain(|event_loop| event_loop.upgrade().is_some());
90100 }
9191-@@ -1045,6 +1079,11 @@
101101+@@ -1045,6 +1082,11 @@
92102 .get(&webview_id)
93103 .and_then(|webview| webview.user_content_manager_id);
94104···100110 let new_pipeline_info = NewPipelineInfo {
101111 parent_info: parent_pipeline_id,
102112 new_pipeline_id,
103103-@@ -1055,6 +1094,13 @@
113113+@@ -1055,6 +1097,13 @@
104114 viewport_details: initial_viewport_details,
105115 user_content_manager_id,
106116 theme,
···114124 };
115125 let pipeline = match Pipeline::spawn(new_pipeline_info, event_loop, self, throttled) {
116126 Ok(pipeline) => pipeline,
117117-@@ -1229,6 +1275,7 @@
127127+@@ -1229,6 +1278,7 @@
118128 BackgroundHangMonitor(HangMonitorAlert),
119129 Embedder(EmbedderToConstellationMessage),
120130 FromSWManager(SWManagerMsg),
···122132 RemoveProcess(usize),
123133 }
124134 // Get one incoming request.
125125-@@ -1249,6 +1296,15 @@
135135+@@ -1249,6 +1299,15 @@
126136 sel.recv(&self.embedder_to_constellation_receiver);
127137 sel.recv(&self.swmanager_receiver);
128138···138148 self.process_manager.register(&mut sel);
139149140150 let request = {
141141-@@ -1277,9 +1333,13 @@
151151+@@ -1277,9 +1336,13 @@
142152 .recv(&self.swmanager_receiver)
143153 .expect("Unexpected SW channel panic in constellation")
144154 .map(Request::FromSWManager),
···153163 let _ = oper.recv(self.process_manager.receiver_at(process_index));
154164 Ok(Request::RemoveProcess(process_index))
155165 },
156156-@@ -1305,6 +1365,9 @@
166166+@@ -1305,6 +1368,9 @@
157167 Request::FromSWManager(message) => {
158168 self.handle_request_from_swmanager(message);
159169 },
···163173 Request::RemoveProcess(index) => self.process_manager.remove(index),
164174 }
165175 }
166166-@@ -1534,11 +1597,7 @@
176176+@@ -1534,11 +1600,7 @@
167177 }
168178 },
169179 EmbedderToConstellationMessage::PreferencesUpdated(updates) => {
···176186 let _ = event_loop.send(ScriptThreadMessage::PreferencesUpdated(
177187 updates
178188 .iter()
179179-@@ -1565,6 +1624,18 @@
189189+@@ -1565,6 +1627,18 @@
180190 EmbedderToConstellationMessage::SetAccessibilityActive(active) => {
181191 self.set_accessibility_active(active);
182192 },
···195205 }
196206 }
197207198198-@@ -1762,7 +1833,13 @@
208208+@@ -1762,7 +1836,13 @@
199209 return warn!("Attempt to add channel name from an unexpected origin.");
200210 }
201211 self.broadcast_channels
···210220 },
211221 ScriptToConstellationMessage::RemoveBroadcastChannelNameInRouter(
212222 router_id,
213213-@@ -1776,7 +1853,13 @@
223223+@@ -1776,7 +1856,13 @@
214224 return warn!("Attempt to remove channel name from an unexpected origin.");
215225 }
216226 self.broadcast_channels
···225235 },
226236 ScriptToConstellationMessage::RemoveBroadcastChannelRouter(router_id, origin) => {
227237 if self
228228-@@ -1788,6 +1871,12 @@
238238+@@ -1788,6 +1874,12 @@
229239 self.broadcast_channels
230240 .remove_broadcast_channel_router(router_id);
231241 },
···238248 ScriptToConstellationMessage::ScheduleBroadcast(router_id, message) => {
239249 if self
240250 .check_origin_against_pipeline(&source_pipeline_id, &message.origin)
241241-@@ -1797,8 +1886,15 @@
251251+@@ -1797,8 +1889,15 @@
242252 "Attempt to schedule broadcast from an origin not matching the origin of the msg."
243253 );
244254 }
···255265 },
256266 ScriptToConstellationMessage::PipelineExited => {
257267 self.handle_pipeline_exited(source_pipeline_id);
258258-@@ -1818,6 +1914,12 @@
268268+@@ -1818,6 +1917,12 @@
259269 ScriptToConstellationMessage::CreateAuxiliaryWebView(load_info) => {
260270 self.handle_script_new_auxiliary(load_info);
261271 },
···268278 ScriptToConstellationMessage::ChangeRunningAnimationsState(animation_state) => {
269279 self.handle_change_running_animations_state(source_pipeline_id, animation_state)
270280 },
271271-@@ -1984,6 +2086,29 @@
281281+@@ -1984,6 +2089,29 @@
272282 new_value,
273283 );
274284 },
···298308 ScriptToConstellationMessage::MediaSessionEvent(pipeline_id, event) => {
299309 // Unlikely at this point, but we may receive events coming from
300310 // different media sessions, so we set the active media session based
301301-@@ -2057,9 +2182,199 @@
311311+@@ -2057,7 +2185,333 @@
302312 let _ = event_loop.send(ScriptThreadMessage::TriggerGarbageCollection);
303313 }
304314 },
···449459+ ScriptToConstellationMessage::PairingRejectPairing(id, callback) => {
450460+ self.pairing.reject_pairing(&id, callback);
451461+ },
452452- }
453453- }
454454-455455-+ fn handle_pairing_event(&self, event: constellation_traits::PairingEvent) {
456456-+ // Handle incoming P2P messages.
457457-+ if let constellation_traits::PairingEvent::MessageReceived { ref from, ref data } = event {
458458-+ if let Some((_from, message)) = self.pairing.handle_incoming_message(from, data) {
459459-+ // Dispatch BroadcastChannelMessage locally.
460460-+ if let pairing::P2pMessage::BroadcastChannelMessage {
461461-+ ref origin,
462462-+ ref name,
463463-+ data: ref serialized,
464464-+ } = message
465465-+ {
466466-+ info!("Remote broadcast from {from}: {origin} / {name}");
467467-+ let Some(origin) = servo_url::ServoUrl::parse(origin).ok().map(|u| u.origin())
468468-+ else {
469469-+ warn!("Failed to parse origin: {origin}");
462462++ ScriptToConstellationMessage::CreatePeerStream(
463463++ peer_id,
464464++ local_port_id,
465465++ remote_port_id,
466466++ callback,
467467++ ) => {
468468++ // Register the virtual remote port that only exists in the constellation,
469469++ // not in any GlobalScope.
470470++ self.message_ports.insert(
471471++ remote_port_id,
472472++ MessagePortInfo {
473473++ state: TransferState::Remote(peer_id.clone()),
474474++ entangled_with: Some(local_port_id),
475475++ },
476476++ );
477477++
478478++ // Entangle the local port with the remote port in the constellation.
479479++ if let Some(info) = self.message_ports.get_mut(&local_port_id) {
480480++ info.entangled_with = Some(remote_port_id);
481481++ }
482482++ // Serialize port ID for the wire.
483483++ let port_id_bytes = match postcard::to_allocvec(&remote_port_id) {
484484++ Ok(bytes) => bytes,
485485++ Err(err) => {
486486++ let _ = callback.send(Err(format!("Failed to serialize port ID: {err}")));
470487+ return;
471471-+ };
472472-+ let msg = constellation_traits::BroadcastChannelMsg {
473473-+ origin,
474474-+ channel_name: name.clone(),
475475-+ data: constellation_traits::StructuredSerializedData {
476476-+ serialized: serialized.clone(),
477477-+ ..Default::default()
488488++ },
489489++ };
490490++ // Send port offer to the remote peer.
491491++ let stream_id = format!("ps-{}", rand::random::<u64>());
492492++ self.pairing.send_message(
493493++ &peer_id,
494494++ &P2pMessage::PortOffer {
495495++ stream_id,
496496++ port_id: port_id_bytes,
497497++ from_peer: peer_id.clone(),
498498++ },
499499++ );
500500++ // TODO: wait for PortOfferAccepted before resolving.
501501++ // For now, resolve immediately.
502502++ let _ = callback.send(Ok(()));
503503++ },
504504++ ScriptToConstellationMessage::PeerStreamResponse(stream_id, from_peer, accepted) => {
505505++ if accepted {
506506++ // Send PortOfferAccepted to the offering peer.
507507++ // For now, we don't have a stream_id → port mapping, so just send
508508++ // the acceptance with the stream_id. The offering side uses stream_id
509509++ // to correlate.
510510++ self.pairing.send_message(
511511++ &from_peer,
512512++ &P2pMessage::PortOfferAccepted {
513513++ stream_id,
514514++ port_id: vec![],
478515+ },
479479-+ };
480480-+ self.broadcast_channels.broadcast_to_all(&msg);
516516++ );
517517++ } else {
518518++ // Denied: send PortOfferDenied and clean up the Remote proxy.
519519++ self.pairing
520520++ .send_message(&from_peer, &P2pMessage::PortOfferDenied { stream_id });
521521++ }
522522++ },
523523++ }
524524++ }
525525++
526526++ fn handle_pairing_event(&mut self, event: constellation_traits::PairingEvent) {
527527++ if let constellation_traits::PairingEvent::MessageReceived { ref from, ref data } = event {
528528++ if let Some((from, message)) = self.pairing.handle_incoming_message(from, data) {
529529++ match message {
530530++ P2pMessage::BroadcastChannelMessage {
531531++ ref origin,
532532++ ref name,
533533++ data: ref serialized,
534534++ } => {
535535++ let Some(origin) =
536536++ servo_url::ServoUrl::parse(origin).ok().map(|u| u.origin())
537537++ else {
538538++ warn!("Failed to parse origin: {origin}");
539539++ return;
540540++ };
541541++ let msg = constellation_traits::BroadcastChannelMsg {
542542++ origin,
543543++ channel_name: name.clone(),
544544++ data: constellation_traits::StructuredSerializedData {
545545++ serialized: serialized.clone(),
546546++ ..Default::default()
547547++ },
548548++ };
549549++ self.broadcast_channels.broadcast_to_all(&msg);
550550++ },
551551++ P2pMessage::PortOffer {
552552++ ref stream_id,
553553++ ref port_id,
554554++ ..
555555++ } => {
556556++ let Ok(remote_port_id) =
557557++ postcard::from_bytes::<base::id::MessagePortId>(port_id)
558558++ else {
559559++ warn!("Failed to deserialize port_id in PortOffer");
560560++ return;
561561++ };
562562++ // Register the remote port in the constellation. Messages sent
563563++ // TO this port ID will be forwarded over P2P back to the offering peer.
564564++ self.message_ports.insert(
565565++ remote_port_id,
566566++ MessagePortInfo {
567567++ state: TransferState::Remote(from.clone()),
568568++ entangled_with: None,
569569++ },
570570++ );
571571++ // Notify script threads to create a MessagePort and fire peerstream event.
572572++ // The script thread will report back via PeerStreamResponse whether
573573++ // the event was accepted or denied.
574574++ let remote_port_id_bytes = port_id.clone();
575575++ for event_loop in self.event_loops() {
576576++ let _ = event_loop.send(ScriptThreadMessage::DispatchPeerStream(
577577++ from.clone(),
578578++ remote_port_id_bytes.clone(),
579579++ stream_id.clone(),
580580++ from.clone(),
581581++ ));
582582++ }
583583++ },
584584++ P2pMessage::PortOfferAccepted { .. } => {
585585++ // The createPeerStream promise is resolved immediately with the
586586++ // local port, so there's nothing to do here. Messages sent before
587587++ // the remote side accepts are buffered by the P2P layer.
588588++ },
589589++ P2pMessage::PortOfferDenied { ref stream_id } => {
590590++ warn!("Peer stream offer denied: {stream_id}");
591591++ // TODO: close/disentangle the local port and clean up the Remote entry
592592++ },
593593++ P2pMessage::PortMessage {
594594++ ref port_id,
595595++ ref task_data,
596596++ } => {
597597++ let Ok(remote_port_id) =
598598++ postcard::from_bytes::<base::id::MessagePortId>(port_id)
599599++ else {
600600++ warn!("Failed to deserialize port_id in PortMessage");
601601++ return;
602602++ };
603603++ let target_port_id = self
604604++ .message_ports
605605++ .get(&remote_port_id)
606606++ .and_then(|info| info.entangled_with)
607607++ .unwrap_or(remote_port_id);
608608++ match postcard::from_bytes::<constellation_traits::PortMessageTask>(
609609++ task_data,
610610++ ) {
611611++ Ok(task) => {
612612++ self.handle_reroute_messageport(target_port_id, task);
613613++ },
614614++ Err(err) => {
615615++ warn!("Failed to deserialize remote PortMessageTask: {err}");
616616++ },
617617++ }
618618++ },
619619++ P2pMessage::PortClose { ref port_id } => {
620620++ if let Ok(port_id) =
621621++ postcard::from_bytes::<base::id::MessagePortId>(port_id)
622622++ {
623623++ self.message_ports.remove(&port_id);
624624++ }
625625++ },
626626++ _ => {},
481627+ }
482628+ }
483629+ return;
···486632+ // Handle peer disconnect: clean up remote channel state.
487633+ if let constellation_traits::PairingEvent::PeerExpired { ref id } = event {
488634+ self.pairing.clear_remote_peer(id);
489489-+ }
635635+ }
490636+
491637+ for event_loop in self.event_loops() {
492638+ if self.embedder_error_listeners.contains(&event_loop.id()) {
493639+ let _ = event_loop.send(ScriptThreadMessage::DispatchPairingEvent(event.clone()));
494640+ }
495641+ }
496496-+ }
497497-+
642642+ }
643643+498644 /// Check the origin of a message against that of the pipeline it came from.
499499- /// Note: this is still limited as a security check,
500500- /// see <https://github.com/servo/servo/issues/11722>
501501-@@ -3246,6 +3561,13 @@
645645+@@ -2376,6 +2830,29 @@
646646+ TransferState::TransferInProgress(queue) => queue.push_back(task),
647647+ TransferState::CompletionFailed(queue) => queue.push_back(task),
648648+ TransferState::CompletionRequested(_, queue) => queue.push_back(task),
649649++ TransferState::Remote(peer_id) => {
650650++ // Serialize and send over P2P.
651651++ let port_id_bytes = match postcard::to_allocvec(&port_id) {
652652++ Ok(bytes) => bytes,
653653++ Err(err) => {
654654++ return warn!("Failed to serialize port_id for remote: {err}");
655655++ },
656656++ };
657657++ match postcard::to_allocvec(&task) {
658658++ Ok(task_data) => {
659659++ self.pairing.send_message(
660660++ peer_id,
661661++ &P2pMessage::PortMessage {
662662++ port_id: port_id_bytes,
663663++ task_data,
664664++ },
665665++ );
666666++ },
667667++ Err(err) => {
668668++ warn!("Failed to serialize PortMessageTask for remote port: {err}");
669669++ },
670670++ }
671671++ },
672672+ }
673673+ }
674674+675675+@@ -3246,6 +3723,13 @@
502676 /// <https://html.spec.whatwg.org/multipage/#destroy-a-top-level-traversable>
503677 fn handle_close_top_level_browsing_context(&mut self, webview_id: WebViewId) {
504678 debug!("{webview_id}: Closing");
···512686 let browsing_context_id = BrowsingContextId::from(webview_id);
513687 // Step 5. Remove traversable from the user agent's top-level traversable set.
514688 let browsing_context =
515515-@@ -3522,8 +3844,27 @@
689689+@@ -3522,8 +4006,27 @@
516690 opener_webview_id,
517691 opener_pipeline_id,
518692 response_sender,
···540714 let Some((webview_id_sender, webview_id_receiver)) = generic_channel::channel() else {
541715 warn!("Failed to create channel");
542716 let _ = response_sender.send(None);
543543-@@ -3622,6 +3963,361 @@
717717+@@ -3622,6 +4125,361 @@
544718 });
545719 }
546720···9021076 #[servo_tracing::instrument(skip_all)]
9031077 fn handle_refresh_cursor(&self, pipeline_id: PipelineId) {
9041078 let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
905905-@@ -4747,7 +5443,7 @@
10791079+@@ -4747,7 +5605,7 @@
9061080 }
90710819081082 #[servo_tracing::instrument(skip_all)]
···9111085 // Send a flat projection of the history to embedder.
9121086 // The final vector is a concatenation of the URLs of the past
9131087 // entries, the current entry and the future entries.
914914-@@ -4850,9 +5546,23 @@
10881088+@@ -4850,9 +5708,23 @@
9151089 );
9161090 self.embedder_proxy.send(EmbedderMsg::HistoryChanged(
9171091 webview_id,
+37-1
patches/components/constellation/pairing.rs.patch
···11--- original
22+++ modified
33-@@ -0,0 +1,662 @@
33+@@ -0,0 +1,698 @@
44+// SPDX-License-Identifier: AGPL-3.0-or-later
55+
66+//! P2P pairing service integration with the constellation.
···3636+ name: String,
3737+ data: Vec<u8>,
3838+ },
3939++ /// Offer a MessagePort to a remote peer.
4040++ PortOffer {
4141++ /// Unique ID for this offer (used to correlate with PortOfferAccepted).
4242++ stream_id: String,
4343++ /// The serialized MessagePortId of the port on the offering side.
4444++ port_id: Vec<u8>,
4545++ /// The peer that is offering the port.
4646++ from_peer: String,
4747++ },
4848++ /// Accept a port offer — the remote side created its port.
4949++ PortOfferAccepted {
5050++ stream_id: String,
5151++ /// The serialized MessagePortId of the newly created port on the accepting side.
5252++ port_id: Vec<u8>,
5353++ },
5454++ /// Forward a MessagePort task (message) to a remote port.
5555++ PortMessage {
5656++ /// The serialized MessagePortId on the receiving side.
5757++ port_id: Vec<u8>,
5858++ task_data: Vec<u8>,
5959++ },
6060++ /// Close/disentangle a remote port.
6161++ PortClose {
6262++ /// The serialized MessagePortId on the receiving side.
6363++ port_id: Vec<u8>,
6464++ },
6565++ /// Deny a port offer — the remote side refused the stream.
6666++ PortOfferDenied { stream_id: String },
3967+}
4068+
4169+impl P2pMessage {
···528556+ },
529557+ P2pMessage::BroadcastChannelMessage { .. } => {
530558+ // Return to constellation for local dispatch.
559559++ Some((from.to_owned(), message))
560560++ },
561561++ P2pMessage::PortOffer { .. } |
562562++ P2pMessage::PortOfferAccepted { .. } |
563563++ P2pMessage::PortMessage { .. } |
564564++ P2pMessage::PortClose { .. } |
565565++ P2pMessage::PortOfferDenied { .. } => {
566566++ // Return to constellation for port routing.
531567+ Some((from.to_owned(), message))
532568+ },
533569+ }
···3535 unminified_js_dir: unminify_js.then(|| unminified_path("unminified-js")),
3636 byte_length_queuing_strategy_size_function: OnceCell::new(),
3737 count_queuing_strategy_size_function: OnceCell::new(),
3838-@@ -3128,6 +3142,16 @@
3838+@@ -1139,6 +1153,25 @@
3939+ .send(ScriptToConstellationMessage::EntanglePorts(port1, port2));
4040+ }
4141+4242++ /// Set a single port's entanglement without requiring the other port to be locally managed.
4343++ /// Used for cross-device ports where the remote port only exists in the constellation.
4444++ pub(crate) fn set_port_entanglement(
4545++ &self,
4646++ port_id: MessagePortId,
4747++ entangled_with: MessagePortId,
4848++ ) {
4949++ if let MessagePortState::Managed(_id, message_ports) =
5050++ &mut *self.message_port_state.borrow_mut()
5151++ {
5252++ if let Some(managed_port) = message_ports.get_mut(&port_id) {
5353++ managed_port.dom_port.entangle(entangled_with);
5454++ if let Some(port_impl) = managed_port.port_impl.as_mut() {
5555++ port_impl.entangle(entangled_with);
5656++ }
5757++ }
5858++ }
5959++ }
6060++
6161+ /// Handle the transfer of a port in the current task.
6262+ pub(crate) fn mark_port_as_transferred(&self, port_id: &MessagePortId) -> MessagePortImpl {
6363+ if let MessagePortState::Managed(_id, message_ports) =
6464+@@ -3128,6 +3161,16 @@
3965 self.inherited_secure_context
4066 }
4167
+2-1
patches/components/script/dom/mod.rs.patch
···1616 pub(crate) mod keyboardevent;
1717 pub(crate) mod location;
1818 pub(crate) mod media;
1919-@@ -350,6 +352,9 @@
1919+@@ -350,6 +352,10 @@
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;
2525+pub(crate) mod peer;
2626++pub(crate) mod peerstreamevent;
2627 pub(crate) mod performance;
2728 pub(crate) use self::performance::*;
2829 pub(crate) mod permissions;
+89-8
patches/components/script/dom/navigator.rs.patch
···11--- original
22+++ modified
33-@@ -2,6 +2,7 @@
33+@@ -2,9 +2,11 @@
44 * License, v. 2.0. If a copy of the MPL was not distributed with this
55 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
66···88 use std::cell::Cell;
99 use std::convert::TryInto;
1010 use std::ops::Deref;
1111-@@ -23,6 +24,7 @@
1111++use std::rc::Rc;
1212+ use std::sync::LazyLock;
1313+1414+ use base::generic_channel;
1515+@@ -23,6 +25,7 @@
1216 use servo_url::ServoUrl;
13171418 use crate::body::Extractable;
···1620 use crate::dom::bindings::cell::DomRefCell;
1721 use crate::dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorMethods;
1822 use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
1919-@@ -38,6 +40,7 @@
2323+@@ -38,6 +41,7 @@
2024 use crate::dom::clipboard::Clipboard;
2125 use crate::dom::credentialmanagement::credentialscontainer::CredentialsContainer;
2226 use crate::dom::csp::{GlobalCspReporting, Violation};
···2428 #[cfg(feature = "gamepad")]
2529 use crate::dom::gamepad::Gamepad;
2630 #[cfg(feature = "gamepad")]
2727-@@ -44,6 +47,7 @@
3131+@@ -44,6 +48,7 @@
2832 use crate::dom::gamepad::gamepadevent::GamepadEventType;
2933 use crate::dom::geolocation::Geolocation;
3034 use crate::dom::globalscope::GlobalScope;
···3236 use crate::dom::mediadevices::MediaDevices;
3337 use crate::dom::mediasession::MediaSession;
3438 use crate::dom::mimetypearray::MimeTypeArray;
3535-@@ -130,6 +134,8 @@
3939+@@ -51,6 +56,7 @@
4040+ use crate::dom::performance::performanceresourcetiming::InitiatorType;
4141+ use crate::dom::permissions::Permissions;
4242+ use crate::dom::pluginarray::PluginArray;
4343++use crate::dom::promise::Promise;
4444+ use crate::dom::serviceworkercontainer::ServiceWorkerContainer;
4545+ use crate::dom::servointernals::ServoInternals;
4646+ use crate::dom::types::UserActivation;
4747+@@ -61,6 +67,7 @@
4848+ use crate::dom::xrsystem::XRSystem;
4949+ use crate::fetch::RequestWithGlobalScope;
5050+ use crate::network_listener::{FetchResponseListener, ResourceTimingListener, submit_timing};
5151++use crate::realms::InRealm;
5252+ use crate::script_runtime::{CanGc, JSContext};
5353+5454+ pub(super) fn hardware_concurrency() -> u64 {
5555+@@ -130,6 +137,8 @@
3656 has_gamepad_gesture: Cell<bool>,
3757 servo_internals: MutNullableDom<ServoInternals>,
3858 user_activation: MutNullableDom<UserActivation>,
···4161 }
42624363 impl Navigator {
4444-@@ -156,6 +162,8 @@
6464+@@ -156,6 +165,8 @@
4565 has_gamepad_gesture: Cell::new(false),
4666 servo_internals: Default::default(),
4767 user_activation: Default::default(),
···5070 }
5171 }
52725353-@@ -168,6 +176,11 @@
7373+@@ -168,6 +179,11 @@
5474 self.xr.get()
5575 }
5676···6282 #[cfg(feature = "gamepad")]
6383 pub(crate) fn get_gamepad(&self, index: usize) -> Option<DomRoot<Gamepad>> {
6484 self.gamepads.borrow().get(index).and_then(|g| g.get())
6565-@@ -559,6 +572,18 @@
8585+@@ -559,6 +575,18 @@
6686 .or_init(|| ServoInternals::new(&self.global(), CanGc::note()))
6787 }
6888···81101 /// <https://html.spec.whatwg.org/multipage/#dom-navigator-registerprotocolhandler>
82102 fn RegisterProtocolHandler(&self, scheme: DOMString, url: USVString) -> Fallible<()> {
83103 // Step 1. Let (normalizedScheme, normalizedURLString) be the result of
104104+@@ -602,6 +630,60 @@
105105+ self.user_activation
106106+ .or_init(|| UserActivation::new(&self.global(), can_gc))
107107+ }
108108++
109109++ fn CreatePeerStream(
110110++ &self,
111111++ peer_id: DOMString,
112112++ comp: InRealm,
113113++ can_gc: CanGc,
114114++ ) -> Fallible<Rc<Promise>> {
115115++ use base::id::MessagePortId;
116116++ use constellation_traits::ScriptToConstellationMessage;
117117++
118118++ use crate::dom::messageport::MessagePort;
119119++
120120++ let global = self.global();
121121++ let promise = Promise::new_in_current_realm(comp, can_gc);
122122++
123123++ // Create a local port and a virtual remote port ID.
124124++ // The remote port is NOT tracked in this global — it only exists
125125++ // in the constellation as a Remote proxy.
126126++ let port1 = MessagePort::new(&global, can_gc);
127127++ let remote_port_id = MessagePortId::new();
128128++
129129++ // Set port1's entanglement so postMessage knows where to route.
130130++ // We set it on both the DOM object and the MessagePortImpl.
131131++ global.track_message_port(&port1, None);
132132++ global.set_port_entanglement(*port1.message_port_id(), remote_port_id);
133133++
134134++ // Tell the constellation to create the remote port and send the offer.
135135++ let callback = base::generic_channel::GenericCallback::new(
136136++ move |result: Result<Result<(), String>, _>| {
137137++ if let Ok(Err(e)) = result {
138138++ log::warn!("CreatePeerStream failed: {e}");
139139++ }
140140++ },
141141++ )
142142++ .expect("Could not create callback");
143143++
144144++ let chan = global.script_to_constellation_chan();
145145++ if chan
146146++ .send(ScriptToConstellationMessage::CreatePeerStream(
147147++ peer_id.to_string(),
148148++ *port1.message_port_id(),
149149++ remote_port_id,
150150++ callback,
151151++ ))
152152++ .is_err()
153153++ {
154154++ promise.reject_error(script_bindings::error::Error::Operation(None), can_gc);
155155++ return Ok(promise);
156156++ }
157157++
158158++ // Resolve with port1 immediately.
159159++ promise.resolve_native(&port1, can_gc);
160160++ Ok(promise)
161161++ }
162162+ }
163163+164164+ struct BeaconFetchListener {
···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,60 @@
122122+@@ -726,6 +775,72 @@
123123 RespondToScreenshotReadinessRequest(ScreenshotReadinessResponse),
124124 /// Request the constellation to force garbage collection in all `ScriptThread`'s.
125125 TriggerGarbageCollection,
···177177+ PairingAcceptPairing(String, GenericCallback<Result<(), String>>),
178178+ /// Reject an incoming pairing request from a remote peer.
179179+ PairingRejectPairing(String, GenericCallback<Result<(), String>>),
180180++ /// Create a peer stream: create a virtual remote port entangled with a local port,
181181++ /// and send the offer to a remote peer.
182182++ /// Args: peer_id, local_port_id, remote_port_id, callback.
183183++ CreatePeerStream(
184184++ String,
185185++ base::id::MessagePortId,
186186++ base::id::MessagePortId,
187187++ GenericCallback<Result<(), String>>,
188188++ ),
189189++ /// Response to a DispatchPeerStream — whether the peer stream was accepted or denied.
190190++ /// Args: stream_id, from_peer_id, accepted.
191191++ PeerStreamResponse(String, String, bool),
180192 }
181193182194 impl fmt::Debug for ScriptToConstellationMessage {
+4-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,21 @@
3838+@@ -312,6 +320,24 @@
3939 SetAccessibilityActive(bool),
4040 /// Force a garbage collection in this script thread.
4141 TriggerGarbageCollection,
···5454+ DispatchServoError(ServoErrorType, String),
5555+ /// Dispatch a pairing event to all `navigator.embedder.pairing` instances in this script thread.
5656+ DispatchPairingEvent(constellation_traits::PairingEvent),
5757++ /// Dispatch a peer stream event — a remote peer is offering a MessagePort.
5858++ /// Contains (peer_id, serialized remote port_id bytes, stream_id, from_peer_id).
5959++ DispatchPeerStream(String, Vec<u8>, String, String),
5760 }
58615962 impl fmt::Debug for ScriptThreadMessage {