script_bindings/
callback.rs1use std::default::Default;
8use std::ffi::CString;
9use std::mem::drop;
10use std::rc::Rc;
11
12use js::jsapi::{
13 AddRawValueRoot, EnterRealm, Heap, IsCallable, JSObject, LeaveRealm, Realm, RemoveRawValueRoot,
14};
15use js::jsval::{JSVal, NullValue, ObjectValue, UndefinedValue};
16use js::rust::wrappers::{JS_GetProperty, JS_WrapObject};
17use js::rust::{HandleObject, MutableHandleValue, Runtime};
18
19use crate::DomTypes;
20use crate::codegen::GenericBindings::WindowBinding::Window_Binding::WindowMethods;
21use crate::error::{Error, Fallible};
22use crate::inheritance::Castable;
23use crate::interfaces::{DocumentHelpers, DomHelpers, GlobalScopeHelpers};
24use crate::realms::{InRealm, enter_realm};
25use crate::reflector::DomObject;
26use crate::root::{Dom, DomRoot};
27use crate::script_runtime::{CanGc, JSContext};
28use crate::settings_stack::{GenericAutoEntryScript, GenericAutoIncumbentScript};
29
30pub trait ThisReflector {
31 fn jsobject(&self) -> *mut JSObject;
32}
33
34impl<T: DomObject> ThisReflector for T {
35 fn jsobject(&self) -> *mut JSObject {
36 self.reflector().get_jsobject().get()
37 }
38}
39
40impl ThisReflector for HandleObject<'_> {
41 fn jsobject(&self) -> *mut JSObject {
42 self.get()
43 }
44}
45
46#[derive(Clone, Copy, PartialEq)]
48pub enum ExceptionHandling {
49 Report,
51 Rethrow,
53}
54
55#[derive(JSTraceable, MallocSizeOf)]
58#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
59pub struct CallbackObject<D: DomTypes> {
60 #[ignore_malloc_size_of = "measured by mozjs"]
62 callback: Heap<*mut JSObject>,
63 #[ignore_malloc_size_of = "measured by mozjs"]
64 permanent_js_root: Heap<JSVal>,
65
66 incumbent: Option<Dom<D::GlobalScope>>,
78}
79
80impl<D: DomTypes> CallbackObject<D> {
81 #[allow(clippy::new_without_default)]
83 fn new() -> Self {
84 Self {
85 callback: Heap::default(),
86 permanent_js_root: Heap::default(),
87 incumbent: D::GlobalScope::incumbent().map(|i| Dom::from_ref(&*i)),
88 }
89 }
90
91 pub fn get(&self) -> *mut JSObject {
92 self.callback.get()
93 }
94
95 #[expect(unsafe_code)]
96 unsafe fn init(&mut self, cx: JSContext, callback: *mut JSObject) {
97 self.callback.set(callback);
98 self.permanent_js_root.set(ObjectValue(callback));
99 unsafe {
100 assert!(AddRawValueRoot(
101 *cx,
102 self.permanent_js_root.get_unsafe(),
103 c"CallbackObject::root".as_ptr()
104 ));
105 }
106 }
107}
108
109impl<D: DomTypes> Drop for CallbackObject<D> {
110 #[expect(unsafe_code)]
111 fn drop(&mut self) {
112 unsafe {
113 if let Some(cx) = Runtime::get() {
114 RemoveRawValueRoot(cx.as_ptr(), self.permanent_js_root.get_unsafe());
115 }
116 }
117 }
118}
119
120impl<D: DomTypes> PartialEq for CallbackObject<D> {
121 fn eq(&self, other: &CallbackObject<D>) -> bool {
122 self.callback.get() == other.callback.get()
123 }
124}
125
126pub trait CallbackContainer<D: DomTypes> {
129 unsafe fn new(cx: JSContext, callback: *mut JSObject) -> Rc<Self>;
134 fn callback_holder(&self) -> &CallbackObject<D>;
136 fn callback(&self) -> *mut JSObject {
138 self.callback_holder().get()
139 }
140 fn incumbent(&self) -> Option<&D::GlobalScope> {
145 self.callback_holder().incumbent.as_deref()
146 }
147}
148
149#[derive(JSTraceable, MallocSizeOf, PartialEq)]
151#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
152pub struct CallbackFunction<D: DomTypes> {
153 object: CallbackObject<D>,
154}
155
156impl<D: DomTypes> CallbackFunction<D> {
157 #[expect(clippy::new_without_default)]
160 pub fn new() -> Self {
161 Self {
162 object: CallbackObject::new(),
163 }
164 }
165
166 pub fn callback_holder(&self) -> &CallbackObject<D> {
168 &self.object
169 }
170
171 pub unsafe fn init(&mut self, cx: JSContext, callback: *mut JSObject) {
177 unsafe { self.object.init(cx, callback) };
178 }
179}
180
181#[derive(JSTraceable, MallocSizeOf, PartialEq)]
183#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
184pub struct CallbackInterface<D: DomTypes> {
185 object: CallbackObject<D>,
186}
187
188impl<D: DomTypes> CallbackInterface<D> {
189 #[expect(clippy::new_without_default)]
192 pub fn new() -> Self {
193 Self {
194 object: CallbackObject::new(),
195 }
196 }
197
198 pub fn callback_holder(&self) -> &CallbackObject<D> {
200 &self.object
201 }
202
203 pub unsafe fn init(&mut self, cx: JSContext, callback: *mut JSObject) {
209 unsafe { self.object.init(cx, callback) };
210 }
211
212 pub fn get_callable_property(&self, cx: JSContext, name: &str) -> Fallible<JSVal> {
215 rooted!(in(*cx) let mut callable = UndefinedValue());
216 rooted!(in(*cx) let obj = self.callback_holder().get());
217 unsafe {
218 let c_name = CString::new(name).unwrap();
219 if !JS_GetProperty(*cx, obj.handle(), c_name.as_ptr(), callable.handle_mut()) {
220 return Err(Error::JSFailed);
221 }
222
223 if !callable.is_object() || !IsCallable(callable.to_object()) {
224 return Err(Error::Type(format!(
225 "The value of the {} property is not callable",
226 name
227 )));
228 }
229 }
230 Ok(callable.get())
231 }
232}
233
234pub(crate) fn wrap_call_this_value<T: ThisReflector>(
236 cx: JSContext,
237 p: &T,
238 mut rval: MutableHandleValue,
239) -> bool {
240 rooted!(in(*cx) let mut obj = p.jsobject());
241
242 if obj.is_null() {
243 rval.set(NullValue());
244 return true;
245 }
246
247 unsafe {
248 if !JS_WrapObject(*cx, obj.handle_mut()) {
249 return false;
250 }
251 }
252
253 rval.set(ObjectValue(*obj));
254 true
255}
256
257pub struct CallSetup<D: DomTypes> {
260 exception_global: DomRoot<D::GlobalScope>,
263 cx: JSContext,
265 old_realm: *mut Realm,
267 handling: ExceptionHandling,
269 entry_script: Option<GenericAutoEntryScript<D>>,
272 incumbent_script: Option<GenericAutoIncumbentScript<D>>,
275}
276
277impl<D: DomTypes> CallSetup<D> {
278 pub fn new<T: CallbackContainer<D>>(callback: &T, handling: ExceptionHandling) -> Self {
280 let global = unsafe { D::GlobalScope::from_object(callback.callback()) };
281 if let Some(window) = global.downcast::<D::Window>() {
282 window.Document().ensure_safe_to_run_script_or_layout();
283 }
284 let cx = D::GlobalScope::get_cx();
285
286 let aes = GenericAutoEntryScript::<D>::new(&global);
287 let ais = callback.incumbent().map(GenericAutoIncumbentScript::new);
288 CallSetup {
289 exception_global: global,
290 cx,
291 old_realm: unsafe { EnterRealm(*cx, callback.callback()) },
292 handling,
293 entry_script: Some(aes),
294 incumbent_script: ais,
295 }
296 }
297
298 pub fn get_context(&self) -> JSContext {
300 self.cx
301 }
302}
303
304impl<D: DomTypes> Drop for CallSetup<D> {
305 fn drop(&mut self) {
306 unsafe {
307 LeaveRealm(*self.cx, self.old_realm);
308 }
309 if self.handling == ExceptionHandling::Report {
310 let ar = enter_realm::<D>(&*self.exception_global);
311 <D as DomHelpers<D>>::report_pending_exception(
312 self.cx,
313 true,
314 InRealm::Entered(&ar),
315 CanGc::note(),
316 );
317 }
318 drop(self.incumbent_script.take());
319 drop(self.entry_script.take().unwrap());
320 }
321}