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_NewStringCopyN, JS_SetReservedSlot, JSClass,
18 JSClassOps, JSContext, JSFUN_CONSTRUCTOR, JSFunctionSpec, JSObject, JSPROP_PERMANENT,
19 JSPROP_READONLY, JSPROP_RESOLVING, JSPropertySpec, JSString, JSTracer, ObjectOps,
20 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_DefineProperty3, JS_DefineProperty4,
26 JS_DefineProperty5, JS_DefinePropertyById5, JS_FireOnNewGlobalObject, JS_IterateCompartments,
27 JS_LinkConstructorAndPrototype, JS_NewFunction, JS_NewGlobalObject, JS_SetTrustedPrincipals,
28 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::js::{JS_DefineProperty, JS_NewObject, JS_NewObjectWithGivenProto, PropertyFlags};
43use crate::principals::ServoJSPrincipals;
44use crate::utils::{
45 DOM_PROTOTYPE_SLOT, DOMJSClass, JSCLASS_DOM_GLOBAL, ProtoOrIfaceArray, get_proto_or_iface_array,
46};
47
48#[derive(Clone, Copy)]
50pub(crate) struct NonCallbackInterfaceObjectClass {
51 pub(crate) _class: JSClass,
53 pub(crate) _proto_id: PrototypeList::ID,
55 pub(crate) _proto_depth: u16,
57 pub(crate) representation: &'static [u8],
59}
60
61unsafe impl Sync for NonCallbackInterfaceObjectClass {}
62
63impl NonCallbackInterfaceObjectClass {
64 pub(crate) const fn new(
66 constructor_behavior: &'static InterfaceConstructorBehavior,
67 string_rep: &'static [u8],
68 proto_id: PrototypeList::ID,
69 proto_depth: u16,
70 ) -> NonCallbackInterfaceObjectClass {
71 NonCallbackInterfaceObjectClass {
72 _class: JSClass {
73 name: c"Function".as_ptr(),
74 flags: 0,
75 cOps: &constructor_behavior.0,
76 spec: ptr::null(),
77 ext: ptr::null(),
78 oOps: &OBJECT_OPS,
79 },
80 _proto_id: proto_id,
81 _proto_depth: proto_depth,
82 representation: string_rep,
83 }
84 }
85
86 pub(crate) fn as_jsclass(&self) -> &JSClass {
88 unsafe { &*(self as *const _ as *const JSClass) }
89 }
90}
91
92pub(crate) type ConstructorClassHook =
94 unsafe extern "C" fn(cx: *mut JSContext, argc: u32, vp: *mut Value) -> bool;
95
96pub(crate) struct InterfaceConstructorBehavior(JSClassOps);
98
99impl InterfaceConstructorBehavior {
100 pub(crate) const fn throw() -> Self {
102 InterfaceConstructorBehavior(JSClassOps {
103 addProperty: None,
104 delProperty: None,
105 enumerate: None,
106 newEnumerate: None,
107 resolve: None,
108 mayResolve: None,
109 finalize: None,
110 call: Some(invalid_constructor),
111 construct: Some(invalid_constructor),
112 trace: None,
113 })
114 }
115
116 pub(crate) const fn call(hook: ConstructorClassHook) -> Self {
118 InterfaceConstructorBehavior(JSClassOps {
119 addProperty: None,
120 delProperty: None,
121 enumerate: None,
122 newEnumerate: None,
123 resolve: None,
124 mayResolve: None,
125 finalize: None,
126 call: Some(non_new_constructor),
127 construct: Some(hook),
128 trace: None,
129 })
130 }
131}
132
133pub(crate) type TraceHook = unsafe extern "C" fn(trc: *mut JSTracer, obj: *mut JSObject);
135
136pub(crate) unsafe fn create_global_object<D: DomTypes>(
138 cx: &mut js::context::JSContext,
139 class: &'static JSClass,
140 private: *const libc::c_void,
141 trace: TraceHook,
142 mut rval: MutableHandleObject,
143 origin: &MutableOrigin,
144 use_system_compartment: bool,
145) {
146 assert!(rval.is_null());
147
148 let mut options = RealmOptions::default();
149 options.creationOptions_.traceGlobal_ = Some(trace);
150 options.creationOptions_.sharedMemoryAndAtomics_ = false;
151 if use_system_compartment {
152 options.creationOptions_.compSpec_ = CompartmentSpecifier::NewCompartmentAndZone;
153 options.creationOptions_.__bindgen_anon_1.comp_ = std::ptr::null_mut();
154 } else {
155 select_compartment(cx, &mut options);
156 }
157
158 let principal = ServoJSPrincipals::new::<D>(origin);
163 if use_system_compartment {
164 unsafe { JS_SetTrustedPrincipals(cx, principal.as_raw()) };
170 }
171
172 rval.set(unsafe {
173 JS_NewGlobalObject(
174 cx,
175 class,
176 principal.as_raw(),
177 OnNewGlobalHookOption::DontFireOnNewGlobalHook,
178 &*options,
179 )
180 });
181 assert!(!rval.is_null());
182
183 let private_val = PrivateValue(private);
186 unsafe { JS_SetReservedSlot(rval.get(), DOM_OBJECT_SLOT, &private_val) };
187 let proto_array: Box<ProtoOrIfaceArray> =
188 Box::new([ptr::null_mut::<JSObject>(); PrototypeList::PROTO_OR_IFACE_LENGTH]);
189 let val = PrivateValue(Box::into_raw(proto_array) as *const libc::c_void);
190 unsafe { JS_SetReservedSlot(rval.get(), DOM_PROTOTYPE_SLOT, &val) };
191
192 let mut cx = AutoRealm::new_from_handle(cx, rval.handle());
193 let cx = &mut cx;
194
195 unsafe { JS_FireOnNewGlobalObject(cx, rval.handle()) };
196}
197
198fn select_compartment(cx: &mut js::context::JSContext, options: &mut RealmOptions) {
200 type Data = *mut Compartment;
201 unsafe extern "C" fn callback(
202 _cx: *mut JSContext,
203 data: *mut libc::c_void,
204 compartment: *mut Compartment,
205 ) -> CompartmentIterResult {
206 let data = data as *mut Data;
207
208 unsafe {
209 if !IsSharableCompartment(compartment) || IsSystemCompartment(compartment) {
210 return CompartmentIterResult::KeepGoing;
211 }
212
213 *data = compartment;
216 }
217
218 CompartmentIterResult::Stop
219 }
220
221 let mut compartment: Data = ptr::null_mut();
222 unsafe {
223 JS_IterateCompartments(
224 cx,
225 (&mut compartment) as *mut Data as *mut libc::c_void,
226 Some(callback),
227 );
228 }
229
230 if compartment.is_null() {
231 options.creationOptions_.compSpec_ = CompartmentSpecifier::NewCompartmentAndZone;
232 } else {
233 options.creationOptions_.compSpec_ = CompartmentSpecifier::ExistingCompartment;
234 options.creationOptions_.__bindgen_anon_1.comp_ = compartment;
235 }
236}
237
238pub(crate) fn create_callback_interface_object<D: DomTypes>(
240 cx: &mut js::context::JSContext,
241 global: HandleObject,
242 constants: &[Guard<&[ConstantSpec]>],
243 name: &CStr,
244 mut rval: MutableHandleObject,
245) {
246 assert!(!constants.is_empty());
247 rval.set(JS_NewObject(cx, None));
248 assert!(!rval.is_null());
249 define_guarded_constants::<D>(cx, rval.handle(), constants, global);
250 define_name(cx, rval.handle(), name);
251 define_on_global_object(cx, global, name, rval.handle());
252}
253
254#[expect(clippy::too_many_arguments)]
256pub(crate) fn create_interface_prototype_object<D: DomTypes>(
257 cx: &mut js::context::JSContext,
258 global: HandleObject,
259 proto: HandleObject,
260 class: &'static JSClass,
261 regular_methods: &[Guard<&'static [JSFunctionSpec]>],
262 regular_properties: &[Guard<&'static [JSPropertySpec]>],
263 constants: &[Guard<&[ConstantSpec]>],
264 unscopable_names: &[&CStr],
265 mut rval: MutableHandleObject,
266) {
267 create_object::<D>(
268 cx,
269 global,
270 proto,
271 class,
272 regular_methods,
273 regular_properties,
274 constants,
275 rval.reborrow(),
276 );
277
278 if !unscopable_names.is_empty() {
279 rooted!(&in(cx) let mut unscopable_obj = ptr::null_mut::<JSObject>());
280 create_unscopable_object(cx, unscopable_names, unscopable_obj.handle_mut());
281 unsafe {
282 let unscopable_symbol = GetWellKnownSymbol(cx, SymbolCode::unscopables);
283 assert!(!unscopable_symbol.is_null());
284
285 rooted!(&in(cx) let mut unscopable_id: jsid);
286 RUST_SYMBOL_TO_JSID(unscopable_symbol, unscopable_id.handle_mut());
287
288 assert!(JS_DefinePropertyById5(
289 cx,
290 rval.handle(),
291 unscopable_id.handle(),
292 unscopable_obj.handle(),
293 JSPROP_READONLY as u32
294 ))
295 }
296 }
297}
298
299#[expect(clippy::too_many_arguments)]
301pub(crate) fn create_noncallback_interface_object<D: DomTypes>(
302 cx: &mut js::context::JSContext,
303 global: HandleObject,
304 proto: HandleObject,
305 class: &'static NonCallbackInterfaceObjectClass,
306 static_methods: &[Guard<&'static [JSFunctionSpec]>],
307 static_properties: &[Guard<&'static [JSPropertySpec]>],
308 constants: &[Guard<&[ConstantSpec]>],
309 interface_prototype_object: HandleObject,
310 name: &CStr,
311 length: u32,
312 legacy_window_alias_names: &[&CStr],
313 mut rval: MutableHandleObject,
314) {
315 create_object::<D>(
316 cx,
317 global,
318 proto,
319 class.as_jsclass(),
320 static_methods,
321 static_properties,
322 constants,
323 rval.reborrow(),
324 );
325 unsafe {
326 assert!(JS_LinkConstructorAndPrototype(
327 cx,
328 rval.handle(),
329 interface_prototype_object
330 ));
331 }
332 define_name(cx, rval.handle(), name);
333 define_length(cx, rval.handle(), i32::try_from(length).expect("overflow"));
334 define_on_global_object(cx, global, name, rval.handle());
335
336 if is_exposed_in(global, Globals::WINDOW) {
337 for legacy_window_alias in legacy_window_alias_names {
338 define_on_global_object(cx, global, legacy_window_alias, rval.handle());
339 }
340 }
341}
342
343pub(crate) fn create_named_constructors(
345 cx: &mut js::context::JSContext,
346 global: HandleObject,
347 named_constructors: &[(ConstructorClassHook, &CStr, u32)],
348 interface_prototype_object: HandleObject,
349) {
350 rooted!(&in(cx) let mut constructor = ptr::null_mut::<JSObject>());
351
352 for &(native, name, arity) in named_constructors {
353 unsafe {
354 let fun = JS_NewFunction(cx, Some(native), arity, JSFUN_CONSTRUCTOR, name.as_ptr());
355 assert!(!fun.is_null());
356 constructor.set(JS_GetFunctionObject(fun));
357 assert!(!constructor.is_null());
358
359 assert!(JS_DefineProperty3(
360 cx,
361 constructor.handle(),
362 c"prototype".as_ptr(),
363 interface_prototype_object,
364 (JSPROP_PERMANENT | JSPROP_READONLY) as u32
365 ));
366 }
367
368 define_on_global_object(cx, global, name, constructor.handle());
369 }
370}
371
372#[expect(clippy::too_many_arguments)]
374pub(crate) fn create_object<D: DomTypes>(
375 cx: &mut js::context::JSContext,
376 global: HandleObject,
377 proto: HandleObject,
378 class: &'static JSClass,
379 methods: &[Guard<&'static [JSFunctionSpec]>],
380 properties: &[Guard<&'static [JSPropertySpec]>],
381 constants: &[Guard<&[ConstantSpec]>],
382 mut rval: MutableHandleObject,
383) {
384 rval.set(JS_NewObjectWithGivenProto(cx, Some(class), proto));
385 assert!(!rval.is_null());
386 define_guarded_methods::<D>(cx, rval.handle(), methods, global);
387 define_guarded_properties::<D>(cx, rval.handle(), properties, global);
388 define_guarded_constants::<D>(cx, rval.handle(), constants, global);
389}
390
391pub(crate) fn define_guarded_constants<D: DomTypes>(
393 cx: &mut js::context::JSContext,
394 obj: HandleObject,
395 constants: &[Guard<&[ConstantSpec]>],
396 global: HandleObject,
397) {
398 for guard in constants {
399 if let Some(specs) = guard.expose::<D>(cx, obj, global) {
400 define_constants(cx, obj, specs);
401 }
402 }
403}
404
405pub(crate) fn define_guarded_methods<D: DomTypes>(
407 cx: &mut js::context::JSContext,
408 obj: HandleObject,
409 methods: &[Guard<&'static [JSFunctionSpec]>],
410 global: HandleObject,
411) {
412 for guard in methods {
413 if let Some(specs) = guard.expose::<D>(cx, obj, global) {
414 unsafe {
415 define_methods(cx, obj, specs).unwrap();
416 }
417 }
418 }
419}
420
421pub(crate) fn define_guarded_properties<D: DomTypes>(
423 cx: &mut js::context::JSContext,
424 obj: HandleObject,
425 properties: &[Guard<&'static [JSPropertySpec]>],
426 global: HandleObject,
427) {
428 for guard in properties {
429 if let Some(specs) = guard.expose::<D>(cx, obj, global) {
430 unsafe {
431 define_properties(cx, obj, specs).unwrap();
432 }
433 }
434 }
435}
436
437pub(crate) fn is_exposed_in(object: HandleObject, globals: Globals) -> bool {
440 unsafe {
441 let unwrapped = UncheckedUnwrapObject(object.get(), false);
442 let dom_class = get_dom_class(unwrapped).unwrap();
443 globals.contains(dom_class.global)
444 }
445}
446
447pub(crate) fn define_on_global_object(
450 cx: &mut js::context::JSContext,
451 global: HandleObject,
452 name: &CStr,
453 obj: HandleObject,
454) {
455 unsafe {
456 assert!(JS_DefineProperty3(
457 cx,
458 global,
459 name.as_ptr(),
460 obj,
461 JSPROP_RESOLVING
462 ));
463 }
464}
465
466const OBJECT_OPS: ObjectOps = ObjectOps {
467 lookupProperty: None,
468 defineProperty: None,
469 hasProperty: None,
470 getProperty: None,
471 setProperty: None,
472 getOwnPropertyDescriptor: None,
473 deleteProperty: None,
474 getElements: None,
475 funToString: Some(fun_to_string_hook),
476};
477
478unsafe extern "C" fn fun_to_string_hook(
479 cx: *mut JSContext,
480 obj: RawHandleObject,
481 _is_to_source: bool,
482) -> *mut JSString {
483 unsafe {
484 let js_class = get_object_class(obj.get());
485 assert!(!js_class.is_null());
486 let repr = (*(js_class as *const NonCallbackInterfaceObjectClass)).representation;
487 assert!(!repr.is_empty());
488 let ret = JS_NewStringCopyN(cx, repr.as_ptr() as *const libc::c_char, repr.len());
489 assert!(!ret.is_null());
490 ret
491 }
492}
493
494fn create_unscopable_object(
495 cx: &mut js::context::JSContext,
496 names: &[&CStr],
497 mut rval: MutableHandleObject,
498) {
499 assert!(!names.is_empty());
500 assert!(rval.is_null());
501 unsafe {
502 rval.set(JS_NewObjectWithGivenProto(cx, None, HandleObject::null()));
503 assert!(!rval.is_null());
504 for &name in names {
505 assert!(JS_DefineProperty(
506 cx,
507 rval.handle(),
508 name,
509 HandleValue::from_raw(TrueHandleValue),
510 PropertyFlags::ENUMERABLE | PropertyFlags::WRITABLE | PropertyFlags::CONFIGURABLE,
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(&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(&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 !crate::js::JS_GetProperty(
690 cx,
691 original_new_target.handle(),
692 c"prototype",
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, desired_proto);
727 Ok(())
728 }
729}