Rewild Your Web
web
browser
dweb
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+}