Rewild Your Web
web browser dweb
at main 291 lines 14 kB view raw
1--- original 2+++ modified 3@@ -5,12 +5,15 @@ 4 use std::cell::Cell; 5 6 use base::Epoch; 7-use base::generic_channel::GenericSend; 8-use constellation_traits::{LoadData, NavigationHistoryBehavior}; 9+use base::generic_channel::{GenericSend, GenericSender}; 10+use constellation_traits::{ 11+ EmbeddedWebViewEventType, LoadData, NavigationHistoryBehavior, ScriptToConstellationMessage, 12+}; 13 use embedder_traits::{ 14- ContextMenuAction, ContextMenuElementInformation, ContextMenuElementInformationFlags, 15- ContextMenuItem, ContextMenuRequest, EditingActionEvent, EmbedderControlId, 16- EmbedderControlRequest, EmbedderControlResponse, EmbedderMsg, 17+ AllowOrDeny, ContextMenuAction, ContextMenuElementInformation, 18+ ContextMenuElementInformationFlags, ContextMenuItem, ContextMenuRequest, EditingActionEvent, 19+ EmbedderControlId, EmbedderControlRequest, EmbedderControlResponse, EmbedderMsg, 20+ PermissionFeature, PermissionPromptRequest, SimpleDialogRequest, 21 }; 22 use euclid::{Point2D, Rect, Size2D}; 23 use ipc_channel::router::ROUTER; 24@@ -35,8 +38,8 @@ 25 use crate::dom::node::{Node, NodeTraits, ShadowIncluding}; 26 use crate::dom::textcontrol::TextControlElement; 27 use crate::dom::types::{ 28- Element, HTMLAnchorElement, HTMLElement, HTMLImageElement, HTMLInputElement, HTMLSelectElement, 29- HTMLTextAreaElement, Window, 30+ Document, Element, HTMLAnchorElement, HTMLElement, HTMLImageElement, HTMLInputElement, 31+ HTMLSelectElement, HTMLTextAreaElement, Window, 32 }; 33 use crate::messaging::MainThreadScriptMsg; 34 35@@ -47,6 +50,7 @@ 36 FileInput(DomRoot<HTMLInputElement>), 37 Ime(DomRoot<HTMLElement>), 38 ContextMenu(ContextMenuNodes), 39+ PermissionPrompt(DomRoot<Document>), 40 } 41 42 impl ControlElement { 43@@ -57,6 +61,7 @@ 44 ControlElement::FileInput(element) => element.upcast::<Node>(), 45 ControlElement::Ime(element) => element.upcast::<Node>(), 46 ControlElement::ContextMenu(context_menu_nodes) => &context_menu_nodes.node, 47+ ControlElement::PermissionPrompt(document) => document.upcast::<Node>(), 48 } 49 } 50 } 51@@ -129,39 +134,77 @@ 52 EmbedderControlRequest::SelectElement(..) | 53 EmbedderControlRequest::ColorPicker(..) | 54 EmbedderControlRequest::InputMethod(..) | 55- EmbedderControlRequest::ContextMenu(..) => self 56- .window 57- .send_to_embedder(EmbedderMsg::ShowEmbedderControl(id, rect, request)), 58+ EmbedderControlRequest::ContextMenu(..) | 59+ EmbedderControlRequest::PermissionPrompt(..) => { 60+ // If this is an embedded webview, route the control request to the parent 61+ // iframe element via the Constellation. Otherwise, send directly to the embedder. 62+ if self.window.as_global_scope().is_embedded_webview() { 63+ self.window.send_to_constellation( 64+ ScriptToConstellationMessage::EmbeddedWebViewNotification( 65+ EmbeddedWebViewEventType::EmbedderControlShow { id, rect, request }, 66+ ), 67+ ); 68+ } else { 69+ // Check if this is an InputMethod request before consuming it 70+ let is_input_method = 71+ matches!(request, EmbedderControlRequest::InputMethod(..)); 72+ self.window 73+ .send_to_embedder(EmbedderMsg::ShowEmbedderControl(id, rect, request)); 74+ // Also notify constellation for IME tracking if this is an InputMethod control 75+ if is_input_method { 76+ self.window.send_to_constellation( 77+ ScriptToConstellationMessage::SetActiveImeWebView( 78+ self.window.webview_id(), 79+ ), 80+ ); 81+ } 82+ } 83+ }, 84 EmbedderControlRequest::FilePicker(file_picker_request) => { 85- let (sender, receiver) = profile_traits::ipc::channel( 86- self.window.as_global_scope().time_profiler_chan().clone(), 87- ) 88- .expect("Error initializing channel"); 89- let main_thread_sender = self.window.main_thread_script_chan().clone(); 90- ROUTER.add_typed_route( 91- receiver.to_ipc_receiver(), 92- Box::new(move |result| { 93- let Ok(embedder_control_response) = result else { 94- return; 95- }; 96- if let Err(error) = main_thread_sender.send( 97- MainThreadScriptMsg::ForwardEmbedderControlResponseFromFileManager( 98+ // If this is an embedded webview, route the control request to the parent 99+ // iframe element via the Constellation. 100+ if self.window.as_global_scope().is_embedded_webview() { 101+ self.window.send_to_constellation( 102+ ScriptToConstellationMessage::EmbeddedWebViewNotification( 103+ EmbeddedWebViewEventType::EmbedderControlShow { 104 id, 105- embedder_control_response, 106- ), 107- ) { 108- warn!("Could not send FileManager response to main thread: {error}") 109- } 110- }), 111- ); 112- self.window 113- .as_global_scope() 114- .resource_threads() 115- .sender() 116- .send(CoreResourceMsg::ToFileManager( 117- FileManagerThreadMsg::SelectFiles(id, file_picker_request, sender), 118- )) 119- .unwrap(); 120+ rect, 121+ request: EmbedderControlRequest::FilePicker(file_picker_request), 122+ }, 123+ ), 124+ ); 125+ } else { 126+ // For non-embedded webviews, use the FileManager directly 127+ let (sender, receiver) = profile_traits::ipc::channel( 128+ self.window.as_global_scope().time_profiler_chan().clone(), 129+ ) 130+ .expect("Error initializing channel"); 131+ let main_thread_sender = self.window.main_thread_script_chan().clone(); 132+ ROUTER.add_typed_route( 133+ receiver.to_ipc_receiver(), 134+ Box::new(move |result| { 135+ let Ok(embedder_control_response) = result else { 136+ return; 137+ }; 138+ if let Err(error) = main_thread_sender.send( 139+ MainThreadScriptMsg::ForwardEmbedderControlResponseFromFileManager( 140+ id, 141+ embedder_control_response, 142+ ), 143+ ) { 144+ warn!("Could not send FileManager response to main thread: {error}") 145+ } 146+ }), 147+ ); 148+ self.window 149+ .as_global_scope() 150+ .resource_threads() 151+ .sender() 152+ .send(CoreResourceMsg::ToFileManager( 153+ FileManagerThreadMsg::SelectFiles(id, file_picker_request, sender), 154+ )) 155+ .unwrap(); 156+ } 157 }, 158 } 159 160@@ -180,12 +223,67 @@ 161 pipeline_id: self.window.pipeline_id(), 162 index: index.0, 163 }; 164- self.window 165- .send_to_embedder(EmbedderMsg::HideEmbedderControl(id)); 166+ // If this is an embedded webview, route the hide request to the parent 167+ // iframe element via the Constellation. Otherwise, send directly to the embedder. 168+ if self.window.as_global_scope().is_embedded_webview() { 169+ self.window.send_to_constellation( 170+ ScriptToConstellationMessage::EmbeddedWebViewNotification( 171+ EmbeddedWebViewEventType::EmbedderControlHide { id }, 172+ ), 173+ ); 174+ } else { 175+ self.window 176+ .send_to_embedder(EmbedderMsg::HideEmbedderControl(id)); 177+ // Also notify constellation to clear IME tracking if this is an Ime control 178+ if matches!(control_element, ControlElement::Ime(_)) { 179+ self.window.send_to_constellation( 180+ ScriptToConstellationMessage::ClearActiveImeWebView( 181+ self.window.webview_id(), 182+ ), 183+ ); 184+ } 185+ } 186 false 187 }); 188 } 189 190+ /// Hide all visible embedder controls. This is called when the document becomes inactive 191+ /// (e.g., when navigating to a new page) to ensure controls like the virtual keyboard 192+ /// are hidden. 193+ pub(crate) fn hide_all_controls(&self) { 194+ let has_ime_control = self 195+ .visible_elements 196+ .borrow() 197+ .values() 198+ .any(|c| matches!(c, ControlElement::Ime(_))); 199+ let indices: Vec<_> = self.visible_elements.borrow().keys().cloned().collect(); 200+ for index in indices { 201+ let id = EmbedderControlId { 202+ webview_id: self.window.webview_id(), 203+ pipeline_id: self.window.pipeline_id(), 204+ index: index.0, 205+ }; 206+ if self.window.as_global_scope().is_embedded_webview() { 207+ self.window.send_to_constellation( 208+ ScriptToConstellationMessage::EmbeddedWebViewNotification( 209+ EmbeddedWebViewEventType::EmbedderControlHide { id }, 210+ ), 211+ ); 212+ } else { 213+ self.window 214+ .send_to_embedder(EmbedderMsg::HideEmbedderControl(id)); 215+ } 216+ } 217+ // Also notify constellation to clear IME tracking if we had an Ime control 218+ if has_ime_control && !self.window.as_global_scope().is_embedded_webview() { 219+ self.window 220+ .send_to_constellation(ScriptToConstellationMessage::ClearActiveImeWebView( 221+ self.window.webview_id(), 222+ )); 223+ } 224+ self.visible_elements.borrow_mut().clear(); 225+ } 226+ 227 pub(crate) fn handle_embedder_control_response( 228 &self, 229 id: EmbedderControlId, 230@@ -229,6 +327,10 @@ 231 ) => { 232 context_menu_nodes.handle_context_menu_action(action, can_gc); 233 }, 234+ (ControlElement::PermissionPrompt(_), EmbedderControlResponse::PermissionPrompt(_)) => { 235+ // Permission prompt responses go directly through the IPC sender 236+ // included in the request, so this path is not used. 237+ }, 238 (_, _) => unreachable!( 239 "The response to a form control should always match it's originating type." 240 ), 241@@ -235,6 +337,36 @@ 242 } 243 } 244 245+ /// Show a simple dialog (alert, confirm, prompt) for an embedded webview. 246+ /// The request includes an IPC sender that the parent iframe will use to respond directly. 247+ pub(crate) fn show_simple_dialog_for_embedded_webview(&self, request: SimpleDialogRequest) { 248+ // Send the request (with IPC sender) to parent via constellation. 249+ // The parent HTMLIFrameElement will store the sender and use it to respond. 250+ self.window.send_to_constellation( 251+ ScriptToConstellationMessage::EmbeddedWebViewNotification( 252+ EmbeddedWebViewEventType::SimpleDialogShow(request), 253+ ), 254+ ); 255+ } 256+ 257+ /// Show a permission prompt for an embedded webview. 258+ /// The request includes an IPC sender that the parent iframe will use to respond directly. 259+ pub(crate) fn show_permission_prompt( 260+ &self, 261+ feature: PermissionFeature, 262+ response_sender: GenericSender<AllowOrDeny>, 263+ ) -> EmbedderControlId { 264+ let request = EmbedderControlRequest::PermissionPrompt(PermissionPromptRequest { 265+ feature, 266+ response_sender, 267+ }); 268+ self.show_embedder_control( 269+ ControlElement::PermissionPrompt(self.window.Document()), 270+ request, 271+ None, 272+ ) 273+ } 274+ 275 pub(crate) fn show_context_menu(&self, hit_test_result: &HitTestResult) { 276 let mut anchor_element = None; 277 let mut image_element = None; 278@@ -410,8 +542,11 @@ 279 let Some(browsing_context) = document.browsing_context() else { 280 return; 281 }; 282- let (browsing_context, new) = browsing_context 283- .choose_browsing_context("_blank".into(), true /* nooopener */); 284+ let (browsing_context, new) = browsing_context.choose_browsing_context( 285+ "_blank".into(), 286+ true, /* nooopener */ 287+ Some(url.clone()), 288+ ); 289 let Some(browsing_context) = browsing_context else { 290 return; 291 };