1use std::convert::TryFrom;
8use std::ffi::CStr;
9use std::ptr::{self, NonNull};
10
11use js::error::throw_type_error_safe;
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 let mut cx = unsafe { js::context::JSContext::from_ptr(NonNull::new(cx).unwrap()) };
549 throw_type_error_safe(&mut cx, c"Illegal constructor.");
550 false
551}
552
553unsafe extern "C" fn non_new_constructor(
554 cx: *mut JSContext,
555 _argc: libc::c_uint,
556 _vp: *mut JSVal,
557) -> bool {
558 let mut cx = unsafe { js::context::JSContext::from_ptr(NonNull::new(cx).unwrap()) };
560 throw_type_error_safe(&mut cx, c"This constructor needs to be called with `new`.");
561 false
562}
563
564pub(crate) enum ProtoOrIfaceIndex {
565 ID(PrototypeList::ID),
566 Constructor(PrototypeList::Constructor),
567}
568
569impl From<ProtoOrIfaceIndex> for usize {
570 fn from(index: ProtoOrIfaceIndex) -> usize {
571 match index {
572 ProtoOrIfaceIndex::ID(id) => id as usize,
573 ProtoOrIfaceIndex::Constructor(constructor) => constructor as usize,
574 }
575 }
576}
577
578pub(crate) fn get_per_interface_object_handle(
579 cx: &mut js::context::JSContext,
580 global: HandleObject,
581 id: ProtoOrIfaceIndex,
582 creator: unsafe fn(&mut js::context::JSContext, HandleObject, *mut ProtoOrIfaceArray),
583 mut rval: MutableHandleObject,
584) {
585 unsafe {
586 assert!(((*get_object_class(global.get())).flags & JSCLASS_DOM_GLOBAL) != 0);
587
588 let proto_or_iface_array = get_proto_or_iface_array(global.get());
590 let index: usize = id.into();
591 rval.set((*proto_or_iface_array)[index]);
592 if !rval.get().is_null() {
593 return;
594 }
595
596 creator(cx, global, proto_or_iface_array);
597 rval.set((*proto_or_iface_array)[index]);
598 assert!(!rval.get().is_null());
599 }
600}
601
602pub(crate) fn define_dom_interface(
603 cx: &mut js::context::JSContext,
604 global: HandleObject,
605 id: ProtoOrIfaceIndex,
606 creator: unsafe fn(&mut js::context::JSContext, HandleObject, *mut ProtoOrIfaceArray),
607 enabled: fn(&mut js::context::JSContext, HandleObject) -> bool,
608) {
609 assert!(!global.get().is_null());
610
611 if !enabled(cx, global) {
612 return;
613 }
614
615 rooted!(&in(cx) let mut proto = ptr::null_mut::<JSObject>());
616 get_per_interface_object_handle(cx, global, id, creator, proto.handle_mut());
617 assert!(!proto.is_null());
618}
619
620fn get_proto_id_for_new_target(new_target: HandleObject) -> Option<PrototypeList::ID> {
621 unsafe {
622 let new_target_class = get_object_class(*new_target);
623 if is_dom_class(&*new_target_class) {
624 let domjsclass: *const DOMJSClass = new_target_class as *const DOMJSClass;
625 let dom_class = &(*domjsclass).dom_class;
626 return Some(dom_class.interface_chain[dom_class.depth as usize]);
627 }
628 None
629 }
630}
631
632#[allow(clippy::result_unit_err)]
633pub fn get_desired_proto(
634 cx: &mut js::context::JSContext,
635 args: &CallArgs,
636 proto_id: PrototypeList::ID,
637 creator: unsafe fn(&mut js::context::JSContext, HandleObject, *mut ProtoOrIfaceArray),
638 mut desired_proto: MutableHandleObject,
639) -> Result<(), ()> {
640 unsafe {
641 assert!(args.is_constructing());
646
647 rooted!(&in(cx) let mut new_target = args.new_target().to_object());
657 rooted!(&in(cx) let original_new_target = *new_target);
658 let target_proto_id = get_proto_id_for_new_target(new_target.handle()).or_else(|| {
661 new_target.set(CheckedUnwrapStatic(*new_target));
665 if !new_target.is_null() && *new_target != *original_new_target {
666 get_proto_id_for_new_target(new_target.handle())
667 } else {
668 None
669 }
670 });
671
672 if let Some(proto_id) = target_proto_id {
673 let global = GetNonCCWObjectGlobal(*new_target);
674 let proto_or_iface_cache = get_proto_or_iface_array(global);
675 desired_proto.set((*proto_or_iface_cache)[proto_id as usize]);
676 if *new_target != *original_new_target &&
677 !js::rust::wrappers2::JS_WrapObject(cx, desired_proto)
678 {
679 return Err(());
680 }
681 return Ok(());
682 }
683
684 rooted!(&in(cx) let mut proto_val = NullValue());
689 if !js::rust::wrappers2::JS_GetProperty(
690 cx,
691 original_new_target.handle(),
692 c"prototype".as_ptr(),
693 proto_val.handle_mut(),
694 ) {
695 return Err(());
696 }
697
698 if proto_val.is_object() {
699 desired_proto.set(proto_val.to_object());
700 return Ok(());
701 }
702
703 let realm = js::rust::wrappers2::GetFunctionRealm(cx, new_target.handle());
706
707 if realm.is_null() {
708 return Err(());
709 }
710
711 {
712 let mut realm = AutoRealm::new(cx, NonNull::new(GetRealmGlobalOrNull(realm)).unwrap());
713 let (global, realm) = realm.global_and_reborrow();
714 get_per_interface_object_handle(
715 realm,
716 global,
717 ProtoOrIfaceIndex::ID(proto_id),
718 creator,
719 desired_proto.reborrow(),
720 );
721 if desired_proto.is_null() {
722 return Err(());
723 }
724 }
725
726 maybe_wrap_object(cx.raw_cx(), desired_proto);
727 Ok(())
728 }
729}