1use std::convert::TryFrom;
8use std::ffi::CStr;
9use std::ptr;
10
11use js::error::throw_type_error;
12use js::glue::UncheckedUnwrapObject;
13use js::jsapi::JS::CompartmentIterResult;
14use js::jsapi::{
15 CallArgs, CheckedUnwrapStatic, Compartment, CompartmentSpecifier, CurrentGlobalOrNull,
16 GetFunctionRealm, GetNonCCWObjectGlobal, GetRealmGlobalOrNull, GetWellKnownSymbol,
17 HandleObject as RawHandleObject, IsSharableCompartment, IsSystemCompartment,
18 JS_AtomizeAndPinString, JS_GetFunctionObject, JS_GetProperty, JS_IterateCompartments,
19 JS_NewFunction, JS_NewGlobalObject, JS_NewObject, JS_NewStringCopyN, JS_SetReservedSlot,
20 JS_SetTrustedPrincipals, JS_WrapObject, JSAutoRealm, JSClass, JSClassOps, JSContext,
21 JSFUN_CONSTRUCTOR, JSFunctionSpec, JSObject, JSPROP_ENUMERATE, JSPROP_PERMANENT,
22 JSPROP_READONLY, JSPROP_RESOLVING, JSPropertySpec, JSString, JSTracer, ObjectOps,
23 OnNewGlobalHookOption, SymbolCode, TrueHandleValue, Value, jsid,
24};
25use js::jsval::{JSVal, NullValue, PrivateValue};
26use js::rust::wrappers::{
27 JS_DefineProperty, JS_DefineProperty3, JS_DefineProperty4, JS_DefineProperty5,
28 JS_DefinePropertyById5, JS_FireOnNewGlobalObject, JS_LinkConstructorAndPrototype,
29 JS_NewObjectWithGivenProto, RUST_SYMBOL_TO_JSID,
30};
31use js::rust::{
32 HandleObject, HandleValue, MutableHandleObject, RealmOptions, define_methods,
33 define_properties, get_object_class, is_dom_class, maybe_wrap_object,
34};
35use servo_url::MutableOrigin;
36
37use crate::DomTypes;
38use crate::codegen::Globals::Globals;
39use crate::codegen::PrototypeList;
40use crate::constant::{ConstantSpec, define_constants};
41use crate::conversions::{DOM_OBJECT_SLOT, get_dom_class};
42use crate::guard::Guard;
43use crate::principals::ServoJSPrincipals;
44use crate::script_runtime::JSContext as SafeJSContext;
45use crate::utils::{
46 DOM_PROTOTYPE_SLOT, DOMJSClass, JSCLASS_DOM_GLOBAL, ProtoOrIfaceArray, get_proto_or_iface_array,
47};
48
49#[derive(Clone, Copy)]
51pub(crate) struct NonCallbackInterfaceObjectClass {
52 pub(crate) _class: JSClass,
54 pub(crate) _proto_id: PrototypeList::ID,
56 pub(crate) _proto_depth: u16,
58 pub(crate) representation: &'static [u8],
60}
61
62unsafe impl Sync for NonCallbackInterfaceObjectClass {}
63
64impl NonCallbackInterfaceObjectClass {
65 pub(crate) const fn new(
67 constructor_behavior: &'static InterfaceConstructorBehavior,
68 string_rep: &'static [u8],
69 proto_id: PrototypeList::ID,
70 proto_depth: u16,
71 ) -> NonCallbackInterfaceObjectClass {
72 NonCallbackInterfaceObjectClass {
73 _class: JSClass {
74 name: c"Function".as_ptr(),
75 flags: 0,
76 cOps: &constructor_behavior.0,
77 spec: 0 as *const _,
78 ext: 0 as *const _,
79 oOps: &OBJECT_OPS,
80 },
81 _proto_id: proto_id,
82 _proto_depth: proto_depth,
83 representation: string_rep,
84 }
85 }
86
87 pub(crate) fn as_jsclass(&self) -> &JSClass {
89 unsafe { &*(self as *const _ as *const JSClass) }
90 }
91}
92
93pub(crate) type ConstructorClassHook =
95 unsafe extern "C" fn(cx: *mut JSContext, argc: u32, vp: *mut Value) -> bool;
96
97pub(crate) struct InterfaceConstructorBehavior(JSClassOps);
99
100impl InterfaceConstructorBehavior {
101 pub(crate) const fn throw() -> Self {
103 InterfaceConstructorBehavior(JSClassOps {
104 addProperty: None,
105 delProperty: None,
106 enumerate: None,
107 newEnumerate: None,
108 resolve: None,
109 mayResolve: None,
110 finalize: None,
111 call: Some(invalid_constructor),
112 construct: Some(invalid_constructor),
113 trace: None,
114 })
115 }
116
117 pub(crate) const fn call(hook: ConstructorClassHook) -> Self {
119 InterfaceConstructorBehavior(JSClassOps {
120 addProperty: None,
121 delProperty: None,
122 enumerate: None,
123 newEnumerate: None,
124 resolve: None,
125 mayResolve: None,
126 finalize: None,
127 call: Some(non_new_constructor),
128 construct: Some(hook),
129 trace: None,
130 })
131 }
132}
133
134pub(crate) type TraceHook = unsafe extern "C" fn(trc: *mut JSTracer, obj: *mut JSObject);
136
137pub(crate) unsafe fn create_global_object<D: DomTypes>(
139 cx: SafeJSContext,
140 class: &'static JSClass,
141 private: *const libc::c_void,
142 trace: TraceHook,
143 mut rval: MutableHandleObject,
144 origin: &MutableOrigin,
145 use_system_compartment: bool,
146) {
147 assert!(rval.is_null());
148
149 let mut options = RealmOptions::default();
150 options.creationOptions_.traceGlobal_ = Some(trace);
151 options.creationOptions_.sharedMemoryAndAtomics_ = false;
152 if use_system_compartment {
153 options.creationOptions_.compSpec_ = CompartmentSpecifier::NewCompartmentAndZone;
154 options.creationOptions_.__bindgen_anon_1.comp_ = std::ptr::null_mut();
155 } else {
156 select_compartment(cx, &mut options);
157 }
158
159 let principal = ServoJSPrincipals::new::<D>(origin);
164 if use_system_compartment {
165 JS_SetTrustedPrincipals(*cx, principal.as_raw());
171 }
172
173 rval.set(JS_NewGlobalObject(
174 *cx,
175 class,
176 principal.as_raw(),
177 OnNewGlobalHookOption::DontFireOnNewGlobalHook,
178 &*options,
179 ));
180 assert!(!rval.is_null());
181
182 let private_val = PrivateValue(private);
185 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 JS_SetReservedSlot(rval.get(), DOM_PROTOTYPE_SLOT, &val);
190
191 let _ac = JSAutoRealm::new(*cx, rval.get());
192 JS_FireOnNewGlobalObject(*cx, rval.handle());
193}
194
195fn select_compartment(cx: SafeJSContext, 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: SafeJSContext,
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, 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#[allow(clippy::too_many_arguments)]
252pub(crate) fn create_interface_prototype_object<D: DomTypes>(
253 cx: SafeJSContext,
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#[allow(clippy::too_many_arguments)]
297pub(crate) fn create_noncallback_interface_object<D: DomTypes>(
298 cx: SafeJSContext,
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: SafeJSContext,
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#[allow(clippy::too_many_arguments)]
370pub(crate) fn create_object<D: DomTypes>(
371 cx: SafeJSContext,
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: SafeJSContext,
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: SafeJSContext,
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, obj, specs).unwrap();
414 }
415 }
416 }
417}
418
419pub(crate) fn define_guarded_properties<D: DomTypes>(
421 cx: SafeJSContext,
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, 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: SafeJSContext,
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(cx: SafeJSContext, names: &[&CStr], mut rval: MutableHandleObject) {
491 assert!(!names.is_empty());
492 assert!(rval.is_null());
493 unsafe {
494 rval.set(JS_NewObjectWithGivenProto(
495 *cx,
496 ptr::null(),
497 HandleObject::null(),
498 ));
499 assert!(!rval.is_null());
500 for &name in names {
501 assert!(JS_DefineProperty(
502 *cx,
503 rval.handle(),
504 name.as_ptr(),
505 HandleValue::from_raw(TrueHandleValue),
506 JSPROP_ENUMERATE as u32,
507 ));
508 }
509 }
510}
511
512fn define_name(cx: SafeJSContext, obj: HandleObject, name: &CStr) {
513 unsafe {
514 rooted!(in(*cx) let name = JS_AtomizeAndPinString(*cx, name.as_ptr()));
515 assert!(!name.is_null());
516 assert!(JS_DefineProperty4(
517 *cx,
518 obj,
519 c"name".as_ptr(),
520 name.handle(),
521 JSPROP_READONLY as u32
522 ));
523 }
524}
525
526fn define_length(cx: SafeJSContext, obj: HandleObject, length: i32) {
527 unsafe {
528 assert!(JS_DefineProperty5(
529 *cx,
530 obj,
531 c"length".as_ptr(),
532 length,
533 JSPROP_READONLY as u32
534 ));
535 }
536}
537
538unsafe extern "C" fn invalid_constructor(
539 cx: *mut JSContext,
540 _argc: libc::c_uint,
541 _vp: *mut JSVal,
542) -> bool {
543 throw_type_error(cx, "Illegal constructor.");
544 false
545}
546
547unsafe extern "C" fn non_new_constructor(
548 cx: *mut JSContext,
549 _argc: libc::c_uint,
550 _vp: *mut JSVal,
551) -> bool {
552 throw_type_error(cx, "This constructor needs to be called with `new`.");
553 false
554}
555
556pub(crate) enum ProtoOrIfaceIndex {
557 ID(PrototypeList::ID),
558 Constructor(PrototypeList::Constructor),
559}
560
561impl From<ProtoOrIfaceIndex> for usize {
562 fn from(index: ProtoOrIfaceIndex) -> usize {
563 match index {
564 ProtoOrIfaceIndex::ID(id) => id as usize,
565 ProtoOrIfaceIndex::Constructor(constructor) => constructor as usize,
566 }
567 }
568}
569
570pub(crate) fn get_per_interface_object_handle(
571 cx: SafeJSContext,
572 global: HandleObject,
573 id: ProtoOrIfaceIndex,
574 creator: unsafe fn(SafeJSContext, HandleObject, *mut ProtoOrIfaceArray),
575 mut rval: MutableHandleObject,
576) {
577 unsafe {
578 assert!(((*get_object_class(global.get())).flags & JSCLASS_DOM_GLOBAL) != 0);
579
580 let proto_or_iface_array = get_proto_or_iface_array(global.get());
582 let index: usize = id.into();
583 rval.set((*proto_or_iface_array)[index]);
584 if !rval.get().is_null() {
585 return;
586 }
587
588 creator(cx, global, proto_or_iface_array);
589 rval.set((*proto_or_iface_array)[index]);
590 assert!(!rval.get().is_null());
591 }
592}
593
594pub(crate) fn define_dom_interface(
595 cx: SafeJSContext,
596 global: HandleObject,
597 id: ProtoOrIfaceIndex,
598 creator: unsafe fn(SafeJSContext, HandleObject, *mut ProtoOrIfaceArray),
599 enabled: fn(SafeJSContext, HandleObject) -> bool,
600) {
601 assert!(!global.get().is_null());
602
603 if !enabled(cx, global) {
604 return;
605 }
606
607 rooted!(in(*cx) let mut proto = ptr::null_mut::<JSObject>());
608 get_per_interface_object_handle(cx, global, id, creator, proto.handle_mut());
609 assert!(!proto.is_null());
610}
611
612fn get_proto_id_for_new_target(new_target: HandleObject) -> Option<PrototypeList::ID> {
613 unsafe {
614 let new_target_class = get_object_class(*new_target);
615 if is_dom_class(&*new_target_class) {
616 let domjsclass: *const DOMJSClass = new_target_class as *const DOMJSClass;
617 let dom_class = &(*domjsclass).dom_class;
618 return Some(dom_class.interface_chain[dom_class.depth as usize]);
619 }
620 None
621 }
622}
623
624#[allow(clippy::result_unit_err)]
625pub fn get_desired_proto(
626 cx: SafeJSContext,
627 args: &CallArgs,
628 proto_id: PrototypeList::ID,
629 creator: unsafe fn(SafeJSContext, HandleObject, *mut ProtoOrIfaceArray),
630 mut desired_proto: MutableHandleObject,
631) -> Result<(), ()> {
632 unsafe {
633 assert!(args.is_constructing());
638
639 rooted!(in(*cx) let mut new_target = args.new_target().to_object());
649 rooted!(in(*cx) let original_new_target = *new_target);
650 let target_proto_id = get_proto_id_for_new_target(new_target.handle()).or_else(|| {
653 new_target.set(CheckedUnwrapStatic(*new_target));
657 if !new_target.is_null() && *new_target != *original_new_target {
658 get_proto_id_for_new_target(new_target.handle())
659 } else {
660 None
661 }
662 });
663
664 if let Some(proto_id) = target_proto_id {
665 let global = GetNonCCWObjectGlobal(*new_target);
666 let proto_or_iface_cache = get_proto_or_iface_array(global);
667 desired_proto.set((*proto_or_iface_cache)[proto_id as usize]);
668 if *new_target != *original_new_target && !JS_WrapObject(*cx, desired_proto.into()) {
669 return Err(());
670 }
671 return Ok(());
672 }
673
674 rooted!(in(*cx) let mut proto_val = NullValue());
679 if !JS_GetProperty(
680 *cx,
681 original_new_target.handle().into(),
682 c"prototype".as_ptr(),
683 proto_val.handle_mut().into(),
684 ) {
685 return Err(());
686 }
687
688 if proto_val.is_object() {
689 desired_proto.set(proto_val.to_object());
690 return Ok(());
691 }
692
693 let realm = GetFunctionRealm(*cx, new_target.handle().into());
696
697 if realm.is_null() {
698 return Err(());
699 }
700
701 {
702 let _realm = JSAutoRealm::new(*cx, GetRealmGlobalOrNull(realm));
703 rooted!(in(*cx) let global = CurrentGlobalOrNull(*cx));
704 get_per_interface_object_handle(
705 cx,
706 global.handle(),
707 ProtoOrIfaceIndex::ID(proto_id),
708 creator,
709 desired_proto.reborrow(),
710 );
711 if desired_proto.is_null() {
712 return Err(());
713 }
714 }
715
716 maybe_wrap_object(*cx, desired_proto);
717 Ok(())
718 }
719}