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};
29use crate::utils::AsCCharPtrPtr;
30
31pub trait ThisReflector {
32 fn jsobject(&self) -> *mut JSObject;
33}
34
35impl<T: DomObject> ThisReflector for T {
36 fn jsobject(&self) -> *mut JSObject {
37 self.reflector().get_jsobject().get()
38 }
39}
40
41impl ThisReflector for HandleObject<'_> {
42 fn jsobject(&self) -> *mut JSObject {
43 self.get()
44 }
45}
46
47#[derive(Clone, Copy, PartialEq)]
49pub enum ExceptionHandling {
50 Report,
52 Rethrow,
54}
55
56#[derive(JSTraceable, MallocSizeOf)]
59#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
60pub struct CallbackObject<D: DomTypes> {
61 #[ignore_malloc_size_of = "measured by mozjs"]
63 callback: Heap<*mut JSObject>,
64 #[ignore_malloc_size_of = "measured by mozjs"]
65 permanent_js_root: Heap<JSVal>,
66
67 incumbent: Option<Dom<D::GlobalScope>>,
79}
80
81impl<D: DomTypes> CallbackObject<D> {
82 #[allow(clippy::new_without_default)]
84 fn new() -> Self {
85 Self {
86 callback: Heap::default(),
87 permanent_js_root: Heap::default(),
88 incumbent: D::GlobalScope::incumbent().map(|i| Dom::from_ref(&*i)),
89 }
90 }
91
92 pub fn get(&self) -> *mut JSObject {
93 self.callback.get()
94 }
95
96 #[expect(unsafe_code)]
97 unsafe fn init(&mut self, cx: JSContext, callback: *mut JSObject) {
98 self.callback.set(callback);
99 self.permanent_js_root.set(ObjectValue(callback));
100 unsafe {
101 assert!(AddRawValueRoot(
102 *cx,
103 self.permanent_js_root.get_unsafe(),
104 b"CallbackObject::root\n".as_c_char_ptr()
105 ));
106 }
107 }
108}
109
110impl<D: DomTypes> Drop for CallbackObject<D> {
111 #[expect(unsafe_code)]
112 fn drop(&mut self) {
113 unsafe {
114 if let Some(cx) = Runtime::get() {
115 RemoveRawValueRoot(cx.as_ptr(), self.permanent_js_root.get_unsafe());
116 }
117 }
118 }
119}
120
121impl<D: DomTypes> PartialEq for CallbackObject<D> {
122 fn eq(&self, other: &CallbackObject<D>) -> bool {
123 self.callback.get() == other.callback.get()
124 }
125}
126
127pub trait CallbackContainer<D: DomTypes> {
130 unsafe fn new(cx: JSContext, callback: *mut JSObject) -> Rc<Self>;
135 fn callback_holder(&self) -> &CallbackObject<D>;
137 fn callback(&self) -> *mut JSObject {
139 self.callback_holder().get()
140 }
141 fn incumbent(&self) -> Option<&D::GlobalScope> {
146 self.callback_holder().incumbent.as_deref()
147 }
148}
149
150#[derive(JSTraceable, MallocSizeOf, PartialEq)]
152#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
153pub struct CallbackFunction<D: DomTypes> {
154 object: CallbackObject<D>,
155}
156
157impl<D: DomTypes> CallbackFunction<D> {
158 #[expect(clippy::new_without_default)]
161 pub fn new() -> Self {
162 Self {
163 object: CallbackObject::new(),
164 }
165 }
166
167 pub fn callback_holder(&self) -> &CallbackObject<D> {
169 &self.object
170 }
171
172 pub unsafe fn init(&mut self, cx: JSContext, callback: *mut JSObject) {
178 unsafe { self.object.init(cx, callback) };
179 }
180}
181
182#[derive(JSTraceable, MallocSizeOf, PartialEq)]
184#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
185pub struct CallbackInterface<D: DomTypes> {
186 object: CallbackObject<D>,
187}
188
189impl<D: DomTypes> CallbackInterface<D> {
190 #[expect(clippy::new_without_default)]
193 pub fn new() -> Self {
194 Self {
195 object: CallbackObject::new(),
196 }
197 }
198
199 pub fn callback_holder(&self) -> &CallbackObject<D> {
201 &self.object
202 }
203
204 pub unsafe fn init(&mut self, cx: JSContext, callback: *mut JSObject) {
210 unsafe { self.object.init(cx, callback) };
211 }
212
213 pub fn get_callable_property(&self, cx: JSContext, name: &str) -> Fallible<JSVal> {
216 rooted!(in(*cx) let mut callable = UndefinedValue());
217 rooted!(in(*cx) let obj = self.callback_holder().get());
218 unsafe {
219 let c_name = CString::new(name).unwrap();
220 if !JS_GetProperty(*cx, obj.handle(), c_name.as_ptr(), callable.handle_mut()) {
221 return Err(Error::JSFailed);
222 }
223
224 if !callable.is_object() || !IsCallable(callable.to_object()) {
225 return Err(Error::Type(format!(
226 "The value of the {} property is not callable",
227 name
228 )));
229 }
230 }
231 Ok(callable.get())
232 }
233}
234
235pub(crate) fn wrap_call_this_value<T: ThisReflector>(
237 cx: JSContext,
238 p: &T,
239 mut rval: MutableHandleValue,
240) -> bool {
241 rooted!(in(*cx) let mut obj = p.jsobject());
242
243 if obj.is_null() {
244 rval.set(NullValue());
245 return true;
246 }
247
248 unsafe {
249 if !JS_WrapObject(*cx, obj.handle_mut()) {
250 return false;
251 }
252 }
253
254 rval.set(ObjectValue(*obj));
255 true
256}
257
258pub struct CallSetup<D: DomTypes> {
261 exception_global: DomRoot<D::GlobalScope>,
264 cx: JSContext,
266 old_realm: *mut Realm,
268 handling: ExceptionHandling,
270 entry_script: Option<GenericAutoEntryScript<D>>,
273 incumbent_script: Option<GenericAutoIncumbentScript<D>>,
276}
277
278impl<D: DomTypes> CallSetup<D> {
279 pub fn new<T: CallbackContainer<D>>(callback: &T, handling: ExceptionHandling) -> Self {
281 let global = unsafe { D::GlobalScope::from_object(callback.callback()) };
282 if let Some(window) = global.downcast::<D::Window>() {
283 window.Document().ensure_safe_to_run_script_or_layout();
284 }
285 let cx = D::GlobalScope::get_cx();
286
287 let aes = GenericAutoEntryScript::<D>::new(&global);
288 let ais = callback.incumbent().map(GenericAutoIncumbentScript::new);
289 CallSetup {
290 exception_global: global,
291 cx,
292 old_realm: unsafe { EnterRealm(*cx, callback.callback()) },
293 handling,
294 entry_script: Some(aes),
295 incumbent_script: ais,
296 }
297 }
298
299 pub fn get_context(&self) -> JSContext {
301 self.cx
302 }
303}
304
305impl<D: DomTypes> Drop for CallSetup<D> {
306 fn drop(&mut self) {
307 unsafe {
308 LeaveRealm(*self.cx, self.old_realm);
309 }
310 if self.handling == ExceptionHandling::Report {
311 let ar = enter_realm::<D>(&*self.exception_global);
312 <D as DomHelpers<D>>::report_pending_exception(
313 self.cx,
314 true,
315 InRealm::Entered(&ar),
316 CanGc::note(),
317 );
318 }
319 drop(self.incumbent_script.take());
320 drop(self.entry_script.take().unwrap());
321 }
322}