Rewild Your Web
18
fork

Configure Feed

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

webtasks: remember the default choice per task name

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

webbeef 08b23592 3a0f30a6

+285 -50
+82 -15
patches/components/constellation/constellation.rs.patch
··· 410 410 ); 411 411 }, 412 412 #[cfg(feature = "webgpu")] 413 - @@ -2089,9 +2246,772 @@ 413 + @@ -2089,7 +2246,839 @@ 414 414 let _ = event_loop.send(ScriptThreadMessage::TriggerGarbageCollection); 415 415 } 416 416 }, ··· 668 668 + serde_json::to_string(&provider_infos).unwrap_or_else(|_| "[]".into()); 669 669 + let request_id = format!("task-{webview_id:?}-{source_pipeline_id:?}"); 670 670 + 671 + + // Check for a user default (stored by href + optional peer_id). 672 + + let default = self.task_registry.get_default(&task_name).cloned(); 673 + + 674 + + // If the default is a remote provider and the peer is connected, 675 + + // send TaskExecute directly without showing the chooser. 676 + + if let Some(ref default) = default { 677 + + if let Some(ref peer_id) = default.remote_peer_id { 678 + + if self.pairing.is_peer_connected(peer_id) { 679 + + // Serialize caller data for P2P. 680 + + let caller_data_bytes = data 681 + + .as_ref() 682 + + .and_then(|d| postcard::to_allocvec(d).ok()) 683 + + .unwrap_or_default(); 684 + + 685 + + self.pairing.send_message( 686 + + peer_id, 687 + + &P2pMessage::TaskExecute { 688 + + request_id: request_id.clone(), 689 + + task_name: task_name.clone(), 690 + + provider_href: default.href.clone(), 691 + + caller_data: caller_data_bytes, 692 + + }, 693 + + ); 694 + + 695 + + // Store the pending request for when TaskResult arrives. 696 + + self.pending_task_requests.insert( 697 + + request_id.clone(), 698 + + tasks::PendingTaskRequest { 699 + + task_name: task_name.clone(), 700 + + data, 701 + + callback, 702 + + provider: None, 703 + + provider_port_id, 704 + + provider_url: None, 705 + + dispatched: false, 706 + + provider_webview_id: None, 707 + + remote_caller_peer_id: None, 708 + + remote_providers: HashMap::new(), 709 + + }, 710 + + ); 711 + + return; 712 + + } 713 + + // Peer not connected — fall through to show chooser. 714 + + } 715 + + } 716 + + 717 + + // For local default, check if it matches a local provider. 718 + + let default_provider_id = default.and_then(|d| { 719 + + provider_infos 720 + + .iter() 721 + + .find(|p| p.href == d.href && p.remote_peer_id.is_none()) 722 + + .map(|p| p.id.clone()) 723 + + }); 724 + + 671 725 + // Register provider_port_id as a Task port in the constellation. 672 726 + // When the provider posts on its entangled port, the message arrives here 673 727 + // and gets routed through the callback to resolve the caller's promise. ··· 703 757 + request_id.clone(), 704 758 + task_name.clone(), 705 759 + providers_json.clone(), 760 + + default_provider_id.clone(), 706 761 + )); 707 762 + } 708 763 + ··· 826 881 + )); 827 882 + } 828 883 + }, 884 + + ScriptToConstellationMessage::SetTaskDefault( 885 + + task_name, 886 + + provider_href, 887 + + remote_peer_id, 888 + + ) => { 889 + + debug!( 890 + + "SetTaskDefault: {task_name} -> {provider_href} (remote: {remote_peer_id:?})" 891 + + ); 892 + + self.task_registry.set_default( 893 + + &task_name, 894 + + &provider_href, 895 + + remote_peer_id.as_deref(), 896 + + ); 897 + + }, 829 898 + ScriptToConstellationMessage::AcceptTask(callback) => { 830 899 + // Find a pending task request whose provider URL matches this pipeline's URL. 831 900 + let pipeline_url = self ··· 858 927 + let _ = callback.send(None); 859 928 + } 860 929 + }, 861 - } 862 - } 863 - 930 + + } 931 + + } 932 + + 864 933 + fn handle_pairing_event(&mut self, event: PairingEvent) { 865 934 + if let PairingEvent::MessageReceived { ref from, ref data } = event { 866 935 + debug!("P2P message received from {from}, {} bytes", data.len()); ··· 1152 1221 + // Handle peer disconnect: clean up remote channel state. 1153 1222 + if let PairingEvent::PeerExpired { ref id } = event { 1154 1223 + self.pairing.clear_remote_peer(id); 1155 - + } 1224 + } 1156 1225 + 1157 1226 + // When a peer connects or reconnects, sync our open broadcast channels to it. 1158 1227 + if let PairingEvent::PeerDiscovered { ref id, .. } | ··· 1178 1247 + let _ = event_loop.send(ScriptThreadMessage::DispatchPairingEvent(event.clone())); 1179 1248 + } 1180 1249 + } 1181 - + } 1182 - + 1250 + } 1251 + 1183 1252 /// Check the origin of a message against that of the pipeline it came from. 1184 - /// Note: this is still limited as a security check, 1185 - /// see <https://github.com/servo/servo/issues/11722> 1186 - @@ -2408,6 +3328,55 @@ 1253 + @@ -2408,6 +3397,55 @@ 1187 1254 TransferState::TransferInProgress(queue) => queue.push_back(task), 1188 1255 TransferState::CompletionFailed(queue) => queue.push_back(task), 1189 1256 TransferState::CompletionRequested(_, queue) => queue.push_back(task), ··· 1239 1306 } 1240 1307 } 1241 1308 1242 - @@ -3301,6 +4270,40 @@ 1309 + @@ -3301,6 +4339,40 @@ 1243 1310 /// <https://html.spec.whatwg.org/multipage/#destroy-a-top-level-traversable> 1244 1311 fn handle_close_top_level_browsing_context(&mut self, webview_id: WebViewId) { 1245 1312 debug!("{webview_id}: Closing"); ··· 1280 1347 let browsing_context_id = BrowsingContextId::from(webview_id); 1281 1348 // Step 5. Remove traversable from the user agent's top-level traversable set. 1282 1349 let browsing_context = 1283 - @@ -3577,8 +4580,27 @@ 1350 + @@ -3577,8 +4649,27 @@ 1284 1351 opener_webview_id, 1285 1352 opener_pipeline_id, 1286 1353 response_sender, ··· 1308 1375 let Some((webview_id_sender, webview_id_receiver)) = generic_channel::channel() else { 1309 1376 warn!("Failed to create channel"); 1310 1377 let _ = response_sender.send(None); 1311 - @@ -3679,6 +4701,398 @@ 1378 + @@ -3679,6 +4770,398 @@ 1312 1379 }); 1313 1380 } 1314 1381 ··· 1707 1774 #[servo_tracing::instrument(skip_all)] 1708 1775 fn handle_refresh_cursor(&self, pipeline_id: PipelineId) { 1709 1776 let Some(pipeline) = self.pipelines.get(&pipeline_id) else { 1710 - @@ -4818,7 +6232,7 @@ 1777 + @@ -4818,7 +6301,7 @@ 1711 1778 } 1712 1779 1713 1780 #[servo_tracing::instrument(skip_all)] ··· 1716 1783 // Send a flat projection of the history to embedder. 1717 1784 // The final vector is a concatenation of the URLs of the past 1718 1785 // entries, the current entry and the future entries. 1719 - @@ -4922,9 +6336,22 @@ 1786 + @@ -4922,9 +6405,22 @@ 1720 1787 self.constellation_to_embedder_proxy 1721 1788 .send(ConstellationToEmbedderMsg::HistoryChanged( 1722 1789 webview_id,
+6 -1
patches/components/constellation/pairing.rs.patch
··· 1 1 --- original 2 2 +++ modified 3 - @@ -0,0 +1,862 @@ 3 + @@ -0,0 +1,867 @@ 4 4 +// SPDX-License-Identifier: AGPL-3.0-or-later 5 5 + 6 6 +//! P2P pairing service integration with the constellation. ··· 136 136 + .unwrap_or_else(|| "Unknown device".to_string()), 137 137 + Err(_) => "Unknown device".to_string(), 138 138 + } 139 + + } 140 + + 141 + + /// Check if a peer is currently confirmed (we've exchanged messages with them). 142 + + pub(crate) fn is_peer_connected(&self, peer_id: &str) -> bool { 143 + + self.confirmed_peers.contains(peer_id) 139 144 + } 140 145 + 141 146 + /// Returns the event receiver for use in the constellation's Select loop.
+81 -1
patches/components/constellation/tasks.rs.patch
··· 1 1 --- original 2 2 +++ modified 3 - @@ -0,0 +1,515 @@ 3 + @@ -0,0 +1,595 @@ 4 4 +// SPDX-License-Identifier: AGPL-3.0-or-later 5 5 + 6 6 +//! Task provider registry for the Web Tasks delegation system. ··· 109 109 + pub remote_peer_id: Option<String>, 110 110 +} 111 111 + 112 + +/// A stored default provider preference. 113 + +#[derive(Clone, Debug, Deserialize, Serialize)] 114 + +pub struct TaskDefault { 115 + + pub href: String, 116 + + pub remote_peer_id: Option<String>, 117 + +} 118 + + 112 119 +/// Registry of all known task providers. 113 120 +pub struct TaskRegistry { 114 121 + /// Map from task name → list of providers. 115 122 + providers: HashMap<String, Vec<TaskProvider>>, 116 123 + /// Path to persist providers. 117 124 + config_path: Option<PathBuf>, 125 + + /// User defaults: task_name → default provider. 126 + + defaults: HashMap<String, TaskDefault>, 127 + + /// Path to persist defaults. 128 + + defaults_path: Option<PathBuf>, 118 129 +} 119 130 + 120 131 +impl Default for TaskRegistry { ··· 122 133 + Self { 123 134 + providers: HashMap::new(), 124 135 + config_path: None, 136 + + defaults: HashMap::new(), 137 + + defaults_path: None, 125 138 + } 126 139 + } 127 140 +} ··· 131 144 + let mut registry = Self { 132 145 + providers: HashMap::new(), 133 146 + config_path: config_dir.map(|d| d.join("task-providers.json")), 147 + + defaults: HashMap::new(), 148 + + defaults_path: config_dir.map(|d| d.join("task-defaults.json")), 134 149 + }; 135 150 + registry.load(); 151 + + registry.load_defaults(); 136 152 + registry 137 153 + } 138 154 + ··· 163 179 + "Registered task provider: {} -> {}", 164 180 + provider.task_name, href_str 165 181 + ); 182 + + // New provider — clear any default for this task so the user 183 + + // gets a chance to see the new option. 184 + + let task_name = provider.task_name.clone(); 166 185 + providers.push(provider); 186 + + if self.defaults.remove(&task_name).is_some() { 187 + + debug!( 188 + + "Cleared default for task '{}' due to new provider", 189 + + task_name 190 + + ); 191 + + self.save_defaults(); 192 + + } 167 193 + } 168 194 + 169 195 + self.save(); ··· 220 246 + providers.get(index) 221 247 + } 222 248 + 249 + + /// Get the default provider for a task, if one is set. 250 + + pub fn get_default(&self, task_name: &str) -> Option<&TaskDefault> { 251 + + self.defaults.get(task_name) 252 + + } 253 + + 254 + + /// Set the default provider for a task (by href and optional peer_id). 255 + + pub fn set_default( 256 + + &mut self, 257 + + task_name: &str, 258 + + provider_href: &str, 259 + + remote_peer_id: Option<&str>, 260 + + ) { 261 + + self.defaults.insert( 262 + + task_name.to_string(), 263 + + TaskDefault { 264 + + href: provider_href.to_string(), 265 + + remote_peer_id: remote_peer_id.map(|s| s.to_string()), 266 + + }, 267 + + ); 268 + + self.save_defaults(); 269 + + } 270 + + 223 271 + /// Save all providers to disk. 224 272 + fn save(&self) { 225 273 + let Some(path) = &self.config_path else { ··· 264 312 + "Failed to parse task providers from {}: {e}", 265 313 + path.display() 266 314 + ), 315 + + } 316 + + } 317 + + 318 + + /// Save defaults to disk. 319 + + fn save_defaults(&self) { 320 + + let Some(path) = &self.defaults_path else { 321 + + return; 322 + + }; 323 + + match serde_json::to_string_pretty(&self.defaults) { 324 + + Ok(json) => { 325 + + if let Err(e) = fs::write(path, json) { 326 + + error!("Failed to save task defaults: {e}"); 327 + + } 328 + + }, 329 + + Err(e) => error!("Failed to serialize task defaults: {e}"), 330 + + } 331 + + } 332 + + 333 + + /// Load defaults from disk. 334 + + fn load_defaults(&mut self) { 335 + + let Some(path) = &self.defaults_path else { 336 + + return; 337 + + }; 338 + + let Ok(json) = fs::read_to_string(path) else { 339 + + return; 340 + + }; 341 + + match serde_json::from_str::<HashMap<String, TaskDefault>>(&json) { 342 + + Ok(defaults) => { 343 + + self.defaults = defaults; 344 + + debug!("Loaded {} task defaults", self.defaults.len()); 345 + + }, 346 + + Err(e) => error!("Failed to parse task defaults from {}: {e}", path.display()), 267 347 + } 268 348 + } 269 349 +}
+2 -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 - @@ -187,6 +195,54 @@ 39 + @@ -187,6 +195,55 @@ 40 40 target!("RespondToScreenshotReadinessRequest") 41 41 }, 42 42 Self::TriggerGarbageCollection => target!("TriggerGarbageCollection"), ··· 88 88 + Self::RegisterTaskProvider(..) => target!("RegisterTaskProvider"), 89 89 + Self::TaskProviderSelected(..) => target!("TaskProviderSelected"), 90 90 + Self::AcceptTask(..) => target!("AcceptTask"), 91 + + Self::SetTaskDefault(..) => target!("SetTaskDefault"), 91 92 } 92 93 } 93 94 }
+38 -13
patches/components/script/dom/embedder.rs.patch
··· 1 1 --- original 2 2 +++ modified 3 - @@ -0,0 +1,512 @@ 3 + @@ -0,0 +1,537 @@ 4 4 +/* SPDX Id: AGPL-3.0-or-later */ 5 5 + 6 6 +//! The `Embedder` interface provides communication between web content and the embedder. ··· 198 198 + request_id: &str, 199 199 + task_name: &str, 200 200 + providers_json: &str, 201 + + default_provider: Option<&str>, 201 202 + can_gc: CanGc, 202 203 + ) { 203 204 + let cx = GlobalScope::get_cx(); ··· 206 207 + unsafe { 207 208 + rooted!(in(*cx) let detail_obj = JS_NewObject(*cx, ptr::null())); 208 209 + if !detail_obj.get().is_null() { 209 - + // Set requestId property 210 - + rooted!(in(*cx) let mut id_val = UndefinedValue()); 211 - + request_id.safe_to_jsval(cx, id_val.handle_mut(), can_gc); 210 + + rooted!(in(*cx) let mut val = UndefinedValue()); 211 + + 212 + + request_id.safe_to_jsval(cx, val.handle_mut(), can_gc); 212 213 + JS_DefineProperty( 213 214 + *cx, 214 215 + detail_obj.handle(), 215 216 + c"requestId".as_ptr(), 216 - + id_val.handle(), 217 + + val.handle(), 217 218 + JSPROP_ENUMERATE as u32, 218 219 + ); 219 220 + 220 - + // Set taskName property 221 - + rooted!(in(*cx) let mut name_val = UndefinedValue()); 222 - + task_name.safe_to_jsval(cx, name_val.handle_mut(), can_gc); 221 + + task_name.safe_to_jsval(cx, val.handle_mut(), can_gc); 223 222 + JS_DefineProperty( 224 223 + *cx, 225 224 + detail_obj.handle(), 226 225 + c"taskName".as_ptr(), 227 - + name_val.handle(), 226 + + val.handle(), 228 227 + JSPROP_ENUMERATE as u32, 229 228 + ); 230 229 + 231 - + // Set providers property (JSON string for now) 232 - + rooted!(in(*cx) let mut providers_val = UndefinedValue()); 233 - + providers_json.safe_to_jsval(cx, providers_val.handle_mut(), can_gc); 230 + + providers_json.safe_to_jsval(cx, val.handle_mut(), can_gc); 234 231 + JS_DefineProperty( 235 232 + *cx, 236 233 + detail_obj.handle(), 237 234 + c"providers".as_ptr(), 238 - + providers_val.handle(), 235 + + val.handle(), 239 236 + JSPROP_ENUMERATE as u32, 240 237 + ); 241 238 + 239 + + if let Some(default_id) = default_provider { 240 + + default_id.safe_to_jsval(cx, val.handle_mut(), can_gc); 241 + + JS_DefineProperty( 242 + + *cx, 243 + + detail_obj.handle(), 244 + + c"defaultProvider".as_ptr(), 245 + + val.handle(), 246 + + JSPROP_ENUMERATE as u32, 247 + + ); 248 + + } 249 + + 242 250 + detail.set(ObjectValue(detail_obj.get())); 243 251 + } 244 252 + } ··· 456 464 + ScriptToConstellationMessage::TaskProviderSelected( 457 465 + request_id.to_string(), 458 466 + provider_id.map(|s| s.to_string()), 467 + + ), 468 + + ); 469 + + } 470 + + 471 + + /// Set a user default for a task (by provider href and optional remote peer ID). 472 + + fn SetTaskDefault( 473 + + &self, 474 + + task_name: DOMString, 475 + + provider_href: USVString, 476 + + remote_peer_id: Option<DOMString>, 477 + + ) { 478 + + let global = self.global(); 479 + + let _ = global.script_to_constellation_chan().send( 480 + + ScriptToConstellationMessage::SetTaskDefault( 481 + + task_name.to_string(), 482 + + provider_href.to_string(), 483 + + remote_peer_id.map(|s| s.to_string()), 459 484 + ), 460 485 + ); 461 486 + }
+28 -14
patches/components/script/script_thread.rs.patch
··· 55 55 use crate::dom::servoparser::{ParserContext, ServoParser}; 56 56 use crate::dom::types::DebuggerGlobalScope; 57 57 #[cfg(feature = "webgpu")] 58 - @@ -1935,11 +1943,44 @@ 58 + @@ -1935,12 +1943,51 @@ 59 59 self.handle_refresh_cursor(pipeline_id); 60 60 }, 61 61 ScriptThreadMessage::PreferencesUpdated(updates) => { ··· 79 79 + 80 80 + // Dispatch preferencechanged events to all Embedder instances 81 81 + self.dispatch_preference_changed_to_embedders(&updates, CanGc::from_cx(cx)); 82 - + }, 83 - + ScriptThreadMessage::ShowTaskChooser(request_id, task_name, providers_json) => { 82 + }, 83 + + ScriptThreadMessage::ShowTaskChooser( 84 + + request_id, 85 + + task_name, 86 + + providers_json, 87 + + default_provider, 88 + + ) => { 84 89 + // Dispatch taskrequest event to all Embedder instances. 85 90 + // Only the system UI will have a handler registered. 86 91 + self.dispatch_task_request_to_embedders( 87 92 + &request_id, 88 93 + &task_name, 89 94 + &providers_json, 95 + + default_provider.as_deref(), 90 96 + CanGc::from_cx(cx), 91 97 + ); 92 98 + }, ··· 101 107 + &providers_json, 102 108 + CanGc::from_cx(cx), 103 109 + ); 104 - }, 110 + + }, 105 111 ScriptThreadMessage::ForwardKeyboardScroll(pipeline_id, scroll) => { 106 112 if let Some(document) = self.documents.borrow().find_document(pipeline_id) { 107 - @@ -1980,6 +2021,35 @@ 113 + document.event_handler().do_keyboard_scroll(scroll); 114 + @@ -1980,6 +2027,35 @@ 108 115 ScriptThreadMessage::TriggerGarbageCollection => unsafe { 109 116 JS_GC(*GlobalScope::get_cx(), GCReason::API); 110 117 }, ··· 140 147 } 141 148 } 142 149 143 - @@ -3023,6 +3093,9 @@ 150 + @@ -3023,6 +3099,9 @@ 144 151 .documents 145 152 .borrow() 146 153 .find_iframe(parent_pipeline_id, browsing_context_id); ··· 150 157 if let Some(frame_element) = frame_element { 151 158 frame_element.update_pipeline_id(new_pipeline_id, reason, cx); 152 159 } 153 - @@ -3042,6 +3115,7 @@ 160 + @@ -3042,6 +3121,7 @@ 154 161 // is no need to pass along existing opener information that 155 162 // will be discarded. 156 163 None, ··· 158 165 ); 159 166 } 160 167 } 161 - @@ -3326,6 +3400,155 @@ 168 + @@ -3326,6 +3406,155 @@ 162 169 } 163 170 } 164 171 ··· 314 321 fn ask_constellation_for_top_level_info( 315 322 &self, 316 323 sender_webview_id: WebViewId, 317 - @@ -3433,7 +3656,13 @@ 324 + @@ -3433,7 +3662,13 @@ 318 325 self.senders.pipeline_to_embedder_sender.clone(), 319 326 self.senders.constellation_sender.clone(), 320 327 incomplete.pipeline_id, ··· 329 336 incomplete.viewport_details, 330 337 origin.clone(), 331 338 final_url.clone(), 332 - @@ -3455,6 +3684,8 @@ 339 + @@ -3455,6 +3690,8 @@ 333 340 #[cfg(feature = "webgpu")] 334 341 self.gpu_id_hub.clone(), 335 342 incomplete.load_data.inherited_secure_context, ··· 338 345 incomplete.theme, 339 346 self.this.clone(), 340 347 ); 341 - @@ -3478,6 +3709,7 @@ 348 + @@ -3478,6 +3715,7 @@ 342 349 incomplete.webview_id, 343 350 incomplete.parent_info, 344 351 incomplete.opener, ··· 346 353 ); 347 354 if window_proxy.parent().is_some() { 348 355 // https://html.spec.whatwg.org/multipage/#navigating-across-documents:delaying-load-events-mode-2 349 - @@ -4309,10 +4541,71 @@ 356 + @@ -4309,10 +4547,78 @@ 350 357 document.event_handler().handle_refresh_cursor(); 351 358 } 352 359 ··· 375 382 + request_id: &str, 376 383 + task_name: &str, 377 384 + providers_json: &str, 385 + + default_provider: Option<&str>, 378 386 + can_gc: CanGc, 379 387 + ) { 380 388 + for (_, document) in self.documents.borrow().iter() { 381 389 + if let Some(embedder) = document.window().Navigator().get_embedder() { 382 390 + let _ac = enter_realm(&*embedder); 383 - + embedder.dispatch_task_request(request_id, task_name, providers_json, can_gc); 391 + + embedder.dispatch_task_request( 392 + + request_id, 393 + + task_name, 394 + + providers_json, 395 + + default_provider, 396 + + can_gc, 397 + + ); 384 398 + } 385 399 + } 386 400 + } ··· 418 432 fn handle_request_screenshot_readiness( 419 433 &self, 420 434 webview_id: WebViewId, 421 - @@ -4353,7 +4646,7 @@ 435 + @@ -4353,7 +4659,7 @@ 422 436 can_gc: CanGc, 423 437 ) { 424 438 let Some(window) = self.documents.borrow().find_window(pipeline_id) else {
+4 -1
patches/components/script_bindings/webidls/Embedder.webidl.patch
··· 1 1 --- original 2 2 +++ modified 3 - @@ -0,0 +1,62 @@ 3 + @@ -0,0 +1,65 @@ 4 4 +/* SPDX Id: AGPL-3.0-or-later */ 5 5 + 6 6 +// Servo-specific API for communication between web content and the embedder. ··· 27 27 + 28 28 + // Respond to a web task request with the selected provider id, or null to cancel. 29 29 + undefined respondToTaskRequest(DOMString requestId, DOMString? providerId); 30 + + 31 + + // Set a user default: always use this provider for this task (identified by href + optional remote peer). 32 + + undefined setTaskDefault(DOMString taskName, USVString providerHref, optional DOMString? remotePeerId = null); 30 33 + 31 34 + // Register a task provider. Only available to privileged pages. 32 35 + undefined registerTaskProvider(TaskProviderDescriptor descriptor);
+4 -1
patches/components/shared/constellation/from_script_message.rs.patch
··· 209 209 /// Mark a new document as active 210 210 ActivateDocument, 211 211 /// Set the document state for a pipeline (used by screenshot / reftests) 212 - @@ -725,6 +858,109 @@ 212 + @@ -725,6 +858,112 @@ 213 213 RespondToScreenshotReadinessRequest(ScreenshotReadinessResponse), 214 214 /// Request the constellation to force garbage collection in all `ScriptThread`'s. 215 215 TriggerGarbageCollection, ··· 316 316 + /// Constellation responds with the task data via callback. 317 317 + /// Args: callback(task_name, data, provider_port_id_bytes). 318 318 + AcceptTask(GenericCallback<Option<(String, Option<StructuredSerializedData>, Vec<u8>)>>), 319 + + /// Set a user default for a task: always use this provider. 320 + + /// Args: task_name, provider_href, remote_peer_id. 321 + + SetTaskDefault(String, String, Option<String>), 319 322 } 320 323 321 324 impl fmt::Debug for ScriptToConstellationMessage {
+2 -2
patches/components/shared/script/lib.rs.patch
··· 41 41 /// Preferences were updated in the parent process. 42 42 PreferencesUpdated(Vec<(String, PrefValue)>), 43 43 + /// A web task request needs to be shown to the system UI. 44 - + /// Fields: request_id, task_name, providers_json. 45 - + ShowTaskChooser(String, String, String), 44 + + /// Fields: request_id, task_name, providers_json, default_provider_id. 45 + + ShowTaskChooser(String, String, String, Option<String>), 46 46 + /// Tell the system UI to open a task provider webview. 47 47 + /// Fields: request_id, provider_url, provider_title. 48 48 + OpenTaskProvider(String, String, String),
+38 -1
ui/system/task_chooser.js
··· 114 114 .cancel:hover { 115 115 background: var(--bg-hover, #f0f0f0); 116 116 } 117 + 118 + .always-use { 119 + display: flex; 120 + align-items: center; 121 + gap: 0.5em; 122 + padding: 0.5em; 123 + font-size: 0.8em; 124 + color: var(--color-text-secondary, #666); 125 + cursor: pointer; 126 + } 117 127 `; 118 128 119 129 constructor() { ··· 213 223 } 214 224 215 225 _handleTaskRequest(e) { 216 - const { requestId, taskName, providers } = e.detail; 226 + const { requestId, taskName, providers, defaultProvider } = e.detail; 217 227 console.log("[TaskChooser] Task request received:", taskName, requestId); 218 228 219 229 this._requestId = requestId; ··· 233 243 return; 234 244 } 235 245 246 + // If there's a default and it's in the provider list, auto-select it. 247 + if (defaultProvider) { 248 + const defaultProv = this._providers.find((p) => p.id === defaultProvider); 249 + if (defaultProv) { 250 + console.log("[TaskChooser] Using default provider:", defaultProv.title); 251 + this._selectProvider(defaultProv); 252 + return; 253 + } 254 + } 255 + 236 256 this.open = true; 237 257 } 238 258 239 259 _selectProvider(provider) { 240 260 console.log("[TaskChooser] Provider selected:", provider.id, provider.title); 261 + 262 + // If "always use this" is checked, save the default. 263 + const checkbox = this.shadowRoot?.querySelector("#always-use"); 264 + if (checkbox?.checked) { 265 + navigator.embedder.setTaskDefault( 266 + this._taskName, 267 + provider.href, 268 + provider.remote_peer_id || null, 269 + ); 270 + } 271 + 241 272 // Store display mode for when opentaskprovider arrives. 242 273 this._selectedDisplay = provider.display === "Inline" ? "inline" : "window"; 243 274 this._respond(provider.id); ··· 287 318 </div> 288 319 `, 289 320 )} 321 + ${this._providers.length > 1 322 + ? html`<label class="always-use"> 323 + <input type="checkbox" id="always-use" /> 324 + Always use the selected provider 325 + </label>` 326 + : ""} 290 327 <button class="cancel" @click=${this._cancel}>Cancel</button> 291 328 </div> 292 329 </div>