Rewild Your Web
web browser dweb
at main 274 lines 10 kB view raw
1--- original 2+++ modified 3@@ -0,0 +1,271 @@ 4+/* SPDX Id: AGPL-3.0-or-later */ 5+ 6+//! The `Embedder` interface provides communication between web content and the embedder. 7+//! This is a Servo-specific, non-standard API. 8+ 9+use std::ptr; 10+ 11+use base::id::ScriptEventLoopId; 12+use constellation_traits::ScriptToConstellationMessage; 13+use dom_struct::dom_struct; 14+use embedder_traits::{EmbedderMsg, NewOSWindowParams, ServoErrorType}; 15+use js::jsapi::{JS_NewObject, JSPROP_ENUMERATE}; 16+use js::jsval::{BooleanValue, Int32Value, ObjectValue, UndefinedValue}; 17+use js::rust::HandleObject; 18+use js::rust::wrappers::JS_DefineProperty; 19+use script_bindings::conversions::SafeToJSValConvertible; 20+use script_bindings::interfaces::EmbedderHelpers; 21+use script_bindings::script_runtime::JSContext; 22+use script_bindings::str::USVString; 23+use servo_config::pref_util::PrefValue; 24+use servo_url::ServoUrl; 25+ 26+use crate::dom::bindings::codegen::Bindings::CustomEventBinding::CustomEventMethods; 27+use crate::dom::bindings::codegen::Bindings::EmbedderBinding::EmbedderMethods; 28+use crate::dom::bindings::inheritance::Castable; 29+use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object}; 30+use crate::dom::bindings::root::{Dom, DomRoot, Root}; 31+use crate::dom::bindings::str::DOMString; 32+use crate::dom::customevent::CustomEvent; 33+use crate::dom::event::Event; 34+use crate::dom::eventtarget::EventTarget; 35+use crate::dom::globalscope::GlobalScope; 36+use crate::realms::{AlreadyInRealm, InRealm}; 37+use crate::script_runtime::CanGc; 38+ 39+#[dom_struct] 40+pub(crate) struct Embedder { 41+ eventtarget: EventTarget, 42+} 43+ 44+impl Embedder { 45+ fn new_inherited() -> Embedder { 46+ Embedder { 47+ eventtarget: EventTarget::new_inherited(), 48+ } 49+ } 50+ 51+ pub(crate) fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<Embedder> { 52+ // Register this script thread as having an embedder error listener. 53+ // The Constellation tracks which script threads have listeners to avoid 54+ // broadcasting error events to threads that don't have any navigator.embedder. 55+ if let Some(event_loop_id) = ScriptEventLoopId::installed() { 56+ let _ = global.script_to_constellation_chan().send( 57+ ScriptToConstellationMessage::RegisterEmbedderErrorListener(event_loop_id), 58+ ); 59+ } 60+ 61+ reflect_dom_object(Box::new(Embedder::new_inherited()), global, can_gc) 62+ } 63+ 64+ /// Dispatch a servoerror event with the given error type and message. 65+ #[expect(unsafe_code)] 66+ pub(crate) fn dispatch_servo_error( 67+ &self, 68+ error_type: &ServoErrorType, 69+ message: &str, 70+ can_gc: CanGc, 71+ ) { 72+ let cx = GlobalScope::get_cx(); 73+ 74+ // Create the detail object: { errorType: string, message: string } 75+ rooted!(in(*cx) let mut detail = UndefinedValue()); 76+ 77+ unsafe { 78+ rooted!(in(*cx) let detail_obj = JS_NewObject(*cx, ptr::null())); 79+ if !detail_obj.get().is_null() { 80+ // Set errorType property 81+ rooted!(in(*cx) let mut error_type_val = UndefinedValue()); 82+ error_type 83+ .as_str() 84+ .safe_to_jsval(cx, error_type_val.handle_mut(), can_gc); 85+ JS_DefineProperty( 86+ *cx, 87+ detail_obj.handle(), 88+ c"errorType".as_ptr(), 89+ error_type_val.handle(), 90+ JSPROP_ENUMERATE as u32, 91+ ); 92+ 93+ // Set message property 94+ rooted!(in(*cx) let mut message_val = UndefinedValue()); 95+ message.safe_to_jsval(cx, message_val.handle_mut(), can_gc); 96+ JS_DefineProperty( 97+ *cx, 98+ detail_obj.handle(), 99+ c"message".as_ptr(), 100+ message_val.handle(), 101+ JSPROP_ENUMERATE as u32, 102+ ); 103+ 104+ detail.set(ObjectValue(detail_obj.get())); 105+ } 106+ } 107+ 108+ let global = self.global(); 109+ let custom_event = CustomEvent::new_uninitialized(&global, can_gc); 110+ custom_event.InitCustomEvent( 111+ cx, 112+ DOMString::from("servoerror"), 113+ false, // bubbles 114+ false, // cancelable 115+ detail.handle(), 116+ ); 117+ 118+ custom_event 119+ .upcast::<Event>() 120+ .fire(self.upcast::<EventTarget>(), can_gc); 121+ } 122+ 123+ /// Dispatch a preferencechanged event with the given name and value. 124+ #[expect(unsafe_code)] 125+ pub(crate) fn dispatch_preference_changed(&self, name: &str, value: &PrefValue, can_gc: CanGc) { 126+ let cx = GlobalScope::get_cx(); 127+ 128+ // Create the detail object: { name: string, value: any } 129+ rooted!(in(*cx) let mut detail = UndefinedValue()); 130+ 131+ unsafe { 132+ rooted!(in(*cx) let detail_obj = JS_NewObject(*cx, ptr::null())); 133+ if !detail_obj.get().is_null() { 134+ // Set name property 135+ rooted!(in(*cx) let mut name_val = UndefinedValue()); 136+ name.safe_to_jsval(cx, name_val.handle_mut(), can_gc); 137+ JS_DefineProperty( 138+ *cx, 139+ detail_obj.handle(), 140+ c"name".as_ptr(), 141+ name_val.handle(), 142+ JSPROP_ENUMERATE as u32, 143+ ); 144+ 145+ // Set value property (convert PrefValue to JSVal) 146+ rooted!(in(*cx) let mut value_val = UndefinedValue()); 147+ match value { 148+ PrefValue::Bool(b) => value_val.set(BooleanValue(*b)), 149+ PrefValue::Int(i) => value_val.set(Int32Value(*i as i32)), 150+ PrefValue::Str(s) => s.safe_to_jsval(cx, value_val.handle_mut(), can_gc), 151+ _ => { 152+ // For other types, convert to string representation 153+ format!("{:?}", value).safe_to_jsval(cx, value_val.handle_mut(), can_gc); 154+ }, 155+ } 156+ JS_DefineProperty( 157+ *cx, 158+ detail_obj.handle(), 159+ c"value".as_ptr(), 160+ value_val.handle(), 161+ JSPROP_ENUMERATE as u32, 162+ ); 163+ 164+ detail.set(ObjectValue(detail_obj.get())); 165+ } 166+ } 167+ 168+ let global = self.global(); 169+ let custom_event = CustomEvent::new_uninitialized(&global, can_gc); 170+ custom_event.InitCustomEvent( 171+ cx, 172+ DOMString::from("preferencechanged"), 173+ false, // bubbles 174+ false, // cancelable 175+ detail.handle(), 176+ ); 177+ 178+ custom_event 179+ .upcast::<Event>() 180+ .fire(self.upcast::<EventTarget>(), can_gc); 181+ } 182+ 183+ pub(crate) fn is_allowed_to_embed_for_url(url: &ServoUrl) -> bool { 184+ // TODO: better permission mechanism with finer granularity 185+ url.domain() == Some("system.localhost") || 186+ url.domain() == Some("settings.localhost") || 187+ url.domain() == Some("keyboard.localhost") || 188+ url.domain() == Some("homescreen.localhost") 189+ } 190+} 191+ 192+impl EmbedderHelpers for Embedder { 193+ #[expect(unsafe_code)] 194+ fn is_allowed_to_embed(cx: JSContext, _global: HandleObject) -> bool { 195+ unsafe { 196+ let in_realm_proof = AlreadyInRealm::assert_for_cx(cx); 197+ let global_scope = GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof)); 198+ Self::is_allowed_to_embed_for_url(&global_scope.get_url()) 199+ } 200+ } 201+} 202+ 203+impl EmbedderMethods<crate::DomTypeHolder> for Embedder { 204+ /// Request the embedder to open a new OS-level window with the given URL. 205+ fn OpenNewOSWindow(&self, url: USVString, features: USVString) { 206+ let global = self.global(); 207+ 208+ // Parse the URL relative to the document's base URL 209+ let base_url = global.api_base_url(); 210+ let Ok(parsed_url) = ServoUrl::parse_with_base(Some(&base_url), &url) else { 211+ // Silently ignore invalid URLs 212+ return; 213+ }; 214+ 215+ let _ = global 216+ .script_to_embedder_chan() 217+ .send(EmbedderMsg::OpenNewOSWindow(NewOSWindowParams { 218+ url: parsed_url, 219+ features: features.into(), 220+ })); 221+ } 222+ 223+ /// Request the embedder to close the currently focused OS window. 224+ fn CloseCurrentOSWindow(&self) { 225+ let _ = self 226+ .global() 227+ .script_to_embedder_chan() 228+ .send(EmbedderMsg::CloseCurrentOSWindow); 229+ } 230+ 231+ /// Request the embedder to exit the application. 232+ fn Exit(&self) { 233+ let _ = self 234+ .global() 235+ .script_to_embedder_chan() 236+ .send(EmbedderMsg::ExitApplication); 237+ } 238+ 239+ /// Request the embedder to start a window drag operation. 240+ fn StartWindowDrag(&self) { 241+ let global = self.global(); 242+ let Some(webview_id) = global.webview_id() else { 243+ return; 244+ }; 245+ let _ = global 246+ .script_to_embedder_chan() 247+ .send(EmbedderMsg::StartWindowDrag(webview_id)); 248+ } 249+ 250+ /// Request the embedder to start a window resize operation. 251+ fn StartWindowResize(&self) { 252+ let global = self.global(); 253+ let Some(webview_id) = global.webview_id() else { 254+ return; 255+ }; 256+ let _ = global 257+ .script_to_embedder_chan() 258+ .send(EmbedderMsg::StartWindowResize(webview_id)); 259+ } 260+ 261+ fn Pairing(&self) -> Root<Dom<<crate::DomTypeHolder as crate::DomTypes>::Pairing>> { 262+ todo!() 263+ } 264+ 265+ // Event handler for servo error events 266+ event_handler!(servoerror, GetOnservoerror, SetOnservoerror); 267+ 268+ // Event handler for preference changed events 269+ event_handler!( 270+ preferencechanged, 271+ GetOnpreferencechanged, 272+ SetOnpreferencechanged 273+ ); 274+}