Rewild Your Web
18
fork

Configure Feed

Select the types of activity you want to include in your feed.

dbus: v1 of a generic dbus bridging api

Signed-off-by: webbeef <me@webbeef.org>

webbeef cfbead28 052b0ff9

+1064 -80
+2
Cargo.lock
··· 9266 9266 "beaver-p2p", 9267 9267 "content-security-policy", 9268 9268 "crossbeam-channel", 9269 + "dbus", 9270 + "dbus-tokio", 9269 9271 "euclid", 9270 9272 "gaol", 9271 9273 "ipc-channel",
+9 -1
patches/components/constellation/Cargo.toml.patch
··· 35 35 servo-background-hang-monitor = { workspace = true } 36 36 servo-background-hang-monitor-api = { workspace = true } 37 37 servo-base = { workspace = true } 38 - @@ -63,6 +70,8 @@ 38 + @@ -63,10 +70,16 @@ 39 39 storage_traits = { workspace = true } 40 40 stylo = { workspace = true } 41 41 stylo_traits = { workspace = true } ··· 44 44 tracing = { workspace = true, optional = true } 45 45 webgpu = { workspace = true, optional = true } 46 46 webgpu_traits = { workspace = true, optional = true } 47 + webxr-api = { workspace = true } 48 + 49 + +[target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies] 50 + +dbus = "0.9" 51 + +dbus-tokio = "0.7" 52 + + 53 + [target.'cfg(any(target_os="macos", all(not(target_os = "windows"), not(target_os = "ios"), not(target_os="android"), not(target_env="ohos"), not(target_arch="arm"), not(target_arch="aarch64"))))'.dependencies] 54 + gaol = "0.2.1"
+169 -40
patches/components/constellation/constellation.rs.patch
··· 67 67 use crate::broadcastchannel::BroadcastChannels; 68 68 use crate::browsingcontext::{ 69 69 AllBrowsingContextsIterator, BrowsingContext, FullyActiveBrowsingContextsIterator, 70 - @@ -186,10 +192,12 @@ 70 + @@ -185,11 +191,15 @@ 71 + NewBrowsingContextInfo, 71 72 }; 72 73 use crate::constellation_webview::ConstellationWebView; 74 + +#[cfg(any(target_os = "linux", target_os = "macos"))] 75 + +use crate::dbus_service; 73 76 use crate::event_loop::EventLoop; 74 77 +use crate::pairing::{P2pMessage, PairingService}; 75 78 use crate::pipeline::Pipeline; ··· 80 83 81 84 struct PendingApprovalNavigation { 82 85 load_data: LoadData, 83 - @@ -220,6 +228,12 @@ 86 + @@ -220,6 +230,12 @@ 84 87 /// While a completion failed, another global requested to complete the transfer. 85 88 /// We are still buffering messages, and awaiting the return of the buffer from the global who failed. 86 89 CompletionRequested(MessagePortRouterId, VecDeque<PortMessageTask>), ··· 93 96 } 94 97 95 98 #[derive(Debug)] 96 - @@ -531,6 +545,31 @@ 99 + @@ -531,6 +547,37 @@ 97 100 /// to the `UserContents` need to be forwared to all the `ScriptThread`s that host 98 101 /// the relevant `WebView`. 99 102 pub(crate) user_contents_for_manager_id: FxHashMap<UserContentManagerId, UserContents>, ··· 117 120 + /// The main process side of the ATProdo DOM API. 118 121 + at_proto: AtProtoManager, 119 122 + 123 + + /// D-Bus service for Linux/macOS device APIs. 124 + + #[cfg(any(target_os = "linux", target_os = "macos"))] 125 + + dbus: dbus_service::DbusService, 126 + + #[cfg(any(target_os = "linux", target_os = "macos"))] 127 + + dbus_signal_receiver: crossbeam_channel::Receiver<servo_constellation_traits::DbusSignalEvent>, 128 + + 120 129 + /// Registry of web task providers for the delegation system. 121 130 + task_registry: tasks::TaskRegistry, 122 131 + ··· 125 134 } 126 135 127 136 /// State needed to construct a constellation. 128 - @@ -594,6 +633,9 @@ 137 + @@ -594,6 +641,9 @@ 129 138 130 139 /// The wake lock provider for acquiring and releasing OS-level screen wake locks. 131 140 pub wake_lock_provider: Box<dyn WakeLockDelegate>, ··· 135 144 } 136 145 137 146 /// When we are exiting a pipeline, we can either force exiting or not. A normal exit 138 - @@ -703,7 +745,7 @@ 147 + @@ -682,6 +732,9 @@ 148 + 149 + let broken_image_icon_data = resources::read_bytes(Resource::BrokenImageIcon); 150 + 151 + + #[cfg(any(target_os = "linux", target_os = "macos"))] 152 + + let (dbus_signal_tx, dbus_signal_rx) = crossbeam_channel::unbounded(); 153 + + 154 + let mut constellation: Constellation<STF, SWF> = Constellation { 155 + event_loops: Default::default(), 156 + namespace_receiver, 157 + @@ -703,7 +756,7 @@ 139 158 script_to_devtools_callback: Default::default(), 140 159 #[cfg(feature = "bluetooth")] 141 160 bluetooth_ipc_sender: state.bluetooth_thread, ··· 144 163 private_resource_threads: state.private_resource_threads, 145 164 public_storage_threads: state.public_storage_threads, 146 165 private_storage_threads: state.private_storage_threads, 147 - @@ -758,6 +800,15 @@ 166 + @@ -758,6 +811,19 @@ 148 167 pending_viewport_changes: Default::default(), 149 168 screenshot_readiness_requests: Vec::new(), 150 169 user_contents_for_manager_id: Default::default(), ··· 152 171 + active_ime_webview: None, 153 172 + embedder_error_listeners: Default::default(), 154 173 + pairing: PairingService::new(), 174 + + #[cfg(any(target_os = "linux", target_os = "macos"))] 175 + + dbus: dbus_service::DbusService::new(dbus_signal_tx), 176 + + #[cfg(any(target_os = "linux", target_os = "macos"))] 177 + + dbus_signal_receiver: dbus_signal_rx, 155 178 + at_proto: AtProtoManager::new( 156 179 + state.public_resource_threads.core_thread, 157 180 + ), ··· 160 183 }; 161 184 162 185 constellation.run(); 163 - @@ -783,6 +834,18 @@ 186 + @@ -783,6 +849,18 @@ 164 187 fn clean_up_finished_script_event_loops(&mut self) { 165 188 self.event_loop_join_handles 166 189 .retain(|join_handle| !join_handle.is_finished()); ··· 179 202 self.event_loops 180 203 .retain(|event_loop| event_loop.upgrade().is_some()); 181 204 } 182 - @@ -1071,6 +1134,11 @@ 205 + @@ -1071,6 +1149,11 @@ 183 206 .get(&webview_id) 184 207 .and_then(|webview| webview.user_content_manager_id); 185 208 ··· 191 214 let new_pipeline_info = NewPipelineInfo { 192 215 parent_info: parent_pipeline_id, 193 216 new_pipeline_id, 194 - @@ -1082,6 +1150,13 @@ 217 + @@ -1082,6 +1165,13 @@ 195 218 user_content_manager_id, 196 219 theme, 197 220 target_snapshot_params, ··· 205 228 }; 206 229 let pipeline = match Pipeline::spawn(new_pipeline_info, event_loop, self, throttled) { 207 230 Ok(pipeline) => pipeline, 208 - @@ -1248,6 +1323,7 @@ 231 + @@ -1248,6 +1338,8 @@ 209 232 BackgroundHangMonitor(HangMonitorAlert), 210 233 Embedder(EmbedderToConstellationMessage), 211 234 FromSWManager(SWManagerMsg), 212 235 + PairingEvent(PairingEvent), 236 + + DBusSignal(servo_constellation_traits::DbusSignalEvent), 213 237 RemoveProcess(usize), 214 238 } 215 239 // Get one incoming request. 216 - @@ -1268,6 +1344,15 @@ 240 + @@ -1268,6 +1360,23 @@ 217 241 sel.recv(&self.embedder_to_constellation_receiver); 218 242 sel.recv(&self.swmanager_receiver); 219 243 ··· 224 248 + } else { 225 249 + false 226 250 + }; 227 - + let process_base_index = if has_pairing_receiver { 6 } else { 5 }; 251 + + 252 + + #[cfg(any(target_os = "linux", target_os = "macos"))] 253 + + sel.recv(&self.dbus_signal_receiver); 254 + + #[cfg(any(target_os = "linux", target_os = "macos"))] 255 + + let dbus_index_offset = 1; 256 + + #[cfg(not(any(target_os = "linux", target_os = "macos")))] 257 + + let dbus_index_offset = 0; 258 + + 259 + + let process_base_index = if has_pairing_receiver { 6 } else { 5 } + dbus_index_offset; 228 260 + 229 261 self.process_manager.register(&mut sel); 230 262 231 263 let request = { 232 - @@ -1298,9 +1383,13 @@ 264 + @@ -1298,9 +1407,26 @@ 233 265 .recv(&self.swmanager_receiver) 234 266 .expect("Unexpected SW channel panic in constellation") 235 267 .map(Request::FromSWManager), ··· 237 269 + oper.recv(self.pairing.event_receiver().expect("checked above")) 238 270 + .expect("Unexpected pairing event channel panic in constellation"), 239 271 + )), 272 + + i if dbus_index_offset > 0 && i == (if has_pairing_receiver { 6 } else { 5 }) => { 273 + + #[cfg(any(target_os = "linux", target_os = "macos"))] 274 + + { 275 + + Ok(Request::DBusSignal( 276 + + oper.recv(&self.dbus_signal_receiver) 277 + + .expect("Unexpected D-Bus signal channel panic"), 278 + + )) 279 + + } 280 + + #[cfg(not(any(target_os = "linux", target_os = "macos")))] 281 + + { 282 + + unreachable!() 283 + + } 284 + + }, 240 285 _ => { 241 286 // This can only be a error reading on a closed lifeline receiver. 242 287 - let process_index = index - 5; ··· 244 289 let _ = oper.recv(self.process_manager.receiver_at(process_index)); 245 290 Ok(Request::RemoveProcess(process_index)) 246 291 }, 247 - @@ -1326,6 +1415,9 @@ 292 + @@ -1326,6 +1452,23 @@ 248 293 Request::FromSWManager(message) => { 249 294 self.handle_request_from_swmanager(message); 250 295 }, 251 296 + Request::PairingEvent(event) => { 252 297 + self.handle_pairing_event(event); 253 298 + }, 299 + + Request::DBusSignal(event) => { 300 + + if let Some(interested) = self 301 + + .pipeline_interests 302 + + .get(&ConstellationInterest::DBusSignal) 303 + + { 304 + + for pipeline_id in interested { 305 + + if let Some(pipeline) = self.pipelines.get(pipeline_id) { 306 + + let _ = pipeline 307 + + .event_loop 308 + + .send(ScriptThreadMessage::DispatchDBusSignal(event.clone())); 309 + + } 310 + + } 311 + + } 312 + + }, 254 313 Request::RemoveProcess(index) => self.process_manager.remove(index), 255 314 } 256 315 } 257 - @@ -1567,11 +1659,7 @@ 316 + @@ -1567,11 +1710,7 @@ 258 317 } 259 318 }, 260 319 EmbedderToConstellationMessage::PreferencesUpdated(updates) => { ··· 267 326 let _ = event_loop.send(ScriptThreadMessage::PreferencesUpdated( 268 327 updates 269 328 .iter() 270 - @@ -1598,6 +1686,18 @@ 329 + @@ -1598,6 +1737,18 @@ 271 330 EmbedderToConstellationMessage::SetAccessibilityActive(webview_id, active) => { 272 331 self.set_accessibility_active(webview_id, active); 273 332 }, ··· 286 345 } 287 346 } 288 347 289 - @@ -1795,7 +1895,13 @@ 348 + @@ -1795,7 +1946,13 @@ 290 349 return warn!("Attempt to add channel name from an unexpected origin."); 291 350 } 292 351 self.broadcast_channels ··· 301 360 }, 302 361 ScriptToConstellationMessage::RemoveBroadcastChannelNameInRouter( 303 362 router_id, 304 - @@ -1809,7 +1915,13 @@ 363 + @@ -1809,7 +1966,13 @@ 305 364 return warn!("Attempt to remove channel name from an unexpected origin."); 306 365 } 307 366 self.broadcast_channels ··· 316 375 }, 317 376 ScriptToConstellationMessage::RemoveBroadcastChannelRouter(router_id, origin) => { 318 377 if self 319 - @@ -1821,6 +1933,12 @@ 378 + @@ -1821,6 +1984,12 @@ 320 379 self.broadcast_channels 321 380 .remove_broadcast_channel_router(router_id); 322 381 }, ··· 329 388 ScriptToConstellationMessage::ScheduleBroadcast(router_id, message) => { 330 389 if self 331 390 .check_origin_against_pipeline(&source_pipeline_id, &message.origin) 332 - @@ -1830,8 +1948,15 @@ 391 + @@ -1830,8 +1999,15 @@ 333 392 "Attempt to schedule broadcast from an origin not matching the origin of the msg." 334 393 ); 335 394 } ··· 346 405 }, 347 406 ScriptToConstellationMessage::PipelineExited => { 348 407 self.handle_pipeline_exited(source_pipeline_id); 349 - @@ -1851,6 +1976,12 @@ 408 + @@ -1851,6 +2027,12 @@ 350 409 ScriptToConstellationMessage::CreateAuxiliaryWebView(load_info) => { 351 410 self.handle_script_new_auxiliary(load_info); 352 411 }, ··· 359 418 ScriptToConstellationMessage::ChangeRunningAnimationsState(animation_state) => { 360 419 self.handle_change_running_animations_state(source_pipeline_id, animation_state) 361 420 }, 362 - @@ -1902,7 +2033,7 @@ 421 + @@ -1902,7 +2084,7 @@ 363 422 ScriptToConstellationMessage::SetFinalUrl(final_url) => { 364 423 // The script may have finished loading after we already started shutting down. 365 424 if let Some(ref mut pipeline) = self.pipelines.get_mut(&source_pipeline_id) { ··· 368 427 } else { 369 428 warn!("constellation got set final url message for dead pipeline"); 370 429 } 371 - @@ -2052,6 +2183,29 @@ 430 + @@ -2052,6 +2234,29 @@ 372 431 new_value, 373 432 ); 374 433 }, ··· 398 457 ScriptToConstellationMessage::MediaSessionEvent(pipeline_id, event) => { 399 458 // Unlikely at this point, but we may receive events coming from 400 459 // different media sessions, so we set the active media session based 401 - @@ -2071,8 +2225,13 @@ 460 + @@ -2071,7 +2276,12 @@ 402 461 } 403 462 self.active_media_session = Some(pipeline_id); 404 463 self.constellation_to_embedder_proxy.send( 405 464 - ConstellationToEmbedderMsg::MediaSessionEvent(webview_id, event), 406 465 + ConstellationToEmbedderMsg::MediaSessionEvent(webview_id, event.clone()), 407 - ); 466 + + ); 408 467 + // Also route to embedded webview parent iframe. 409 468 + self.handle_embedded_webview_notification( 410 469 + webview_id, 411 470 + EmbeddedWebViewEventType::MediaSessionEvent(event), 412 - + ); 471 + ); 413 472 }, 414 473 #[cfg(feature = "webgpu")] 415 - ScriptToConstellationMessage::RequestAdapter(response_sender, options, ids) => self 416 - @@ -2146,6 +2305,891 @@ 474 + @@ -2146,6 +2356,962 @@ 417 475 } 418 476 }, 419 477 }, ··· 635 693 + ConstellationToEmbedderMsg::ContentBlockerResetCounts(origin, callback), 636 694 + ); 637 695 + }, 696 + + ScriptToConstellationMessage::DBusCall( 697 + + bus, 698 + + destination, 699 + + path, 700 + + interface, 701 + + method, 702 + + args, 703 + + callback, 704 + + ) => { 705 + + #[cfg(any(target_os = "linux", target_os = "macos"))] 706 + + self.dbus 707 + + .call(bus, destination, path, interface, method, args, callback); 708 + + #[cfg(not(any(target_os = "linux", target_os = "macos")))] 709 + + let _ = callback.send(Err("D-Bus not available on this platform".to_owned())); 710 + + }, 711 + + ScriptToConstellationMessage::DBusGetProperty( 712 + + bus, 713 + + destination, 714 + + path, 715 + + interface, 716 + + property, 717 + + callback, 718 + + ) => { 719 + + #[cfg(any(target_os = "linux", target_os = "macos"))] 720 + + self.dbus 721 + + .get_property(bus, destination, path, interface, property, callback); 722 + + #[cfg(not(any(target_os = "linux", target_os = "macos")))] 723 + + let _ = callback.send(Err("D-Bus not available on this platform".to_owned())); 724 + + }, 725 + + ScriptToConstellationMessage::DBusSetProperty( 726 + + bus, 727 + + destination, 728 + + path, 729 + + interface, 730 + + property, 731 + + value, 732 + + callback, 733 + + ) => { 734 + + #[cfg(any(target_os = "linux", target_os = "macos"))] 735 + + self.dbus.set_property( 736 + + bus, 737 + + destination, 738 + + path, 739 + + interface, 740 + + property, 741 + + value, 742 + + callback, 743 + + ); 744 + + #[cfg(not(any(target_os = "linux", target_os = "macos")))] 745 + + let _ = callback.send(Err("D-Bus not available on this platform".to_owned())); 746 + + }, 747 + + ScriptToConstellationMessage::DBusSubscribe( 748 + + bus, 749 + + interface, 750 + + signal, 751 + + path, 752 + + sender, 753 + + callback, 754 + + ) => { 755 + + #[cfg(any(target_os = "linux", target_os = "macos"))] 756 + + self.dbus 757 + + .subscribe(bus, interface, signal, path, sender, callback); 758 + + #[cfg(not(any(target_os = "linux", target_os = "macos")))] 759 + + let _ = callback.send(Err("D-Bus not available on this platform".to_owned())); 760 + + }, 761 + + ScriptToConstellationMessage::DBusUnsubscribe(id, callback) => { 762 + + #[cfg(any(target_os = "linux", target_os = "macos"))] 763 + + self.dbus.unsubscribe(id, callback); 764 + + #[cfg(not(any(target_os = "linux", target_os = "macos")))] 765 + + let _ = callback.send(Err("D-Bus not available on this platform".to_owned())); 766 + + }, 638 767 + ScriptToConstellationMessage::CreatePeerStream( 639 768 + peer_id, 640 769 + local_port_id, ··· 1305 1434 } 1306 1435 } 1307 1436 1308 - @@ -2465,6 +3509,55 @@ 1437 + @@ -2465,6 +3631,55 @@ 1309 1438 TransferState::TransferInProgress(queue) => queue.push_back(task), 1310 1439 TransferState::CompletionFailed(queue) => queue.push_back(task), 1311 1440 TransferState::CompletionRequested(_, queue) => queue.push_back(task), ··· 1361 1490 } 1362 1491 } 1363 1492 1364 - @@ -3364,6 +4457,40 @@ 1493 + @@ -3364,6 +4579,40 @@ 1365 1494 /// <https://html.spec.whatwg.org/multipage/#destroy-a-top-level-traversable> 1366 1495 fn handle_close_top_level_browsing_context(&mut self, webview_id: WebViewId) { 1367 1496 debug!("{webview_id}: Closing"); ··· 1402 1531 let browsing_context_id = BrowsingContextId::from(webview_id); 1403 1532 // Step 5. Remove traversable from the user agent's top-level traversable set. 1404 1533 let browsing_context = 1405 - @@ -3640,8 +4767,27 @@ 1534 + @@ -3640,8 +4889,27 @@ 1406 1535 opener_webview_id, 1407 1536 opener_pipeline_id, 1408 1537 response_sender, ··· 1430 1559 let Some((webview_id_sender, webview_id_receiver)) = generic_channel::channel() else { 1431 1560 warn!("Failed to create channel"); 1432 1561 let _ = response_sender.send(None); 1433 - @@ -3740,6 +4886,395 @@ 1562 + @@ -3740,6 +5008,395 @@ 1434 1563 }); 1435 1564 } 1436 1565 ··· 1826 1955 #[servo_tracing::instrument(skip_all)] 1827 1956 fn handle_refresh_cursor(&self, pipeline_id: PipelineId) { 1828 1957 let Some(pipeline) = self.pipelines.get(&pipeline_id) else { 1829 - @@ -4289,7 +5824,7 @@ 1958 + @@ -4289,7 +5946,7 @@ 1830 1959 }, 1831 1960 }; 1832 1961 ··· 1835 1964 match self.browsing_contexts.get_mut(&browsing_context_id) { 1836 1965 Some(browsing_context) => { 1837 1966 let old_pipeline_id = browsing_context.pipeline_id; 1838 - @@ -4298,6 +5833,7 @@ 1967 + @@ -4298,6 +5955,7 @@ 1839 1968 old_pipeline_id, 1840 1969 browsing_context.parent_pipeline_id, 1841 1970 browsing_context.webview_id, ··· 1843 1972 ) 1844 1973 }, 1845 1974 None => { 1846 - @@ -4307,6 +5843,15 @@ 1975 + @@ -4307,6 +5965,15 @@ 1847 1976 1848 1977 self.unload_document(old_pipeline_id); 1849 1978 ··· 1859 1988 if let Some(new_pipeline) = self.pipelines.get(&new_pipeline_id) { 1860 1989 if let Some(ref chan) = self.devtools_sender { 1861 1990 let state = NavigationState::Start(new_pipeline.url.clone()); 1862 - @@ -4872,7 +6417,7 @@ 1991 + @@ -4872,7 +6539,7 @@ 1863 1992 } 1864 1993 1865 1994 #[servo_tracing::instrument(skip_all)] ··· 1868 1997 // Send a flat projection of the history to embedder. 1869 1998 // The final vector is a concatenation of the URLs of the past 1870 1999 // entries, the current entry and the future entries. 1871 - @@ -4976,9 +6521,22 @@ 2000 + @@ -4976,9 +6643,22 @@ 1872 2001 self.constellation_to_embedder_proxy 1873 2002 .send(ConstellationToEmbedderMsg::HistoryChanged( 1874 2003 webview_id, ··· 1892 2021 } 1893 2022 1894 2023 #[servo_tracing::instrument(skip_all)] 1895 - @@ -4997,7 +6555,7 @@ 2024 + @@ -4997,7 +6677,7 @@ 1896 2025 } 1897 2026 } 1898 2027 ··· 1901 2030 match self.browsing_contexts.get_mut(&change.browsing_context_id) { 1902 2031 Some(browsing_context) => { 1903 2032 debug!("Adding pipeline to existing browsing context."); 1904 - @@ -5004,11 +6562,15 @@ 2033 + @@ -5004,11 +6684,15 @@ 1905 2034 let old_pipeline_id = browsing_context.pipeline_id; 1906 2035 browsing_context.pipelines.insert(change.new_pipeline_id); 1907 2036 browsing_context.update_current_entry(change.new_pipeline_id); ··· 1919 2048 }, 1920 2049 }; 1921 2050 1922 - @@ -5016,6 +6578,18 @@ 2051 + @@ -5016,6 +6700,18 @@ 1923 2052 self.unload_document(old_pipeline_id); 1924 2053 } 1925 2054
+369
patches/components/constellation/dbus_service.rs.patch
··· 1 + --- original 2 + +++ modified 3 + @@ -0,0 +1,366 @@ 4 + +// SPDX-License-Identifier: AGPL-3.0-or-later 5 + + 6 + +use std::collections::HashMap; 7 + +use std::sync::Arc; 8 + +use std::sync::atomic::{AtomicU32, Ordering}; 9 + + 10 + +use crossbeam_channel::Sender; 11 + +use dbus::arg::RefArg; 12 + +use dbus::channel::MatchingReceiver; 13 + +use dbus::message::MatchRule; 14 + +use dbus::nonblock::{NonblockReply, SyncConnection}; 15 + +use dbus_tokio::connection; 16 + +use servo_base::generic_channel::GenericCallback; 17 + +use servo_constellation_traits::{DbusSignalEvent, DbusValue}; 18 + + 19 + +static NEXT_SUB_ID: AtomicU32 = AtomicU32::new(1); 20 + + 21 + +async fn send_and_await( 22 + + conn: &SyncConnection, 23 + + msg: dbus::Message, 24 + +) -> Result<dbus::Message, dbus::Error> { 25 + + let (tx, rx) = tokio::sync::oneshot::channel(); 26 + + conn.send_with_reply( 27 + + msg, 28 + + Box::new(move |reply, _| { 29 + + let _ = tx.send(reply); 30 + + }), 31 + + ) 32 + + .map_err(|_| dbus::Error::new_failed("Failed to send message"))?; 33 + + let reply = rx 34 + + .await 35 + + .map_err(|_| dbus::Error::new_failed("Reply channel closed"))?; 36 + + if reply.msg_type() == dbus::MessageType::Error { 37 + + let body = reply.get1::<String>().unwrap_or_default(); 38 + + Err(dbus::Error::new_failed(&body)) 39 + + } else { 40 + + Ok(reply) 41 + + } 42 + +} 43 + + 44 + +pub(crate) struct DbusService { 45 + + system_conn: Option<Arc<SyncConnection>>, 46 + + session_conn: Option<Arc<SyncConnection>>, 47 + + subscriptions: HashMap<u32, dbus::channel::Token>, 48 + + signal_sender: Sender<DbusSignalEvent>, 49 + +} 50 + + 51 + +impl DbusService { 52 + + pub(crate) fn new(signal_sender: Sender<DbusSignalEvent>) -> Self { 53 + + Self { 54 + + system_conn: None, 55 + + session_conn: None, 56 + + subscriptions: HashMap::new(), 57 + + signal_sender, 58 + + } 59 + + } 60 + + 61 + + fn get_connection(&mut self, bus: &str) -> Result<Arc<SyncConnection>, String> { 62 + + let conn = match bus { 63 + + "system" => &mut self.system_conn, 64 + + "session" => &mut self.session_conn, 65 + + _ => return Err(format!("Unknown bus type: {bus}")), 66 + + }; 67 + + 68 + + if conn.is_none() { 69 + + let (resource, new_conn) = match bus { 70 + + "system" => connection::new_system_sync(), 71 + + "session" => connection::new_session_sync(), 72 + + _ => unreachable!(), 73 + + } 74 + + .map_err(|e| format!("Failed to connect to {bus} bus: {e}"))?; 75 + + 76 + + // Spawn the D-Bus connection resource on the tokio runtime 77 + + net::async_runtime::spawn_task(async { 78 + + let err = resource.await; 79 + + log::error!("[DBus] Connection lost: {err}"); 80 + + }); 81 + + 82 + + *conn = Some(new_conn); 83 + + } 84 + + 85 + + Ok(conn.as_ref().expect("connection just set").clone()) 86 + + } 87 + + 88 + + pub(crate) fn call( 89 + + &mut self, 90 + + bus: String, 91 + + destination: String, 92 + + path: String, 93 + + interface: String, 94 + + method: String, 95 + + args: Vec<DbusValue>, 96 + + callback: GenericCallback<Result<String, String>>, 97 + + ) { 98 + + let conn = match self.get_connection(&bus) { 99 + + Ok(c) => c, 100 + + Err(e) => { 101 + + let _ = callback.send(Err(e)); 102 + + return; 103 + + }, 104 + + }; 105 + + 106 + + net::async_runtime::spawn_task(async move { 107 + + let mut msg = dbus::Message::new_method_call(&destination, &path, &interface, &method) 108 + + .expect("Failed to create message"); 109 + + 110 + + for arg in &args { 111 + + msg = append_dbus_value(msg, arg); 112 + + } 113 + + 114 + + let result = match send_and_await(&conn, msg).await { 115 + + Ok(reply) => { 116 + + let args: Vec<_> = reply.iter_init().collect(); 117 + + Ok(match args.len() { 118 + + 0 => "null".to_owned(), 119 + + 1 => refarg_to_json(&args[0]), 120 + + _ => { 121 + + let items: Vec<String> = 122 + + args.iter().map(|a| refarg_to_json(a)).collect(); 123 + + format!("[{}]", items.join(",")) 124 + + }, 125 + + }) 126 + + }, 127 + + Err(e) => Err(format!("D-Bus call failed: {e}")), 128 + + }; 129 + + 130 + + let _ = callback.send(result); 131 + + }); 132 + + } 133 + + 134 + + pub(crate) fn get_property( 135 + + &mut self, 136 + + bus: String, 137 + + destination: String, 138 + + path: String, 139 + + interface: String, 140 + + property: String, 141 + + callback: GenericCallback<Result<String, String>>, 142 + + ) { 143 + + let conn = match self.get_connection(&bus) { 144 + + Ok(c) => c, 145 + + Err(e) => { 146 + + let _ = callback.send(Err(e)); 147 + + return; 148 + + }, 149 + + }; 150 + + 151 + + net::async_runtime::spawn_task(async move { 152 + + let msg = dbus::Message::new_method_call( 153 + + &destination, 154 + + &path, 155 + + "org.freedesktop.DBus.Properties", 156 + + "Get", 157 + + ) 158 + + .expect("Failed to create message") 159 + + .append2(&*interface, &*property); 160 + + 161 + + let result = send_and_await(&conn, msg).await; 162 + + 163 + + let response = match result { 164 + + Ok(reply) => { 165 + + let args: Vec<_> = reply.iter_init().collect(); 166 + + Ok(if args.is_empty() { 167 + + "null".to_owned() 168 + + } else { 169 + + refarg_to_json(&args[0]) 170 + + }) 171 + + }, 172 + + Err(e) => Err(format!("D-Bus getProperty failed: {e}")), 173 + + }; 174 + + 175 + + let _ = callback.send(response); 176 + + }); 177 + + } 178 + + 179 + + pub(crate) fn set_property( 180 + + &mut self, 181 + + bus: String, 182 + + destination: String, 183 + + path: String, 184 + + interface: String, 185 + + property: String, 186 + + value: DbusValue, 187 + + callback: GenericCallback<Result<(), String>>, 188 + + ) { 189 + + let conn = match self.get_connection(&bus) { 190 + + Ok(c) => c, 191 + + Err(e) => { 192 + + let _ = callback.send(Err(e)); 193 + + return; 194 + + }, 195 + + }; 196 + + 197 + + net::async_runtime::spawn_task(async move { 198 + + use dbus::arg::messageitem::MessageItem; 199 + + 200 + + let variant = match &value { 201 + + DbusValue::Bool(v) => MessageItem::Variant(Box::new(MessageItem::Bool(*v))), 202 + + DbusValue::Int32(v) => MessageItem::Variant(Box::new(MessageItem::Int32(*v))), 203 + + DbusValue::Uint32(v) => MessageItem::Variant(Box::new(MessageItem::UInt32(*v))), 204 + + DbusValue::Int64(v) => MessageItem::Variant(Box::new(MessageItem::Int64(*v))), 205 + + DbusValue::Double(v) => MessageItem::Variant(Box::new(MessageItem::Double(*v))), 206 + + DbusValue::String(v) => MessageItem::Variant(Box::new(MessageItem::Str(v.clone()))), 207 + + DbusValue::Array(_) => { 208 + + let _ = 209 + + callback.send(Err("Array property values not yet supported".to_owned())); 210 + + return; 211 + + }, 212 + + }; 213 + + 214 + + let msg = dbus::Message::new_method_call( 215 + + &destination, 216 + + &path, 217 + + "org.freedesktop.DBus.Properties", 218 + + "Set", 219 + + ) 220 + + .expect("Failed to create message") 221 + + .append3( 222 + + MessageItem::Str(interface.clone()), 223 + + MessageItem::Str(property.clone()), 224 + + variant, 225 + + ); 226 + + 227 + + let result = send_and_await(&conn, msg).await; 228 + + 229 + + let _ = callback.send( 230 + + result 231 + + .map(|_| ()) 232 + + .map_err(|e| format!("D-Bus setProperty failed: {e}")), 233 + + ); 234 + + }); 235 + + } 236 + + 237 + + pub(crate) fn subscribe( 238 + + &mut self, 239 + + bus: String, 240 + + interface: String, 241 + + signal: String, 242 + + _path: String, 243 + + _sender: String, 244 + + callback: GenericCallback<Result<u32, String>>, 245 + + ) { 246 + + let conn = match self.get_connection(&bus) { 247 + + Ok(c) => c, 248 + + Err(e) => { 249 + + let _ = callback.send(Err(e)); 250 + + return; 251 + + }, 252 + + }; 253 + + 254 + + let sub_id = NEXT_SUB_ID.fetch_add(1, Ordering::Relaxed); 255 + + 256 + + let mut rule = MatchRule::new(); 257 + + rule.msg_type = Some(dbus::MessageType::Signal); 258 + + rule.interface = dbus::strings::Interface::new(&interface) 259 + + .ok() 260 + + .map(|i| i.into_static()); 261 + + rule.member = dbus::strings::Member::new(&signal) 262 + + .ok() 263 + + .map(|m| m.into_static()); 264 + + 265 + + let sig_name = signal.clone(); 266 + + let iface_name = interface.clone(); 267 + + let signal_sender = self.signal_sender.clone(); 268 + + 269 + + // Tell the bus daemon to forward matching signals to us 270 + + let match_str = rule.match_str(); 271 + + let conn2 = conn.clone(); 272 + + net::async_runtime::spawn_task(async move { 273 + + let add_match_msg = dbus::Message::new_method_call( 274 + + "org.freedesktop.DBus", 275 + + "/org/freedesktop/DBus", 276 + + "org.freedesktop.DBus", 277 + + "AddMatch", 278 + + ) 279 + + .expect("Failed to create AddMatch message") 280 + + .append1(&*match_str); 281 + + let _ = send_and_await(&conn2, add_match_msg).await; 282 + + }); 283 + + 284 + + let token = conn.start_receive( 285 + + rule, 286 + + Box::new(move |msg, _conn| { 287 + + let msg_path = msg.path().map(|p| p.to_string()).unwrap_or_default(); 288 + + 289 + + let args: Vec<_> = msg.iter_init().collect(); 290 + + let args_json = if args.is_empty() { 291 + + "[]".to_owned() 292 + + } else { 293 + + let items: Vec<String> = args.iter().map(|a| refarg_to_json(a)).collect(); 294 + + format!("[{}]", items.join(",")) 295 + + }; 296 + + 297 + + let _ = signal_sender.send(DbusSignalEvent { 298 + + signal: sig_name.clone(), 299 + + interface: iface_name.clone(), 300 + + path: msg_path, 301 + + args_json, 302 + + }); 303 + + 304 + + true 305 + + }), 306 + + ); 307 + + 308 + + self.subscriptions.insert(sub_id, token); 309 + + let _ = callback.send(Ok(sub_id)); 310 + + } 311 + + 312 + + pub(crate) fn unsubscribe(&mut self, id: u32, callback: GenericCallback<Result<(), String>>) { 313 + + if let Some(_token) = self.subscriptions.remove(&id) { 314 + + // Note: SyncConnection doesn't expose stop_receive directly. 315 + + // The token is removed from our tracking; the match rule stays 316 + + // until the connection is dropped. 317 + + let _ = callback.send(Ok(())); 318 + + } else { 319 + + let _ = callback.send(Err(format!("Unknown subscription ID: {id}"))); 320 + + } 321 + + } 322 + +} 323 + + 324 + +fn refarg_to_json(arg: &dyn RefArg) -> String { 325 + + // Unwrap variants to their inner value 326 + + if arg.arg_type() == dbus::arg::ArgType::Variant { 327 + + if let Some(mut iter) = arg.as_iter() { 328 + + if let Some(inner) = iter.next() { 329 + + return refarg_to_json(&inner); 330 + + } 331 + + } 332 + + } 333 + + 334 + + // Basic types 335 + + if let Some(s) = arg.as_str() { 336 + + return format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\"")); 337 + + } 338 + + if let Some(b) = arg.as_u64() { 339 + + return b.to_string(); 340 + + } 341 + + if let Some(b) = arg.as_i64() { 342 + + return b.to_string(); 343 + + } 344 + + if let Some(b) = arg.as_f64() { 345 + + return b.to_string(); 346 + + } 347 + + 348 + + // Arrays, dicts, structs 349 + + if let Some(iter) = arg.as_iter() { 350 + + let items: Vec<String> = iter.map(|item| refarg_to_json(&item)).collect(); 351 + + return format!("[{}]", items.join(",")); 352 + + } 353 + + 354 + + // Fallback 355 + + format!("\"{}\"", format!("{:?}", arg).replace('"', "\\\"")) 356 + +} 357 + + 358 + +fn append_dbus_value(msg: dbus::Message, value: &DbusValue) -> dbus::Message { 359 + + use dbus::arg::messageitem::MessageItem; 360 + + match value { 361 + + DbusValue::Bool(v) => msg.append1(MessageItem::Bool(*v)), 362 + + DbusValue::Int32(v) => msg.append1(MessageItem::Int32(*v)), 363 + + DbusValue::Uint32(v) => msg.append1(MessageItem::UInt32(*v)), 364 + + DbusValue::Int64(v) => msg.append1(MessageItem::Int64(*v)), 365 + + DbusValue::Double(v) => msg.append1(MessageItem::Double(*v)), 366 + + DbusValue::String(v) => msg.append1(MessageItem::Str(v.clone())), 367 + + DbusValue::Array(_) => msg, 368 + + } 369 + +}
+4 -2
patches/components/constellation/lib.rs.patch
··· 1 1 --- original 2 2 +++ modified 3 - @@ -7,6 +7,7 @@ 3 + @@ -7,18 +7,23 @@ 4 4 #[macro_use] 5 5 mod tracing; 6 6 ··· 8 8 mod broadcastchannel; 9 9 mod browsingcontext; 10 10 mod constellation; 11 - @@ -14,11 +15,13 @@ 11 + mod constellation_webview; 12 + +#[cfg(any(target_os = "linux", target_os = "macos"))] 13 + +mod dbus_service; 12 14 mod embedder; 13 15 mod event_loop; 14 16 mod logging;
+6 -1
patches/components/constellation/tracing.rs.patch
··· 36 36 Self::ActivateDocument => target!("ActivateDocument"), 37 37 Self::SetDocumentState(..) => target!("SetDocumentState"), 38 38 Self::SetFinalUrl(..) => target!("SetFinalUrl"), 39 - @@ -193,6 +201,69 @@ 39 + @@ -193,6 +201,74 @@ 40 40 Self::TriggerGarbageCollection => target!("TriggerGarbageCollection"), 41 41 Self::AcquireWakeLock(..) => target!("AcquireWakeLock"), 42 42 Self::ReleaseWakeLock(..) => target!("ReleaseWakeLock"), ··· 103 103 + target!("ContentBlockerSetOriginEnabled") 104 104 + }, 105 105 + Self::ContentBlockerResetCounts(..) => target!("ContentBlockerResetCounts"), 106 + + Self::DBusCall(..) => target!("DBusCall"), 107 + + Self::DBusGetProperty(..) => target!("DBusGetProperty"), 108 + + Self::DBusSetProperty(..) => target!("DBusSetProperty"), 109 + + Self::DBusSubscribe(..) => target!("DBusSubscribe"), 110 + + Self::DBusUnsubscribe(..) => target!("DBusUnsubscribe"), 106 111 } 107 112 } 108 113 }
+302
patches/components/script/dom/dbus.rs.patch
··· 1 + --- original 2 + +++ modified 3 + @@ -0,0 +1,299 @@ 4 + +/* SPDX Id: AGPL-3.0-or-later */ 5 + + 6 + +use std::rc::Rc; 7 + + 8 + +use dom_struct::dom_struct; 9 + +use js::context::JSContext; 10 + +use js::conversions::FromJSValConvertible; 11 + +use js::jsval::JSVal; 12 + +use js::rust::HandleValue; 13 + +use script_bindings::error::Error; 14 + +use script_bindings::script_runtime::JSContext as SafeJSContext; 15 + +use script_bindings::trace::RootedTraceableBox; 16 + +use servo_constellation_traits::{DbusSignalEvent, DbusValue, ScriptToConstellationMessage}; 17 + + 18 + +use crate::dom::bindings::codegen::Bindings::CustomEventBinding::CustomEventMethods; 19 + +use crate::dom::bindings::codegen::Bindings::DBusBinding::{ 20 + + DBusBusType, DBusCallOptions, DBusMethods, DBusPropertyOptions, DBusSignalOptions, 21 + +}; 22 + +use crate::dom::bindings::inheritance::Castable; 23 + +use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object}; 24 + +use crate::dom::bindings::root::DomRoot; 25 + +use crate::dom::bindings::str::DOMString; 26 + +use crate::dom::customevent::CustomEvent; 27 + +use crate::dom::event::Event; 28 + +use crate::dom::eventtarget::EventTarget; 29 + +use crate::dom::globalscope::GlobalScope; 30 + +use crate::dom::promise::Promise; 31 + +use crate::realms::InRealm; 32 + +use crate::routed_promise::{RoutedPromiseListener, callback_promise}; 33 + +use crate::script_runtime::CanGc; 34 + + 35 + +#[dom_struct] 36 + +pub(crate) struct DBus { 37 + + eventtarget: EventTarget, 38 + +} 39 + + 40 + +impl DBus { 41 + + fn new_inherited() -> DBus { 42 + + DBus { 43 + + eventtarget: EventTarget::new_inherited(), 44 + + } 45 + + } 46 + + 47 + + pub(crate) fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<DBus> { 48 + + reflect_dom_object(Box::new(DBus::new_inherited()), global, can_gc) 49 + + } 50 + +} 51 + + 52 + +fn bus_type_str(bus: &DBusBusType) -> &'static str { 53 + + match bus { 54 + + DBusBusType::System => "system", 55 + + DBusBusType::Session => "session", 56 + + } 57 + +} 58 + + 59 + +#[expect(unsafe_code)] 60 + +fn jsval_to_dbus(cx: *mut js::jsapi::JSContext, val: &JSVal) -> DbusValue { 61 + + if val.is_boolean() { 62 + + DbusValue::Bool(val.to_boolean()) 63 + + } else if val.is_int32() { 64 + + DbusValue::Int32(val.to_int32()) 65 + + } else if val.is_double() { 66 + + DbusValue::Double(val.to_double()) 67 + + } else if val.is_string() { 68 + + unsafe { 69 + + rooted!(in(cx) let rooted_val = *val); 70 + + match String::from_jsval(cx, rooted_val.handle(), ()) { 71 + + Ok(js::conversions::ConversionResult::Success(s)) => DbusValue::String(s), 72 + + _ => DbusValue::String(String::new()), 73 + + } 74 + + } 75 + + } else { 76 + + DbusValue::String(format!("{:?}", val)) 77 + + } 78 + +} 79 + + 80 + +impl DBusMethods<crate::DomTypeHolder> for DBus { 81 + + fn Call( 82 + + &self, 83 + + options: RootedTraceableBox<DBusCallOptions>, 84 + + comp: InRealm, 85 + + can_gc: CanGc, 86 + + ) -> Rc<Promise> { 87 + + let global = &self.global(); 88 + + let promise = Promise::new_in_current_realm(comp, can_gc); 89 + + let task_source = global.task_manager().dom_manipulation_task_source(); 90 + + let callback = callback_promise(&promise, self, task_source); 91 + + 92 + + let cx = GlobalScope::get_cx(); 93 + + let args: Vec<DbusValue> = options 94 + + .args 95 + + .iter() 96 + + .map(|arg| jsval_to_dbus(cx.raw_cx(), &arg.get())) 97 + + .collect(); 98 + + 99 + + let chan = global.script_to_constellation_chan(); 100 + + if chan 101 + + .send(ScriptToConstellationMessage::DBusCall( 102 + + bus_type_str(&options.bus).to_owned(), 103 + + options.destination.to_string(), 104 + + options.path.to_string(), 105 + + options.interfaceName.to_string(), 106 + + options.method.to_string(), 107 + + args, 108 + + callback, 109 + + )) 110 + + .is_err() 111 + + { 112 + + promise.reject_error(Error::Operation(None), can_gc); 113 + + } 114 + + promise 115 + + } 116 + + 117 + + fn GetProperty( 118 + + &self, 119 + + options: &DBusPropertyOptions, 120 + + comp: InRealm, 121 + + can_gc: CanGc, 122 + + ) -> Rc<Promise> { 123 + + let global = &self.global(); 124 + + let promise = Promise::new_in_current_realm(comp, can_gc); 125 + + let task_source = global.task_manager().dom_manipulation_task_source(); 126 + + let callback = callback_promise(&promise, self, task_source); 127 + + 128 + + let chan = global.script_to_constellation_chan(); 129 + + if chan 130 + + .send(ScriptToConstellationMessage::DBusGetProperty( 131 + + bus_type_str(&options.bus).to_owned(), 132 + + options.destination.to_string(), 133 + + options.path.to_string(), 134 + + options.interfaceName.to_string(), 135 + + options.property.to_string(), 136 + + callback, 137 + + )) 138 + + .is_err() 139 + + { 140 + + promise.reject_error(Error::Operation(None), can_gc); 141 + + } 142 + + promise 143 + + } 144 + + 145 + + fn SetProperty( 146 + + &self, 147 + + cx: SafeJSContext, 148 + + options: &DBusPropertyOptions, 149 + + value: HandleValue, 150 + + comp: InRealm, 151 + + can_gc: CanGc, 152 + + ) -> Rc<Promise> { 153 + + let global = &self.global(); 154 + + let promise = Promise::new_in_current_realm(comp, can_gc); 155 + + let task_source = global.task_manager().dom_manipulation_task_source(); 156 + + let callback = callback_promise(&promise, self, task_source); 157 + + 158 + + let dbus_value = jsval_to_dbus(cx.raw_cx(), &value.get()); 159 + + 160 + + let chan = global.script_to_constellation_chan(); 161 + + if chan 162 + + .send(ScriptToConstellationMessage::DBusSetProperty( 163 + + bus_type_str(&options.bus).to_owned(), 164 + + options.destination.to_string(), 165 + + options.path.to_string(), 166 + + options.interfaceName.to_string(), 167 + + options.property.to_string(), 168 + + dbus_value, 169 + + callback, 170 + + )) 171 + + .is_err() 172 + + { 173 + + promise.reject_error(Error::Operation(None), can_gc); 174 + + } 175 + + promise 176 + + } 177 + + 178 + + fn Subscribe(&self, options: &DBusSignalOptions, comp: InRealm, can_gc: CanGc) -> Rc<Promise> { 179 + + let global = &self.global(); 180 + + let promise = Promise::new_in_current_realm(comp, can_gc); 181 + + let task_source = global.task_manager().dom_manipulation_task_source(); 182 + + let callback = callback_promise(&promise, self, task_source); 183 + + 184 + + let chan = global.script_to_constellation_chan(); 185 + + 186 + + // Register interest so the constellation sends signals to this pipeline 187 + + let _ = chan.send(ScriptToConstellationMessage::RegisterInterest( 188 + + servo_constellation_traits::ConstellationInterest::DBusSignal, 189 + + )); 190 + + 191 + + if chan 192 + + .send(ScriptToConstellationMessage::DBusSubscribe( 193 + + bus_type_str(&options.bus).to_owned(), 194 + + options.interfaceName.to_string(), 195 + + options.signal.to_string(), 196 + + options.path.to_string(), 197 + + options.sender.to_string(), 198 + + callback, 199 + + )) 200 + + .is_err() 201 + + { 202 + + promise.reject_error(Error::Operation(None), can_gc); 203 + + } 204 + + promise 205 + + } 206 + + 207 + + fn Unsubscribe(&self, subscription_id: u32, comp: InRealm, can_gc: CanGc) -> Rc<Promise> { 208 + + let global = &self.global(); 209 + + let promise = Promise::new_in_current_realm(comp, can_gc); 210 + + let task_source = global.task_manager().dom_manipulation_task_source(); 211 + + let callback = callback_promise(&promise, self, task_source); 212 + + 213 + + let chan = global.script_to_constellation_chan(); 214 + + if chan 215 + + .send(ScriptToConstellationMessage::DBusUnsubscribe( 216 + + subscription_id, 217 + + callback, 218 + + )) 219 + + .is_err() 220 + + { 221 + + promise.reject_error(Error::Operation(None), can_gc); 222 + + } 223 + + promise 224 + + } 225 + +} 226 + + 227 + +impl DBus { 228 + + #[expect(unsafe_code)] 229 + + pub(crate) fn dispatch_signal(&self, event: &DbusSignalEvent, can_gc: CanGc) { 230 + + let global = self.global(); 231 + + let cx = GlobalScope::get_cx(); 232 + + 233 + + rooted!(in(cx.raw_cx()) let mut detail = js::jsval::UndefinedValue()); 234 + + unsafe { 235 + + let json = format!( 236 + + "{{\"signal\":\"{}\",\"interface\":\"{}\",\"path\":\"{}\",\"args\":{}}}", 237 + + event.signal, event.interface, event.path, event.args_json 238 + + ); 239 + + let utf16: Vec<u16> = json.encode_utf16().collect(); 240 + + let _ = js::jsapi::JS_ParseJSON( 241 + + cx.raw_cx(), 242 + + utf16.as_ptr(), 243 + + utf16.len() as u32, 244 + + detail.handle_mut().into(), 245 + + ); 246 + + } 247 + + 248 + + let custom_event = CustomEvent::new_uninitialized(&global, can_gc); 249 + + custom_event.InitCustomEvent( 250 + + cx, 251 + + DOMString::from(&*event.signal), 252 + + false, 253 + + false, 254 + + detail.handle(), 255 + + ); 256 + + custom_event 257 + + .upcast::<Event>() 258 + + .fire(self.upcast::<EventTarget>(), can_gc); 259 + + } 260 + +} 261 + + 262 + +impl RoutedPromiseListener<Result<String, String>> for DBus { 263 + + fn handle_response( 264 + + &self, 265 + + cx: &mut JSContext, 266 + + response: Result<String, String>, 267 + + promise: &Rc<Promise>, 268 + + ) { 269 + + match response { 270 + + Ok(value) => promise.resolve_native(&value, CanGc::from_cx(cx)), 271 + + Err(msg) => promise.reject_error(Error::Operation(Some(msg)), CanGc::from_cx(cx)), 272 + + } 273 + + } 274 + +} 275 + + 276 + +impl RoutedPromiseListener<Result<(), String>> for DBus { 277 + + fn handle_response( 278 + + &self, 279 + + cx: &mut JSContext, 280 + + response: Result<(), String>, 281 + + promise: &Rc<Promise>, 282 + + ) { 283 + + match response { 284 + + Ok(()) => promise.resolve_native(&(), CanGc::from_cx(cx)), 285 + + Err(msg) => promise.reject_error(Error::Operation(Some(msg)), CanGc::from_cx(cx)), 286 + + } 287 + + } 288 + +} 289 + + 290 + +impl RoutedPromiseListener<Result<u32, String>> for DBus { 291 + + fn handle_response( 292 + + &self, 293 + + cx: &mut JSContext, 294 + + response: Result<u32, String>, 295 + + promise: &Rc<Promise>, 296 + + ) { 297 + + match response { 298 + + Ok(id) => promise.resolve_native(&id, CanGc::from_cx(cx)), 299 + + Err(msg) => promise.reject_error(Error::Operation(Some(msg)), CanGc::from_cx(cx)), 300 + + } 301 + + } 302 + +}
+12 -1
patches/components/script/dom/embedder.rs.patch
··· 1 1 --- original 2 2 +++ modified 3 - @@ -0,0 +1,545 @@ 3 + @@ -0,0 +1,556 @@ 4 4 +/* SPDX Id: AGPL-3.0-or-later */ 5 5 + 6 6 +//! The `Embedder` interface provides communication between web content and the embedder. ··· 33 33 +use crate::dom::bindings::str::DOMString; 34 34 +use crate::dom::contentblocker::ContentBlocker; 35 35 +use crate::dom::customevent::CustomEvent; 36 + +use crate::dom::dbus::DBus; 36 37 +use crate::dom::event::Event; 37 38 +use crate::dom::eventtarget::EventTarget; 38 39 +use crate::dom::globalscope::GlobalScope; ··· 45 46 + eventtarget: EventTarget, 46 47 + pairing: MutNullableDom<Pairing>, 47 48 + content_blocker: MutNullableDom<ContentBlocker>, 49 + + dbus: MutNullableDom<DBus>, 48 50 +} 49 51 + 50 52 +impl Embedder { ··· 53 55 + eventtarget: EventTarget::new_inherited(), 54 56 + pairing: Default::default(), 55 57 + content_blocker: Default::default(), 58 + + dbus: Default::default(), 56 59 + } 57 60 + } 58 61 + ··· 72 75 + /// Get the Pairing instance if it has already been created (without lazy-initializing). 73 76 + pub(crate) fn get_pairing(&self) -> Option<DomRoot<Pairing>> { 74 77 + self.pairing.get() 78 + + } 79 + + 80 + + pub(crate) fn get_dbus(&self) -> Option<DomRoot<DBus>> { 81 + + self.dbus.get() 75 82 + } 76 83 + 77 84 + /// Dispatch a servoerror event with the given error type and message. ··· 524 531 + fn ContentBlocker(&self, can_gc: CanGc) -> DomRoot<ContentBlocker> { 525 532 + self.content_blocker 526 533 + .or_init(|| ContentBlocker::new(&self.global(), can_gc)) 534 + + } 535 + + 536 + + fn Dbus(&self, can_gc: CanGc) -> DomRoot<DBus> { 537 + + self.dbus.or_init(|| DBus::new(&self.global(), can_gc)) 527 538 + } 528 539 + 529 540 + // Event handler for servo error events
+8 -4
patches/components/script/dom/mod.rs.patch
··· 8 8 pub(crate) mod attr; 9 9 pub(crate) mod audio; 10 10 pub(crate) use self::audio::*; 11 - @@ -236,6 +237,7 @@ 11 + @@ -236,9 +237,11 @@ 12 12 pub(crate) use self::clipboard::*; 13 13 pub(crate) mod comment; 14 14 pub(crate) mod console; ··· 16 16 pub(crate) mod cookiestore; 17 17 mod create; 18 18 pub(crate) mod credentialmanagement; 19 - @@ -271,6 +273,7 @@ 19 + +pub(crate) mod dbus; 20 + pub(crate) use self::credentialmanagement::*; 21 + pub(crate) mod css; 22 + pub(crate) use self::css::*; 23 + @@ -271,6 +274,7 @@ 20 24 pub(crate) mod elementinternals; 21 25 pub(crate) mod encoding; 22 26 pub(crate) use self::encoding::*; ··· 24 28 pub(crate) mod event; 25 29 pub(crate) use self::event::*; 26 30 pub(crate) mod eventsource; 27 - @@ -296,6 +299,7 @@ 31 + @@ -296,6 +300,7 @@ 28 32 pub(crate) use self::indexeddb::*; 29 33 pub(crate) mod intersectionobserver; 30 34 pub(crate) mod intersectionobserverentry; ··· 32 36 pub(crate) mod location; 33 37 pub(crate) mod media; 34 38 pub(crate) use self::media::*; 35 - @@ -317,6 +321,10 @@ 39 + @@ -317,6 +322,10 @@ 36 40 pub(crate) mod origin; 37 41 pub(crate) mod paintsize; 38 42 pub(crate) mod paintworkletglobalscope;
+2 -1
patches/components/script/messaging.rs.patch
··· 1 1 --- original 2 2 +++ modified 3 - @@ -110,6 +110,14 @@ 3 + @@ -110,6 +110,15 @@ 4 4 ScriptThreadMessage::UpdatePinchZoomInfos(id, _) => Some(*id), 5 5 ScriptThreadMessage::SetAccessibilityActive(..) => None, 6 6 ScriptThreadMessage::TriggerGarbageCollection => None, 7 7 + ScriptThreadMessage::DispatchEmbeddedWebViewEvent { parent, .. } => Some(*parent), 8 8 + ScriptThreadMessage::DispatchServoError(..) => None, 9 9 + ScriptThreadMessage::DispatchPairingEvent(..) => None, 10 + + ScriptThreadMessage::DispatchDBusSignal(..) => None, 10 11 + ScriptThreadMessage::DispatchPeerStream(..) => None, 11 12 + ScriptThreadMessage::SetSpatialNavigation(pipeline_id, ..) => Some(*pipeline_id), 12 13 + ScriptThreadMessage::ShowTaskChooser(..) => None,
+31 -12
patches/components/script/script_thread.rs.patch
··· 57 57 use crate::dom::servoparser::{ParserContext, ServoParser}; 58 58 use crate::dom::types::DebuggerGlobalScope; 59 59 #[cfg(feature = "webgpu")] 60 - @@ -1944,11 +1951,50 @@ 60 + @@ -1944,12 +1951,51 @@ 61 61 self.handle_refresh_cursor(pipeline_id); 62 62 }, 63 63 ScriptThreadMessage::PreferencesUpdated(updates) => { ··· 81 81 + 82 82 + // Dispatch preferencechanged events to all Embedder instances 83 83 + self.dispatch_preference_changed_to_embedders(&updates, CanGc::from_cx(cx)); 84 - + }, 84 + }, 85 85 + ScriptThreadMessage::ShowTaskChooser( 86 86 + request_id, 87 87 + task_name, ··· 109 109 + &providers_json, 110 110 + CanGc::from_cx(cx), 111 111 + ); 112 - }, 112 + + }, 113 113 ScriptThreadMessage::ForwardKeyboardScroll(pipeline_id, scroll) => { 114 114 if let Some(document) = self.documents.borrow().find_document(pipeline_id) { 115 - @@ -1984,6 +2030,40 @@ 115 + document.event_handler().do_keyboard_scroll(scroll); 116 + @@ -1984,6 +2030,43 @@ 116 117 ScriptThreadMessage::TriggerGarbageCollection => unsafe { 117 118 JS_GC(*GlobalScope::get_cx(), GCReason::API); 118 119 }, ··· 129 130 + ScriptThreadMessage::DispatchPairingEvent(event) => { 130 131 + self.handle_dispatch_pairing_event(event, CanGc::from_cx(cx)); 131 132 + }, 133 + + ScriptThreadMessage::DispatchDBusSignal(event) => { 134 + + self.handle_dispatch_dbus_signal(event, CanGc::from_cx(cx)); 135 + + }, 132 136 + ScriptThreadMessage::DispatchPeerStream( 133 137 + peer_id, 134 138 + remote_port_id_bytes, ··· 153 157 } 154 158 } 155 159 156 - @@ -3003,6 +3083,9 @@ 160 + @@ -3003,6 +3086,9 @@ 157 161 .documents 158 162 .borrow() 159 163 .find_iframe(parent_pipeline_id, browsing_context_id); ··· 163 167 let Some(frame_element) = frame_element else { 164 168 return; 165 169 }; 166 - @@ -3027,6 +3110,7 @@ 170 + @@ -3027,6 +3113,7 @@ 167 171 // is no need to pass along existing opener information that 168 172 // will be discarded. 169 173 None, ··· 171 175 ); 172 176 } 173 177 174 - @@ -3308,6 +3392,155 @@ 178 + @@ -3308,6 +3395,170 @@ 175 179 } 176 180 } 177 181 ··· 225 229 + } 226 230 + } 227 231 + 232 + + fn handle_dispatch_dbus_signal( 233 + + &self, 234 + + event: servo_constellation_traits::DbusSignalEvent, 235 + + can_gc: CanGc, 236 + + ) { 237 + + for (_, document) in self.documents.borrow().iter() { 238 + + if let Some(embedder) = document.window().Navigator().get_embedder() { 239 + + if let Some(dbus) = embedder.get_dbus() { 240 + + let _ac = enter_realm(&*dbus); 241 + + dbus.dispatch_signal(&event, can_gc); 242 + + } 243 + + } 244 + + } 245 + + } 246 + + 228 247 + /// Handle an incoming peer stream: create a local MessagePort and fire "peerstream" on Window. 229 248 + /// Only fires on documents whose URL matches the target_url specified by the sender. 230 249 + /// If preventDefault() is called on the event, deny the offer. ··· 327 346 fn ask_constellation_for_top_level_info( 328 347 &self, 329 348 sender_webview_id: WebViewId, 330 - @@ -3416,7 +3649,13 @@ 349 + @@ -3416,7 +3667,13 @@ 331 350 self.senders.pipeline_to_embedder_sender.clone(), 332 351 self.senders.constellation_sender.clone(), 333 352 incomplete.pipeline_id, ··· 342 361 incomplete.viewport_details, 343 362 origin.clone(), 344 363 final_url.clone(), 345 - @@ -3438,6 +3677,8 @@ 364 + @@ -3438,6 +3695,8 @@ 346 365 #[cfg(feature = "webgpu")] 347 366 self.gpu_id_hub.clone(), 348 367 incomplete.load_data.inherited_secure_context, ··· 351 370 incomplete.theme, 352 371 self.this.clone(), 353 372 metadata.https_state, 354 - @@ -3458,6 +3699,7 @@ 373 + @@ -3458,6 +3717,7 @@ 355 374 incomplete.webview_id, 356 375 incomplete.parent_info, 357 376 incomplete.opener, ··· 359 378 ); 360 379 if window_proxy.parent().is_some() { 361 380 // https://html.spec.whatwg.org/multipage/#navigating-across-documents:delaying-load-events-mode-2 362 - @@ -4294,10 +4536,78 @@ 381 + @@ -4294,10 +4554,78 @@ 363 382 document.event_handler().handle_refresh_cursor(); 364 383 } 365 384 ··· 438 457 fn handle_request_screenshot_readiness( 439 458 &self, 440 459 webview_id: WebViewId, 441 - @@ -4338,7 +4648,7 @@ 460 + @@ -4338,7 +4666,7 @@ 442 461 can_gc: CanGc, 443 462 ) { 444 463 let Some(window) = self.documents.borrow().find_window(pipeline_id) else {
+11 -6
patches/components/script_bindings/codegen/Bindings.conf.patch
··· 12 12 'Attr': { 13 13 'implicitCxSetters': True, 14 14 }, 15 - @@ -117,6 +122,11 @@ 15 + @@ -117,6 +122,16 @@ 16 16 'cx': ['Constructor'], 17 17 }, 18 18 ··· 21 21 + 'canGc': ['GetOriginState', 'SetOriginEnabled', 'ResetCounts'], 22 22 +}, 23 23 + 24 + +'DBus': { 25 + + 'inRealms': ['Call', 'GetProperty', 'SetProperty', 'Subscribe', 'Unsubscribe'], 26 + + 'canGc': ['Call', 'GetProperty', 'SetProperty', 'Subscribe', 'Unsubscribe'], 27 + +}, 28 + + 24 29 'CookieStore': { 25 30 'cx': ['Set', 'Set_', 'Get', 'Get_', 'GetAll', 'GetAll_', 'Delete', 'Delete_'], 26 31 }, 27 - @@ -326,6 +336,11 @@ 32 + @@ -326,6 +341,11 @@ 28 33 'cx': ['CheckValidity', 'ReportValidity'], 29 34 }, 30 35 31 36 +'Embedder': { 32 37 + 'additionalTraits': ['crate::interfaces::EmbedderHelpers'], 33 - + 'canGc': ['Pairing', 'ContentBlocker'], 38 + + 'canGc': ['Pairing', 'ContentBlocker', 'Dbus'], 34 39 +}, 35 40 + 36 41 'EventSource': { 37 42 'weakReferenceable': True, 38 43 }, 39 - @@ -752,9 +767,9 @@ 44 + @@ -752,9 +772,9 @@ 40 45 }, 41 46 42 47 'Navigator': { ··· 49 54 }, 50 55 51 56 'CredentialsContainer': { 52 - @@ -799,6 +814,11 @@ 57 + @@ -799,6 +819,11 @@ 53 58 'cx': ['RegisterPaint'], 54 59 }, 55 60 ··· 61 66 'Performance': { 62 67 'cx': ['Mark', 'Measure'], 63 68 }, 64 - @@ -969,7 +989,8 @@ 69 + @@ -969,7 +994,8 @@ 65 70 }, 66 71 67 72 'Window': {
+56
patches/components/script_bindings/webidls/DBus.webidl.patch
··· 1 + --- original 2 + +++ modified 3 + @@ -0,0 +1,53 @@ 4 + +/* SPDX Id: AGPL-3.0-or-later */ 5 + + 6 + +[Exposed=Window, 7 + +Func="Embedder::is_allowed_to_embed"] 8 + +interface DBus : EventTarget { 9 + + // Call a method on a D-Bus object. 10 + + Promise<any> call(DBusCallOptions options); 11 + + 12 + + // Get a property value. 13 + + Promise<any> getProperty(DBusPropertyOptions options); 14 + + 15 + + // Set a property value. 16 + + Promise<undefined> setProperty(DBusPropertyOptions options, any value); 17 + + 18 + + // Subscribe to a D-Bus signal. Returns a subscription ID. 19 + + Promise<unsigned long> subscribe(DBusSignalOptions options); 20 + + 21 + + // Unsubscribe from a signal by ID. 22 + + Promise<undefined> unsubscribe(unsigned long subscriptionId); 23 + +}; 24 + + 25 + +dictionary DBusCallOptions { 26 + + required DBusBusType bus; 27 + + required USVString destination; 28 + + required USVString path; 29 + + required USVString interfaceName; 30 + + required USVString method; 31 + + sequence<any> args = []; 32 + + USVString signature = ""; 33 + +}; 34 + + 35 + +dictionary DBusPropertyOptions { 36 + + required DBusBusType bus; 37 + + required USVString destination; 38 + + required USVString path; 39 + + required USVString interfaceName; 40 + + required USVString property; 41 + +}; 42 + + 43 + +dictionary DBusSignalOptions { 44 + + required DBusBusType bus; 45 + + required USVString interfaceName; 46 + + required USVString signal; 47 + + USVString path = ""; 48 + + USVString sender = ""; 49 + +}; 50 + + 51 + +enum DBusBusType { "system", "session" }; 52 + + 53 + +partial interface Embedder { 54 + + [Func="Embedder::is_allowed_to_embed"] 55 + + readonly attribute DBus dbus; 56 + +};
+57 -9
patches/components/shared/constellation/from_script_message.rs.patch
··· 87 87 /// Specifies the information required to load an iframe. 88 88 #[derive(Debug, Deserialize, Serialize)] 89 89 pub struct IFrameLoadInfo { 90 - @@ -552,6 +589,90 @@ 90 + @@ -550,8 +587,94 @@ 91 + pub enum ConstellationInterest { 92 + /// Interest in `storage` events (fired when another same-origin pipeline modifies storage). 91 93 StorageEvent, 92 - } 93 - 94 + + /// Interest in D-Bus signal events. 95 + + DBusSignal, 96 + +} 97 + + 94 98 +#[derive(Deserialize, Serialize)] 95 99 +pub enum AtProtoRequest { 96 100 + /// User, Password ··· 148 152 + pub email_auth_factor: bool, 149 153 + pub active: bool, 150 154 + pub status: Option<String>, 151 - +} 152 - + 155 + } 156 + 153 157 +/// Data returned by com.atproto.server.refreshSession xrpc calls. 154 158 +#[derive(Clone, Debug, Deserialize, Serialize)] 155 159 +#[serde(rename_all = "camelCase")] ··· 178 182 /// Messages from the script to the constellation. 179 183 #[derive(Deserialize, IntoStaticStr, Serialize)] 180 184 pub enum ScriptToConstellationMessage { 181 - @@ -596,6 +717,10 @@ 185 + @@ -596,6 +719,10 @@ 182 186 NewBroadcastChannelNameInRouter(BroadcastChannelRouterId, String, ImmutableOrigin), 183 187 /// A global stopped managing broadcast channels for a given channel-name. 184 188 RemoveBroadcastChannelNameInRouter(BroadcastChannelRouterId, String, ImmutableOrigin), ··· 189 193 /// Broadcast a message to all same-origin broadcast channels, 190 194 /// excluding the source of the broadcast. 191 195 ScheduleBroadcast(BroadcastChannelRouterId, BroadcastChannelMsg), 192 - @@ -614,6 +739,9 @@ 196 + @@ -614,6 +741,9 @@ 193 197 Option<String>, 194 198 Option<String>, 195 199 ), ··· 199 203 /// Indicates whether this pipeline is currently running animations. 200 204 ChangeRunningAnimationsState(AnimationState), 201 205 /// Requests that a new 2D canvas thread be created. (This is done in the constellation because 202 - @@ -701,6 +829,10 @@ 206 + @@ -701,6 +831,10 @@ 203 207 ScriptNewIFrame(IFrameLoadInfoWithData), 204 208 /// Script has opened a new auxiliary browsing context. 205 209 CreateAuxiliaryWebView(AuxiliaryWebViewCreationRequest), ··· 210 214 /// Mark a new document as active 211 215 ActivateDocument, 212 216 /// Set the document state for a pipeline (used by screenshot / reftests) 213 - @@ -758,6 +890,131 @@ 217 + @@ -758,6 +892,175 @@ 214 218 /// aggregate lock count and notify the provider only when the count transitions from N to 0. 215 219 /// <https://w3c.github.io/screen-wake-lock/#dfn-release-wake-lock> 216 220 ReleaseWakeLock(WakeLockType), ··· 291 295 + ContentBlockerSetOriginEnabled(String, bool, GenericCallback<Result<(), String>>), 292 296 + /// Content blocker: reset counts for an origin. 293 297 + ContentBlockerResetCounts(String, GenericCallback<Result<(), String>>), 298 + + /// D-Bus: call a method. 299 + + /// Args: bus, destination, path, interface, method, args, callback. 300 + + DBusCall( 301 + + String, 302 + + String, 303 + + String, 304 + + String, 305 + + String, 306 + + Vec<super::DbusValue>, 307 + + GenericCallback<Result<String, String>>, 308 + + ), 309 + + /// D-Bus: get a property. 310 + + /// Args: bus, destination, path, interface, property, callback. 311 + + DBusGetProperty( 312 + + String, 313 + + String, 314 + + String, 315 + + String, 316 + + String, 317 + + GenericCallback<Result<String, String>>, 318 + + ), 319 + + /// D-Bus: set a property. 320 + + /// Args: bus, destination, path, interface, property, value, callback. 321 + + DBusSetProperty( 322 + + String, 323 + + String, 324 + + String, 325 + + String, 326 + + String, 327 + + super::DbusValue, 328 + + GenericCallback<Result<(), String>>, 329 + + ), 330 + + /// D-Bus: subscribe to a signal. 331 + + /// Args: bus, interface, signal, path, sender, callback (returns subscription ID). 332 + + DBusSubscribe( 333 + + String, 334 + + String, 335 + + String, 336 + + String, 337 + + String, 338 + + GenericCallback<Result<u32, String>>, 339 + + ), 340 + + /// D-Bus: unsubscribe from a signal. 341 + + DBusUnsubscribe(u32, GenericCallback<Result<(), String>>), 294 342 + /// Create a peer stream: create a virtual remote port entangled with a local port, 295 343 + /// and send the offer to a remote peer. 296 344 + /// Args: peer_id, local_port_id, remote_port_id, target_url, callback.
+23 -2
patches/components/shared/constellation/lib.rs.patch
··· 19 19 }; 20 20 pub use from_script_message::*; 21 21 use malloc_size_of_derive::MallocSizeOf; 22 - @@ -30,15 +32,152 @@ 22 + @@ -30,15 +32,173 @@ 23 23 use rustc_hash::FxHashMap; 24 24 use serde::{Deserialize, Serialize}; 25 25 use servo_base::cross_process_instant::CrossProcessInstant; ··· 103 103 + pub allowed_count: u32, 104 104 +} 105 105 + 106 + +/// A D-Bus argument value that can cross IPC boundaries. 107 + +#[derive(Clone, Debug, Deserialize, Serialize)] 108 + +pub enum DbusValue { 109 + + Bool(bool), 110 + + Int32(i32), 111 + + Uint32(u32), 112 + + Int64(i64), 113 + + Double(f64), 114 + + String(String), 115 + + Array(Vec<DbusValue>), 116 + +} 117 + + 118 + +/// A D-Bus signal received from a subscription. 119 + +#[derive(Clone, Debug, Deserialize, Serialize)] 120 + +pub struct DbusSignalEvent { 121 + + pub signal: String, 122 + + pub interface: String, 123 + + pub path: String, 124 + + pub args_json: String, 125 + +} 126 + + 106 127 +/// Information about a remote P2P peer. 107 128 +#[derive(Clone, Debug, Deserialize, Serialize)] 108 129 +pub struct PeerInfo { ··· 174 195 /// Messages to the Constellation from the embedding layer, whether from `ServoRenderer` or 175 196 /// from `libservo` itself. 176 197 #[derive(IntoStaticStr)] 177 - @@ -118,6 +257,9 @@ 198 + @@ -118,6 +278,9 @@ 178 199 UpdatePinchZoomInfos(PipelineId, PinchZoomInfos), 179 200 /// Activate or deactivate accessibility features for the given `WebView`. 180 201 SetAccessibilityActive(WebViewId, bool),
+3 -1
patches/components/shared/script/lib.rs.patch
··· 53 53 /// Notify the `ScriptThread` that the Servo renderer is no longer waiting on 54 54 /// asynchronous image uploads for the given `Pipeline`. These are mainly used 55 55 /// by canvas to perform uploads while the display list is being built. 56 - @@ -329,6 +346,26 @@ 56 + @@ -329,6 +346,28 @@ 57 57 SetAccessibilityActive(PipelineId, bool, Epoch), 58 58 /// Force a garbage collection in this script thread. 59 59 TriggerGarbageCollection, ··· 72 72 + DispatchServoError(ServoErrorType, String), 73 73 + /// Dispatch a pairing event to all `navigator.embedder.pairing` instances in this script thread. 74 74 + DispatchPairingEvent(PairingEvent), 75 + + /// Dispatch a D-Bus signal event to script. 76 + + DispatchDBusSignal(servo_constellation_traits::DbusSignalEvent), 75 77 + /// Dispatch a peer stream event — a remote peer is offering a MessagePort. 76 78 + /// Contains (peer_id, serialized remote port_id bytes, stream_id, from_peer_id, target_url). 77 79 + DispatchPeerStream(String, Vec<u8>, String, String, String),