1use std::convert::TryFrom;
8use std::ffi::CStr;
9use std::ptr::{self, NonNull};
10
11use js::error::throw_type_error;
12use js::glue::UncheckedUnwrapObject;
13use js::jsapi::JS::CompartmentIterResult;
14use js::jsapi::{
15 CallArgs, CheckedUnwrapStatic, Compartment, CompartmentSpecifier, GetNonCCWObjectGlobal,
16 GetRealmGlobalOrNull, HandleObject as RawHandleObject, IsSharableCompartment,
17 IsSystemCompartment, JS_GetFunctionObject, JS_IterateCompartments, JS_NewGlobalObject,
18 JS_NewObject, JS_NewStringCopyN, JS_SetReservedSlot, JS_SetTrustedPrincipals, JSAutoRealm,
19 JSClass, JSClassOps, JSContext, JSFUN_CONSTRUCTOR, JSFunctionSpec, JSObject, JSPROP_ENUMERATE,
20 JSPROP_PERMANENT, JSPROP_READONLY, JSPROP_RESOLVING, JSPropertySpec, JSString, JSTracer,
21 ObjectOps, OnNewGlobalHookOption, SymbolCode, TrueHandleValue, Value, jsid,
22};
23use js::jsval::{JSVal, NullValue, PrivateValue};
24use js::realm::AutoRealm;
25use js::rust::wrappers::{JS_FireOnNewGlobalObject, RUST_SYMBOL_TO_JSID};
26use js::rust::wrappers2::{
27 GetWellKnownSymbol, JS_AtomizeAndPinString, JS_DefineProperty, JS_DefineProperty3,
28 JS_DefineProperty4, JS_DefineProperty5, JS_DefinePropertyById5, JS_LinkConstructorAndPrototype,
29 JS_NewFunction, JS_NewObjectWithGivenProto,
30};
31use js::rust::{
32 HandleObject, HandleValue, MutableHandleObject, RealmOptions, define_methods,
33 define_properties, get_object_class, is_dom_class, maybe_wrap_object,
34};
35use servo_url::MutableOrigin;
36
37use crate::DomTypes;
38use crate::codegen::Globals::Globals;
39use crate::codegen::PrototypeList;
40use crate::constant::{ConstantSpec, define_constants};
41use crate::conversions::{DOM_OBJECT_SLOT, get_dom_class};
42use crate::guard::Guard;
43use crate::principals::ServoJSPrincipals;
44use crate::script_runtime::JSContext as SafeJSContext;
45use crate::utils::{
46 DOM_PROTOTYPE_SLOT, DOMJSClass, JSCLASS_DOM_GLOBAL, ProtoOrIfaceArray, get_proto_or_iface_array,
47};
48
49#[derive(Clone, Copy)]
51pub(crate) struct NonCallbackInterfaceObjectClass {
52 pub(crate) _class: JSClass,
54 pub(crate) _proto_id: PrototypeList::ID,
56 pub(crate) _proto_depth: u16,
58 pub(crate) representation: &'static [u8],
60}
61
62unsafe impl Sync for NonCallbackInterfaceObjectClass {}
63
64impl NonCallbackInterfaceObjectClass {
65 pub(crate) const fn new(
67 constructor_behavior: &'static InterfaceConstructorBehavior,
68 string_rep: &'static [u8],
69 proto_id: PrototypeList::ID,
70 proto_depth: u16,
71 ) -> NonCallbackInterfaceObjectClass {
72 NonCallbackInterfaceObjectClass {
73 _class: JSClass {
74 name: c"Function".as_ptr(),
75 flags: 0,
76 cOps: &constructor_behavior.0,
77 spec: ptr::null(),
78 ext: ptr::null(),
79 oOps: &OBJECT_OPS,
80 },
81 _proto_id: proto_id,
82 _proto_depth: proto_depth,
83 representation: string_rep,
84 }
85 }
86
87 pub(crate) fn as_jsclass(&self) -> &JSClass {
89 unsafe { &*(self as *const _ as *const JSClass) }
90 }
91}
92
93pub(crate) type ConstructorClassHook =
95 unsafe extern "C" fn(cx: *mut JSContext, argc: u32, vp: *mut Value) -> bool;
96
97pub(crate) struct InterfaceConstructorBehavior(JSClassOps);
99
100impl InterfaceConstructorBehavior {
101 pub(crate) const fn throw() -> Self {
103 InterfaceConstructorBehavior(JSClassOps {
104 addProperty: None,
105 delProperty: None,
106 enumerate: None,
107 newEnumerate: None,
108 resolve: None,
109 mayResolve: None,
110 finalize: None,
111 call: Some(invalid_constructor),
112 construct: Some(invalid_constructor),
113 trace: None,
114 })
115 }
116
117 pub(crate) const fn call(hook: ConstructorClassHook) -> Self {
119 InterfaceConstructorBehavior(JSClassOps {
120 addProperty: None,
121 delProperty: None,
122 enumerate: None,
123 newEnumerate: None,
124 resolve: None,
125 mayResolve: None,
126 finalize: None,
127 call: Some(non_new_constructor),
128 construct: Some(hook),
129 trace: None,
130 })
131 }
132}
133
134pub(crate) type TraceHook = unsafe extern "C" fn(trc: *mut JSTracer, obj: *mut JSObject);
136
137pub(crate) unsafe fn create_global_object<D: DomTypes>(
139 cx: SafeJSContext,
140 class: &'static JSClass,
141 private: *const libc::c_void,
142 trace: TraceHook,
143 mut rval: MutableHandleObject,
144 origin: &MutableOrigin,
145 use_system_compartment: bool,
146) {
147 assert!(rval.is_null());
148
149 let mut options = RealmOptions::default();
150 options.creationOptions_.traceGlobal_ = Some(trace);
151 options.creationOptions_.sharedMemoryAndAtomics_ = false;
152 if use_system_compartment {
153 options.creationOptions_.compSpec_ = CompartmentSpecifier::NewCompartmentAndZone;
154 options.creationOptions_.__bindgen_anon_1.comp_ = std::ptr::null_mut();
155 } else {
156 select_compartment(cx, &mut options);
157 }
158
159 let principal = ServoJSPrincipals::new::<D>(origin);
164 if use_system_compartment {
165 JS_SetTrustedPrincipals(*cx, principal.as_raw());
171 }
172
173 rval.set(JS_NewGlobalObject(
174 *cx,
175 class,
176 principal.as_raw(),
177 OnNewGlobalHookOption::DontFireOnNewGlobalHook,
178 &*options,
179 ));
180 assert!(!rval.is_null());
181
182 let private_val = PrivateValue(private);
185 JS_SetReservedSlot(rval.get(), DOM_OBJECT_SLOT, &private_val);
186 let proto_array: Box<ProtoOrIfaceArray> =
187 Box::new([ptr::null_mut::<JSObject>(); PrototypeList::PROTO_OR_IFACE_LENGTH]);
188 let val = PrivateValue(Box::into_raw(proto_array) as *const libc::c_void);
189 JS_SetReservedSlot(rval.get(), DOM_PROTOTYPE_SLOT, &val);
190
191 let _ac = JSAutoRealm::new(*cx, rval.get());
192 JS_FireOnNewGlobalObject(*cx, rval.handle());
193}
194
195fn select_compartment(cx: SafeJSContext, options: &mut RealmOptions) {
197 type Data = *mut Compartment;
198 unsafe extern "C" fn callback(
199 _cx: *mut JSContext,
200 data: *mut libc::c_void,
201 compartment: *mut Compartment,
202 ) -> CompartmentIterResult {
203 let data = data as *mut Data;
204
205 if !IsSharableCompartment(compartment) || IsSystemCompartment(compartment) {
206 return CompartmentIterResult::KeepGoing;
207 }
208
209 *data = compartment;
212 CompartmentIterResult::Stop
213 }
214
215 let mut compartment: Data = ptr::null_mut();
216 unsafe {
217 JS_IterateCompartments(
218 *cx,
219 (&mut compartment) as *mut Data as *mut libc::c_void,
220 Some(callback),
221 );
222 }
223
224 if compartment.is_null() {
225 options.creationOptions_.compSpec_ = CompartmentSpecifier::NewCompartmentAndZone;
226 } else {
227 options.creationOptions_.compSpec_ = CompartmentSpecifier::ExistingCompartment;
228 options.creationOptions_.__bindgen_anon_1.comp_ = compartment;
229 }
230}
231
232pub(crate) fn create_callback_interface_object<D: DomTypes>(
234 cx: &mut js::context::JSContext,
235 global: HandleObject,
236 constants: &[Guard<&[ConstantSpec]>],
237 name: &CStr,
238 mut rval: MutableHandleObject,
239) {
240 assert!(!constants.is_empty());
241 unsafe {
242 rval.set(JS_NewObject(cx.raw_cx(), ptr::null()));
243 }
244 assert!(!rval.is_null());
245 define_guarded_constants::<D>(cx, rval.handle(), constants, global);
246 define_name(cx, rval.handle(), name);
247 define_on_global_object(cx, global, name, rval.handle());
248}
249
250#[expect(clippy::too_many_arguments)]
252pub(crate) fn create_interface_prototype_object<D: DomTypes>(
253 cx: &mut js::context::JSContext,
254 global: HandleObject,
255 proto: HandleObject,
256 class: &'static JSClass,
257 regular_methods: &[Guard<&'static [JSFunctionSpec]>],
258 regular_properties: &[Guard<&'static [JSPropertySpec]>],
259 constants: &[Guard<&[ConstantSpec]>],
260 unscopable_names: &[&CStr],
261 mut rval: MutableHandleObject,
262) {
263 create_object::<D>(
264 cx,
265 global,
266 proto,
267 class,
268 regular_methods,
269 regular_properties,
270 constants,
271 rval.reborrow(),
272 );
273
274 if !unscopable_names.is_empty() {
275 rooted!(&in(cx) let mut unscopable_obj = ptr::null_mut::<JSObject>());
276 create_unscopable_object(cx, unscopable_names, unscopable_obj.handle_mut());
277 unsafe {
278 let unscopable_symbol = GetWellKnownSymbol(cx, SymbolCode::unscopables);
279 assert!(!unscopable_symbol.is_null());
280
281 rooted!(&in(cx) let mut unscopable_id: jsid);
282 RUST_SYMBOL_TO_JSID(unscopable_symbol, unscopable_id.handle_mut());
283
284 assert!(JS_DefinePropertyById5(
285 cx,
286 rval.handle(),
287 unscopable_id.handle(),
288 unscopable_obj.handle(),
289 JSPROP_READONLY as u32
290 ))
291 }
292 }
293}
294
295#[expect(clippy::too_many_arguments)]
297pub(crate) fn create_noncallback_interface_object<D: DomTypes>(
298 cx: &mut js::context::JSContext,
299 global: HandleObject,
300 proto: HandleObject,
301 class: &'static NonCallbackInterfaceObjectClass,
302 static_methods: &[Guard<&'static [JSFunctionSpec]>],
303 static_properties: &[Guard<&'static [JSPropertySpec]>],
304 constants: &[Guard<&[ConstantSpec]>],
305 interface_prototype_object: HandleObject,
306 name: &CStr,
307 length: u32,
308 legacy_window_alias_names: &[&CStr],
309 mut rval: MutableHandleObject,
310) {
311 create_object::<D>(
312 cx,
313 global,
314 proto,
315 class.as_jsclass(),
316 static_methods,
317 static_properties,
318 constants,
319 rval.reborrow(),
320 );
321 unsafe {
322 assert!(JS_LinkConstructorAndPrototype(
323 cx,
324 rval.handle(),
325 interface_prototype_object
326 ));
327 }
328 define_name(cx, rval.handle(), name);
329 define_length(cx, rval.handle(), i32::try_from(length).expect("overflow"));
330 define_on_global_object(cx, global, name, rval.handle());
331
332 if is_exposed_in(global, Globals::WINDOW) {
333 for legacy_window_alias in legacy_window_alias_names {
334 define_on_global_object(cx, global, legacy_window_alias, rval.handle());
335 }
336 }
337}
338
339pub(crate) fn create_named_constructors(
341 cx: &mut js::context::JSContext,
342 global: HandleObject,
343 named_constructors: &[(ConstructorClassHook, &CStr, u32)],
344 interface_prototype_object: HandleObject,
345) {
346 rooted!(&in(cx) let mut constructor = ptr::null_mut::<JSObject>());
347
348 for &(native, name, arity) in named_constructors {
349 unsafe {
350 let fun = JS_NewFunction(cx, Some(native), arity, JSFUN_CONSTRUCTOR, name.as_ptr());
351 assert!(!fun.is_null());
352 constructor.set(JS_GetFunctionObject(fun));
353 assert!(!constructor.is_null());
354
355 assert!(JS_DefineProperty3(
356 cx,
357 constructor.handle(),
358 c"prototype".as_ptr(),
359 interface_prototype_object,
360 (JSPROP_PERMANENT | JSPROP_READONLY) as u32
361 ));
362 }
363
364 define_on_global_object(cx, global, name, constructor.handle());
365 }
366}
367
368#[expect(clippy::too_many_arguments)]
370pub(crate) fn create_object<D: DomTypes>(
371 cx: &mut js::context::JSContext,
372 global: HandleObject,
373 proto: HandleObject,
374 class: &'static JSClass,
375 methods: &[Guard<&'static [JSFunctionSpec]>],
376 properties: &[Guard<&'static [JSPropertySpec]>],
377 constants: &[Guard<&[ConstantSpec]>],
378 mut rval: MutableHandleObject,
379) {
380 unsafe {
381 rval.set(JS_NewObjectWithGivenProto(cx, class, proto));
382 }
383 assert!(!rval.is_null());
384 define_guarded_methods::<D>(cx, rval.handle(), methods, global);
385 define_guarded_properties::<D>(cx, rval.handle(), properties, global);
386 define_guarded_constants::<D>(cx, rval.handle(), constants, global);
387}
388
389pub(crate) fn define_guarded_constants<D: DomTypes>(
391 cx: &mut js::context::JSContext,
392 obj: HandleObject,
393 constants: &[Guard<&[ConstantSpec]>],
394 global: HandleObject,
395) {
396 for guard in constants {
397 if let Some(specs) = guard.expose::<D>(cx, obj, global) {
398 define_constants(cx, obj, specs);
399 }
400 }
401}
402
403pub(crate) fn define_guarded_methods<D: DomTypes>(
405 cx: &mut js::context::JSContext,
406 obj: HandleObject,
407 methods: &[Guard<&'static [JSFunctionSpec]>],
408 global: HandleObject,
409) {
410 for guard in methods {
411 if let Some(specs) = guard.expose::<D>(cx, obj, global) {
412 unsafe {
413 define_methods(cx.raw_cx(), obj, specs).unwrap();
414 }
415 }
416 }
417}
418
419pub(crate) fn define_guarded_properties<D: DomTypes>(
421 cx: &mut js::context::JSContext,
422 obj: HandleObject,
423 properties: &[Guard<&'static [JSPropertySpec]>],
424 global: HandleObject,
425) {
426 for guard in properties {
427 if let Some(specs) = guard.expose::<D>(cx, obj, global) {
428 unsafe {
429 define_properties(cx.raw_cx(), obj, specs).unwrap();
430 }
431 }
432 }
433}
434
435pub(crate) fn is_exposed_in(object: HandleObject, globals: Globals) -> bool {
438 unsafe {
439 let unwrapped = UncheckedUnwrapObject(object.get(), false);
440 let dom_class = get_dom_class(unwrapped).unwrap();
441 globals.contains(dom_class.global)
442 }
443}
444
445pub(crate) fn define_on_global_object(
448 cx: &mut js::context::JSContext,
449 global: HandleObject,
450 name: &CStr,
451 obj: HandleObject,
452) {
453 unsafe {
454 assert!(JS_DefineProperty3(
455 cx,
456 global,
457 name.as_ptr(),
458 obj,
459 JSPROP_RESOLVING
460 ));
461 }
462}
463
464const OBJECT_OPS: ObjectOps = ObjectOps {
465 lookupProperty: None,
466 defineProperty: None,
467 hasProperty: None,
468 getProperty: None,
469 setProperty: None,
470 getOwnPropertyDescriptor: None,
471 deleteProperty: None,
472 getElements: None,
473 funToString: Some(fun_to_string_hook),
474};
475
476unsafe extern "C" fn fun_to_string_hook(
477 cx: *mut JSContext,
478 obj: RawHandleObject,
479 _is_to_source: bool,
480) -> *mut JSString {
481 let js_class = get_object_class(obj.get());
482 assert!(!js_class.is_null());
483 let repr = (*(js_class as *const NonCallbackInterfaceObjectClass)).representation;
484 assert!(!repr.is_empty());
485 let ret = JS_NewStringCopyN(cx, repr.as_ptr() as *const libc::c_char, repr.len());
486 assert!(!ret.is_null());
487 ret
488}
489
490fn create_unscopable_object(
491 cx: &mut js::context::JSContext,
492 names: &[&CStr],
493 mut rval: MutableHandleObject,
494) {
495 assert!(!names.is_empty());
496 assert!(rval.is_null());
497 unsafe {
498 rval.set(JS_NewObjectWithGivenProto(
499 cx,
500 ptr::null(),
501 HandleObject::null(),
502 ));
503 assert!(!rval.is_null());
504 for &name in names {
505 assert!(JS_DefineProperty(
506 cx,
507 rval.handle(),
508 name.as_ptr(),
509 HandleValue::from_raw(TrueHandleValue),
510 JSPROP_ENUMERATE as u32,
511 ));
512 }
513 }
514}
515
516fn define_name(cx: &mut js::context::JSContext, obj: HandleObject, name: &CStr) {
517 unsafe {
518 rooted!(&in(cx) let name = JS_AtomizeAndPinString(cx, name.as_ptr()));
519 assert!(!name.is_null());
520 assert!(JS_DefineProperty4(
521 cx,
522 obj,
523 c"name".as_ptr(),
524 name.handle(),
525 JSPROP_READONLY as u32
526 ));
527 }
528}
529
530fn define_length(cx: &mut js::context::JSContext, obj: HandleObject, length: i32) {
531 unsafe {
532 assert!(JS_DefineProperty5(
533 cx,
534 obj,
535 c"length".as_ptr(),
536 length,
537 JSPROP_READONLY as u32
538 ));
539 }
540}
541
542unsafe extern "C" fn invalid_constructor(
543 cx: *mut JSContext,
544 _argc: libc::c_uint,
545 _vp: *mut JSVal,
546) -> bool {
547 throw_type_error(cx, c"Illegal constructor.");
548 false
549}
550
551unsafe extern "C" fn non_new_constructor(
552 cx: *mut JSContext,
553 _argc: libc::c_uint,
554 _vp: *mut JSVal,
555) -> bool {
556 throw_type_error(cx, c"This constructor needs to be called with `new`.");
557 false
558}
559
560pub(crate) enum ProtoOrIfaceIndex {
561 ID(PrototypeList::ID),
562 Constructor(PrototypeList::Constructor),
563}
564
565impl From<ProtoOrIfaceIndex> for usize {
566 fn from(index: ProtoOrIfaceIndex) -> usize {
567 match index {
568 ProtoOrIfaceIndex::ID(id) => id as usize,
569 ProtoOrIfaceIndex::Constructor(constructor) => constructor as usize,
570 }
571 }
572}
573
574pub(crate) fn get_per_interface_object_handle(
575 cx: &mut js::context::JSContext,
576 global: HandleObject,
577 id: ProtoOrIfaceIndex,
578 creator: unsafe fn(&mut js::context::JSContext, HandleObject, *mut ProtoOrIfaceArray),
579 mut rval: MutableHandleObject,
580) {
581 unsafe {
582 assert!(((*get_object_class(global.get())).flags & JSCLASS_DOM_GLOBAL) != 0);
583
584 let proto_or_iface_array = get_proto_or_iface_array(global.get());
586 let index: usize = id.into();
587 rval.set((*proto_or_iface_array)[index]);
588 if !rval.get().is_null() {
589 return;
590 }
591
592 creator(cx, global, proto_or_iface_array);
593 rval.set((*proto_or_iface_array)[index]);
594 assert!(!rval.get().is_null());
595 }
596}
597
598pub(crate) fn define_dom_interface(
599 cx: &mut js::context::JSContext,
600 global: HandleObject,
601 id: ProtoOrIfaceIndex,
602 creator: unsafe fn(&mut js::context::JSContext, HandleObject, *mut ProtoOrIfaceArray),
603 enabled: fn(&mut js::context::JSContext, HandleObject) -> bool,
604) {
605 assert!(!global.get().is_null());
606
607 if !enabled(cx, global) {
608 return;
609 }
610
611 rooted!(&in(cx) let mut proto = ptr::null_mut::<JSObject>());
612 get_per_interface_object_handle(cx, global, id, creator, proto.handle_mut());
613 assert!(!proto.is_null());
614}
615
616fn get_proto_id_for_new_target(new_target: HandleObject) -> Option<PrototypeList::ID> {
617 unsafe {
618 let new_target_class = get_object_class(*new_target);
619 if is_dom_class(&*new_target_class) {
620 let domjsclass: *const DOMJSClass = new_target_class as *const DOMJSClass;
621 let dom_class = &(*domjsclass).dom_class;
622 return Some(dom_class.interface_chain[dom_class.depth as usize]);
623 }
624 None
625 }
626}
627
628#[allow(clippy::result_unit_err)]
629pub fn get_desired_proto(
630 cx: &mut js::context::JSContext,
631 args: &CallArgs,
632 proto_id: PrototypeList::ID,
633 creator: unsafe fn(&mut js::context::JSContext, HandleObject, *mut ProtoOrIfaceArray),
634 mut desired_proto: MutableHandleObject,
635) -> Result<(), ()> {
636 unsafe {
637 assert!(args.is_constructing());
642
643 rooted!(&in(cx) let mut new_target = args.new_target().to_object());
653 rooted!(&in(cx) let original_new_target = *new_target);
654 let target_proto_id = get_proto_id_for_new_target(new_target.handle()).or_else(|| {
657 new_target.set(CheckedUnwrapStatic(*new_target));
661 if !new_target.is_null() && *new_target != *original_new_target {
662 get_proto_id_for_new_target(new_target.handle())
663 } else {
664 None
665 }
666 });
667
668 if let Some(proto_id) = target_proto_id {
669 let global = GetNonCCWObjectGlobal(*new_target);
670 let proto_or_iface_cache = get_proto_or_iface_array(global);
671 desired_proto.set((*proto_or_iface_cache)[proto_id as usize]);
672 if *new_target != *original_new_target &&
673 !js::rust::wrappers2::JS_WrapObject(cx, desired_proto)
674 {
675 return Err(());
676 }
677 return Ok(());
678 }
679
680 rooted!(&in(cx) let mut proto_val = NullValue());
685 if !js::rust::wrappers2::JS_GetProperty(
686 cx,
687 original_new_target.handle(),
688 c"prototype".as_ptr(),
689 proto_val.handle_mut(),
690 ) {
691 return Err(());
692 }
693
694 if proto_val.is_object() {
695 desired_proto.set(proto_val.to_object());
696 return Ok(());
697 }
698
699 let realm = js::rust::wrappers2::GetFunctionRealm(cx, new_target.handle());
702
703 if realm.is_null() {
704 return Err(());
705 }
706
707 {
708 let mut realm = AutoRealm::new(cx, NonNull::new(GetRealmGlobalOrNull(realm)).unwrap());
709 let (global, realm) = realm.global_and_reborrow();
710 get_per_interface_object_handle(
711 realm,
712 global,
713 ProtoOrIfaceIndex::ID(proto_id),
714 creator,
715 desired_proto.reborrow(),
716 );
717 if desired_proto.is_null() {
718 return Err(());
719 }
720 }
721
722 maybe_wrap_object(cx.raw_cx(), desired_proto);
723 Ok(())
724 }
725}