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 unsafe { JS_SetTrustedPrincipals(cx, principal.as_raw()) };
169 }
170
171 rval.set(unsafe {
172 JS_NewGlobalObject(
173 cx,
174 class,
175 principal.as_raw(),
176 OnNewGlobalHookOption::DontFireOnNewGlobalHook,
177 &*options,
178 )
179 });
180 assert!(!rval.is_null());
181
182 let private_val = PrivateValue(private);
185 unsafe { 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 unsafe { JS_SetReservedSlot(rval.get(), DOM_PROTOTYPE_SLOT, &val) };
190
191 let mut cx = AutoRealm::new_from_handle(cx, rval.handle());
192 let cx = &mut cx;
193
194 unsafe { JS_FireOnNewGlobalObject(cx, rval.handle()) };
195}
196
197fn select_compartment(cx: &mut js::context::JSContext, options: &mut RealmOptions) {
199 type Data = *mut Compartment;
200 unsafe extern "C" fn callback(
201 _cx: *mut JSContext,
202 data: *mut libc::c_void,
203 compartment: *mut Compartment,
204 ) -> CompartmentIterResult {
205 let data = data as *mut Data;
206
207 unsafe {
208 if !IsSharableCompartment(compartment) || IsSystemCompartment(compartment) {
209 return CompartmentIterResult::KeepGoing;
210 }
211
212 *data = compartment;
215 }
216
217 CompartmentIterResult::Stop
218 }
219
220 let mut compartment: Data = ptr::null_mut();
221 unsafe {
222 JS_IterateCompartments(
223 cx,
224 (&mut compartment) as *mut Data as *mut libc::c_void,
225 Some(callback),
226 );
227 }
228
229 if compartment.is_null() {
230 options.creationOptions_.compSpec_ = CompartmentSpecifier::NewCompartmentAndZone;
231 } else {
232 options.creationOptions_.compSpec_ = CompartmentSpecifier::ExistingCompartment;
233 options.creationOptions_.__bindgen_anon_1.comp_ = compartment;
234 }
235}
236
237pub(crate) fn create_callback_interface_object<D: DomTypes>(
239 cx: &mut js::context::JSContext,
240 global: HandleObject,
241 constants: &[Guard<&[ConstantSpec]>],
242 name: &CStr,
243 mut rval: MutableHandleObject,
244) {
245 assert!(!constants.is_empty());
246 unsafe {
247 rval.set(JS_NewObject(cx.raw_cx(), ptr::null()));
248 }
249 assert!(!rval.is_null());
250 define_guarded_constants::<D>(cx, rval.handle(), constants, global);
251 define_name(cx, rval.handle(), name);
252 define_on_global_object(cx, global, name, rval.handle());
253}
254
255#[expect(clippy::too_many_arguments)]
257pub(crate) fn create_interface_prototype_object<D: DomTypes>(
258 cx: &mut js::context::JSContext,
259 global: HandleObject,
260 proto: HandleObject,
261 class: &'static JSClass,
262 regular_methods: &[Guard<&'static [JSFunctionSpec]>],
263 regular_properties: &[Guard<&'static [JSPropertySpec]>],
264 constants: &[Guard<&[ConstantSpec]>],
265 unscopable_names: &[&CStr],
266 mut rval: MutableHandleObject,
267) {
268 create_object::<D>(
269 cx,
270 global,
271 proto,
272 class,
273 regular_methods,
274 regular_properties,
275 constants,
276 rval.reborrow(),
277 );
278
279 if !unscopable_names.is_empty() {
280 rooted!(&in(cx) let mut unscopable_obj = ptr::null_mut::<JSObject>());
281 create_unscopable_object(cx, unscopable_names, unscopable_obj.handle_mut());
282 unsafe {
283 let unscopable_symbol = GetWellKnownSymbol(cx, SymbolCode::unscopables);
284 assert!(!unscopable_symbol.is_null());
285
286 rooted!(&in(cx) let mut unscopable_id: jsid);
287 RUST_SYMBOL_TO_JSID(unscopable_symbol, unscopable_id.handle_mut());
288
289 assert!(JS_DefinePropertyById5(
290 cx,
291 rval.handle(),
292 unscopable_id.handle(),
293 unscopable_obj.handle(),
294 JSPROP_READONLY as u32
295 ))
296 }
297 }
298}
299
300#[expect(clippy::too_many_arguments)]
302pub(crate) fn create_noncallback_interface_object<D: DomTypes>(
303 cx: &mut js::context::JSContext,
304 global: HandleObject,
305 proto: HandleObject,
306 class: &'static NonCallbackInterfaceObjectClass,
307 static_methods: &[Guard<&'static [JSFunctionSpec]>],
308 static_properties: &[Guard<&'static [JSPropertySpec]>],
309 constants: &[Guard<&[ConstantSpec]>],
310 interface_prototype_object: HandleObject,
311 name: &CStr,
312 length: u32,
313 legacy_window_alias_names: &[&CStr],
314 mut rval: MutableHandleObject,
315) {
316 create_object::<D>(
317 cx,
318 global,
319 proto,
320 class.as_jsclass(),
321 static_methods,
322 static_properties,
323 constants,
324 rval.reborrow(),
325 );
326 unsafe {
327 assert!(JS_LinkConstructorAndPrototype(
328 cx,
329 rval.handle(),
330 interface_prototype_object
331 ));
332 }
333 define_name(cx, rval.handle(), name);
334 define_length(cx, rval.handle(), i32::try_from(length).expect("overflow"));
335 define_on_global_object(cx, global, name, rval.handle());
336
337 if is_exposed_in(global, Globals::WINDOW) {
338 for legacy_window_alias in legacy_window_alias_names {
339 define_on_global_object(cx, global, legacy_window_alias, rval.handle());
340 }
341 }
342}
343
344pub(crate) fn create_named_constructors(
346 cx: &mut js::context::JSContext,
347 global: HandleObject,
348 named_constructors: &[(ConstructorClassHook, &CStr, u32)],
349 interface_prototype_object: HandleObject,
350) {
351 rooted!(&in(cx) let mut constructor = ptr::null_mut::<JSObject>());
352
353 for &(native, name, arity) in named_constructors {
354 unsafe {
355 let fun = JS_NewFunction(cx, Some(native), arity, JSFUN_CONSTRUCTOR, name.as_ptr());
356 assert!(!fun.is_null());
357 constructor.set(JS_GetFunctionObject(fun));
358 assert!(!constructor.is_null());
359
360 assert!(JS_DefineProperty3(
361 cx,
362 constructor.handle(),
363 c"prototype".as_ptr(),
364 interface_prototype_object,
365 (JSPROP_PERMANENT | JSPROP_READONLY) as u32
366 ));
367 }
368
369 define_on_global_object(cx, global, name, constructor.handle());
370 }
371}
372
373#[expect(clippy::too_many_arguments)]
375pub(crate) fn create_object<D: DomTypes>(
376 cx: &mut js::context::JSContext,
377 global: HandleObject,
378 proto: HandleObject,
379 class: &'static JSClass,
380 methods: &[Guard<&'static [JSFunctionSpec]>],
381 properties: &[Guard<&'static [JSPropertySpec]>],
382 constants: &[Guard<&[ConstantSpec]>],
383 mut rval: MutableHandleObject,
384) {
385 unsafe {
386 rval.set(JS_NewObjectWithGivenProto(cx, class, proto));
387 }
388 assert!(!rval.is_null());
389 define_guarded_methods::<D>(cx, rval.handle(), methods, global);
390 define_guarded_properties::<D>(cx, rval.handle(), properties, global);
391 define_guarded_constants::<D>(cx, rval.handle(), constants, global);
392}
393
394pub(crate) fn define_guarded_constants<D: DomTypes>(
396 cx: &mut js::context::JSContext,
397 obj: HandleObject,
398 constants: &[Guard<&[ConstantSpec]>],
399 global: HandleObject,
400) {
401 for guard in constants {
402 if let Some(specs) = guard.expose::<D>(cx, obj, global) {
403 define_constants(cx, obj, specs);
404 }
405 }
406}
407
408pub(crate) fn define_guarded_methods<D: DomTypes>(
410 cx: &mut js::context::JSContext,
411 obj: HandleObject,
412 methods: &[Guard<&'static [JSFunctionSpec]>],
413 global: HandleObject,
414) {
415 for guard in methods {
416 if let Some(specs) = guard.expose::<D>(cx, obj, global) {
417 unsafe {
418 define_methods(cx, obj, specs).unwrap();
419 }
420 }
421 }
422}
423
424pub(crate) fn define_guarded_properties<D: DomTypes>(
426 cx: &mut js::context::JSContext,
427 obj: HandleObject,
428 properties: &[Guard<&'static [JSPropertySpec]>],
429 global: HandleObject,
430) {
431 for guard in properties {
432 if let Some(specs) = guard.expose::<D>(cx, obj, global) {
433 unsafe {
434 define_properties(cx, obj, specs).unwrap();
435 }
436 }
437 }
438}
439
440pub(crate) fn is_exposed_in(object: HandleObject, globals: Globals) -> bool {
443 unsafe {
444 let unwrapped = UncheckedUnwrapObject(object.get(), false);
445 let dom_class = get_dom_class(unwrapped).unwrap();
446 globals.contains(dom_class.global)
447 }
448}
449
450pub(crate) fn define_on_global_object(
453 cx: &mut js::context::JSContext,
454 global: HandleObject,
455 name: &CStr,
456 obj: HandleObject,
457) {
458 unsafe {
459 assert!(JS_DefineProperty3(
460 cx,
461 global,
462 name.as_ptr(),
463 obj,
464 JSPROP_RESOLVING
465 ));
466 }
467}
468
469const OBJECT_OPS: ObjectOps = ObjectOps {
470 lookupProperty: None,
471 defineProperty: None,
472 hasProperty: None,
473 getProperty: None,
474 setProperty: None,
475 getOwnPropertyDescriptor: None,
476 deleteProperty: None,
477 getElements: None,
478 funToString: Some(fun_to_string_hook),
479};
480
481unsafe extern "C" fn fun_to_string_hook(
482 cx: *mut JSContext,
483 obj: RawHandleObject,
484 _is_to_source: bool,
485) -> *mut JSString {
486 unsafe {
487 let js_class = get_object_class(obj.get());
488 assert!(!js_class.is_null());
489 let repr = (*(js_class as *const NonCallbackInterfaceObjectClass)).representation;
490 assert!(!repr.is_empty());
491 let ret = JS_NewStringCopyN(cx, repr.as_ptr() as *const libc::c_char, repr.len());
492 assert!(!ret.is_null());
493 ret
494 }
495}
496
497fn create_unscopable_object(
498 cx: &mut js::context::JSContext,
499 names: &[&CStr],
500 mut rval: MutableHandleObject,
501) {
502 assert!(!names.is_empty());
503 assert!(rval.is_null());
504 unsafe {
505 rval.set(JS_NewObjectWithGivenProto(
506 cx,
507 ptr::null(),
508 HandleObject::null(),
509 ));
510 assert!(!rval.is_null());
511 for &name in names {
512 assert!(JS_DefineProperty(
513 cx,
514 rval.handle(),
515 name.as_ptr(),
516 HandleValue::from_raw(TrueHandleValue),
517 JSPROP_ENUMERATE as u32,
518 ));
519 }
520 }
521}
522
523fn define_name(cx: &mut js::context::JSContext, obj: HandleObject, name: &CStr) {
524 unsafe {
525 rooted!(&in(cx) let name = JS_AtomizeAndPinString(cx, name.as_ptr()));
526 assert!(!name.is_null());
527 assert!(JS_DefineProperty4(
528 cx,
529 obj,
530 c"name".as_ptr(),
531 name.handle(),
532 JSPROP_READONLY as u32
533 ));
534 }
535}
536
537fn define_length(cx: &mut js::context::JSContext, obj: HandleObject, length: i32) {
538 unsafe {
539 assert!(JS_DefineProperty5(
540 cx,
541 obj,
542 c"length".as_ptr(),
543 length,
544 JSPROP_READONLY as u32
545 ));
546 }
547}
548
549unsafe extern "C" fn invalid_constructor(
550 cx: *mut JSContext,
551 _argc: libc::c_uint,
552 _vp: *mut JSVal,
553) -> bool {
554 let mut cx = unsafe { js::context::JSContext::from_ptr(NonNull::new(cx).unwrap()) };
556 throw_type_error(&mut cx, c"Illegal constructor.");
557 false
558}
559
560unsafe extern "C" fn non_new_constructor(
561 cx: *mut JSContext,
562 _argc: libc::c_uint,
563 _vp: *mut JSVal,
564) -> bool {
565 let mut cx = unsafe { js::context::JSContext::from_ptr(NonNull::new(cx).unwrap()) };
567 throw_type_error(&mut cx, c"This constructor needs to be called with `new`.");
568 false
569}
570
571pub(crate) enum ProtoOrIfaceIndex {
572 ID(PrototypeList::ID),
573 Constructor(PrototypeList::Constructor),
574}
575
576impl From<ProtoOrIfaceIndex> for usize {
577 fn from(index: ProtoOrIfaceIndex) -> usize {
578 match index {
579 ProtoOrIfaceIndex::ID(id) => id as usize,
580 ProtoOrIfaceIndex::Constructor(constructor) => constructor as usize,
581 }
582 }
583}
584
585pub(crate) fn get_per_interface_object_handle(
586 cx: &mut js::context::JSContext,
587 global: HandleObject,
588 id: ProtoOrIfaceIndex,
589 creator: unsafe fn(&mut js::context::JSContext, HandleObject, *mut ProtoOrIfaceArray),
590 mut rval: MutableHandleObject,
591) {
592 unsafe {
593 assert!(((*get_object_class(global.get())).flags & JSCLASS_DOM_GLOBAL) != 0);
594
595 let proto_or_iface_array = get_proto_or_iface_array(global.get());
597 let index: usize = id.into();
598 rval.set((*proto_or_iface_array)[index]);
599 if !rval.get().is_null() {
600 return;
601 }
602
603 creator(cx, global, proto_or_iface_array);
604 rval.set((*proto_or_iface_array)[index]);
605 assert!(!rval.get().is_null());
606 }
607}
608
609pub(crate) fn define_dom_interface(
610 cx: &mut js::context::JSContext,
611 global: HandleObject,
612 id: ProtoOrIfaceIndex,
613 creator: unsafe fn(&mut js::context::JSContext, HandleObject, *mut ProtoOrIfaceArray),
614 enabled: fn(&mut js::context::JSContext, HandleObject) -> bool,
615) {
616 assert!(!global.get().is_null());
617
618 if !enabled(cx, global) {
619 return;
620 }
621
622 rooted!(&in(cx) let mut proto = ptr::null_mut::<JSObject>());
623 get_per_interface_object_handle(cx, global, id, creator, proto.handle_mut());
624 assert!(!proto.is_null());
625}
626
627fn get_proto_id_for_new_target(new_target: HandleObject) -> Option<PrototypeList::ID> {
628 unsafe {
629 let new_target_class = get_object_class(*new_target);
630 if is_dom_class(&*new_target_class) {
631 let domjsclass: *const DOMJSClass = new_target_class as *const DOMJSClass;
632 let dom_class = &(*domjsclass).dom_class;
633 return Some(dom_class.interface_chain[dom_class.depth as usize]);
634 }
635 None
636 }
637}
638
639#[allow(clippy::result_unit_err)]
640pub fn get_desired_proto(
641 cx: &mut js::context::JSContext,
642 args: &CallArgs,
643 proto_id: PrototypeList::ID,
644 creator: unsafe fn(&mut js::context::JSContext, HandleObject, *mut ProtoOrIfaceArray),
645 mut desired_proto: MutableHandleObject,
646) -> Result<(), ()> {
647 unsafe {
648 assert!(args.is_constructing());
653
654 rooted!(&in(cx) let mut new_target = args.new_target().to_object());
664 rooted!(&in(cx) let original_new_target = *new_target);
665 let target_proto_id = get_proto_id_for_new_target(new_target.handle()).or_else(|| {
668 new_target.set(CheckedUnwrapStatic(*new_target));
672 if !new_target.is_null() && *new_target != *original_new_target {
673 get_proto_id_for_new_target(new_target.handle())
674 } else {
675 None
676 }
677 });
678
679 if let Some(proto_id) = target_proto_id {
680 let global = GetNonCCWObjectGlobal(*new_target);
681 let proto_or_iface_cache = get_proto_or_iface_array(global);
682 desired_proto.set((*proto_or_iface_cache)[proto_id as usize]);
683 if *new_target != *original_new_target &&
684 !js::rust::wrappers2::JS_WrapObject(cx, desired_proto)
685 {
686 return Err(());
687 }
688 return Ok(());
689 }
690
691 rooted!(&in(cx) let mut proto_val = NullValue());
696 if !js::rust::wrappers2::JS_GetProperty(
697 cx,
698 original_new_target.handle(),
699 c"prototype".as_ptr(),
700 proto_val.handle_mut(),
701 ) {
702 return Err(());
703 }
704
705 if proto_val.is_object() {
706 desired_proto.set(proto_val.to_object());
707 return Ok(());
708 }
709
710 let realm = js::rust::wrappers2::GetFunctionRealm(cx, new_target.handle());
713
714 if realm.is_null() {
715 return Err(());
716 }
717
718 {
719 let mut realm = AutoRealm::new(cx, NonNull::new(GetRealmGlobalOrNull(realm)).unwrap());
720 let (global, realm) = realm.global_and_reborrow();
721 get_per_interface_object_handle(
722 realm,
723 global,
724 ProtoOrIfaceIndex::ID(proto_id),
725 creator,
726 desired_proto.reborrow(),
727 );
728 if desired_proto.is_null() {
729 return Err(());
730 }
731 }
732
733 maybe_wrap_object(cx, desired_proto);
734 Ok(())
735 }
736}