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