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_NewObject, JS_NewStringCopyN, JS_SetReservedSlot,
18 JSClass, JSClassOps, JSContext, JSFUN_CONSTRUCTOR, JSFunctionSpec, JSObject, JSPROP_ENUMERATE,
19 JSPROP_PERMANENT, JSPROP_READONLY, JSPROP_RESOLVING, JSPropertySpec, JSString, JSTracer,
20 ObjectOps, OnNewGlobalHookOption, SymbolCode, TrueHandleValue, Value, jsid,
21};
22use js::jsval::{JSVal, NullValue, PrivateValue};
23use js::realm::AutoRealm;
24use js::rust::wrappers2::{
25 GetWellKnownSymbol, JS_AtomizeAndPinString, JS_DefineProperty, JS_DefineProperty3,
26 JS_DefineProperty4, JS_DefineProperty5, JS_DefinePropertyById5, JS_FireOnNewGlobalObject,
27 JS_IterateCompartments, JS_LinkConstructorAndPrototype, JS_NewFunction, JS_NewGlobalObject,
28 JS_NewObjectWithGivenProto, JS_SetTrustedPrincipals, RUST_SYMBOL_TO_JSID,
29};
30use js::rust::{
31 HandleObject, HandleValue, MutableHandleObject, RealmOptions, define_methods,
32 define_properties, get_object_class, is_dom_class, maybe_wrap_object,
33};
34use servo_url::MutableOrigin;
35
36use crate::DomTypes;
37use crate::codegen::Globals::Globals;
38use crate::codegen::PrototypeList;
39use crate::constant::{ConstantSpec, define_constants};
40use crate::conversions::{DOM_OBJECT_SLOT, get_dom_class};
41use crate::guard::Guard;
42use crate::principals::ServoJSPrincipals;
43use crate::utils::{
44 DOM_PROTOTYPE_SLOT, DOMJSClass, JSCLASS_DOM_GLOBAL, ProtoOrIfaceArray, get_proto_or_iface_array,
45};
46
47#[derive(Clone, Copy)]
49pub(crate) struct NonCallbackInterfaceObjectClass {
50 pub(crate) _class: JSClass,
52 pub(crate) _proto_id: PrototypeList::ID,
54 pub(crate) _proto_depth: u16,
56 pub(crate) representation: &'static [u8],
58}
59
60unsafe impl Sync for NonCallbackInterfaceObjectClass {}
61
62impl NonCallbackInterfaceObjectClass {
63 pub(crate) const fn new(
65 constructor_behavior: &'static InterfaceConstructorBehavior,
66 string_rep: &'static [u8],
67 proto_id: PrototypeList::ID,
68 proto_depth: u16,
69 ) -> NonCallbackInterfaceObjectClass {
70 NonCallbackInterfaceObjectClass {
71 _class: JSClass {
72 name: c"Function".as_ptr(),
73 flags: 0,
74 cOps: &constructor_behavior.0,
75 spec: ptr::null(),
76 ext: ptr::null(),
77 oOps: &OBJECT_OPS,
78 },
79 _proto_id: proto_id,
80 _proto_depth: proto_depth,
81 representation: string_rep,
82 }
83 }
84
85 pub(crate) fn as_jsclass(&self) -> &JSClass {
87 unsafe { &*(self as *const _ as *const JSClass) }
88 }
89}
90
91pub(crate) type ConstructorClassHook =
93 unsafe extern "C" fn(cx: *mut JSContext, argc: u32, vp: *mut Value) -> bool;
94
95pub(crate) struct InterfaceConstructorBehavior(JSClassOps);
97
98impl InterfaceConstructorBehavior {
99 pub(crate) const fn throw() -> Self {
101 InterfaceConstructorBehavior(JSClassOps {
102 addProperty: None,
103 delProperty: None,
104 enumerate: None,
105 newEnumerate: None,
106 resolve: None,
107 mayResolve: None,
108 finalize: None,
109 call: Some(invalid_constructor),
110 construct: Some(invalid_constructor),
111 trace: None,
112 })
113 }
114
115 pub(crate) const fn call(hook: ConstructorClassHook) -> Self {
117 InterfaceConstructorBehavior(JSClassOps {
118 addProperty: None,
119 delProperty: None,
120 enumerate: None,
121 newEnumerate: None,
122 resolve: None,
123 mayResolve: None,
124 finalize: None,
125 call: Some(non_new_constructor),
126 construct: Some(hook),
127 trace: None,
128 })
129 }
130}
131
132pub(crate) type TraceHook = unsafe extern "C" fn(trc: *mut JSTracer, obj: *mut JSObject);
134
135pub(crate) unsafe fn create_global_object<D: DomTypes>(
137 cx: &mut js::context::JSContext,
138 class: &'static JSClass,
139 private: *const libc::c_void,
140 trace: TraceHook,
141 mut rval: MutableHandleObject,
142 origin: &MutableOrigin,
143 use_system_compartment: bool,
144) {
145 assert!(rval.is_null());
146
147 let mut options = RealmOptions::default();
148 options.creationOptions_.traceGlobal_ = Some(trace);
149 options.creationOptions_.sharedMemoryAndAtomics_ = false;
150 if use_system_compartment {
151 options.creationOptions_.compSpec_ = CompartmentSpecifier::NewCompartmentAndZone;
152 options.creationOptions_.__bindgen_anon_1.comp_ = std::ptr::null_mut();
153 } else {
154 select_compartment(cx, &mut options);
155 }
156
157 let principal = ServoJSPrincipals::new::<D>(origin);
162 if use_system_compartment {
163 JS_SetTrustedPrincipals(cx, principal.as_raw());
169 }
170
171 rval.set(JS_NewGlobalObject(
172 cx,
173 class,
174 principal.as_raw(),
175 OnNewGlobalHookOption::DontFireOnNewGlobalHook,
176 &*options,
177 ));
178 assert!(!rval.is_null());
179
180 let private_val = PrivateValue(private);
183 JS_SetReservedSlot(rval.get(), DOM_OBJECT_SLOT, &private_val);
184 let proto_array: Box<ProtoOrIfaceArray> =
185 Box::new([ptr::null_mut::<JSObject>(); PrototypeList::PROTO_OR_IFACE_LENGTH]);
186 let val = PrivateValue(Box::into_raw(proto_array) as *const libc::c_void);
187 JS_SetReservedSlot(rval.get(), DOM_PROTOTYPE_SLOT, &val);
188
189 let mut cx = AutoRealm::new_from_handle(cx, rval.handle());
190 let cx = &mut cx;
191
192 JS_FireOnNewGlobalObject(cx, rval.handle());
193}
194
195fn select_compartment(cx: &mut js::context::JSContext, 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}