1pub use jsobject::{RecursionLimiter, Ref, RefMut};
6pub use operations::IntegrityLevel;
7pub use property_map::*;
8use thin_vec::ThinVec;
9
10use self::{internal_methods::ORDINARY_INTERNAL_METHODS, shape::Shape};
11use crate::{
12 builtins::{
13 function::{
14 arguments::{MappedArguments, UnmappedArguments},
15 ConstructorKind,
16 },
17 typed_array::{TypedArray, TypedArrayKind},
18 OrdinaryObject,
19 },
20 context::intrinsics::StandardConstructor,
21 js_string,
22 native_function::{NativeFunction, NativeFunctionObject},
23 property::{Attribute, PropertyDescriptor, PropertyKey},
24 realm::Realm,
25 string::StaticJsStrings,
26 Context, JsString, JsSymbol, JsValue,
27};
28
29use boa_gc::{Finalize, Trace};
30use std::{
31 any::{Any, TypeId},
32 fmt::Debug,
33};
34
35#[cfg(test)]
36mod tests;
37
38pub(crate) mod internal_methods;
39
40pub mod builtins;
41mod datatypes;
42mod jsobject;
43mod operations;
44mod property_map;
45pub mod shape;
46
47pub(crate) use builtins::*;
48
49pub use datatypes::JsData;
50pub use jsobject::*;
51
52pub const CONSTRUCTOR: JsString = js_string!("constructor");
54
55pub const PROTOTYPE: JsString = js_string!("prototype");
57
58pub type JsPrototype = Option<JsObject>;
62
63pub(crate) type ObjectStorage = Vec<JsValue>;
67
68pub trait NativeObject: Any + Trace + JsData {
72 fn as_any(&self) -> &dyn Any;
74
75 fn as_mut_any(&mut self) -> &mut dyn Any;
77
78 fn type_name_of_value(&self) -> &'static str;
80}
81
82impl<T: Any + Trace + JsData> NativeObject for T {
84 fn as_any(&self) -> &dyn Any {
85 self
86 }
87
88 fn as_mut_any(&mut self) -> &mut dyn Any {
89 self
90 }
91
92 fn type_name_of_value(&self) -> &'static str {
93 fn name_of_val<T: ?Sized>(_val: &T) -> &'static str {
94 std::any::type_name::<T>()
95 }
96
97 name_of_val(self)
98 }
99}
100
101impl dyn NativeObject {
103 #[inline]
105 pub fn is<T: NativeObject>(&self) -> bool {
106 let t = TypeId::of::<T>();
108
109 let concrete = self.type_id();
111
112 t == concrete
114 }
115
116 #[inline]
119 pub fn downcast_ref<T: NativeObject>(&self) -> Option<&T> {
120 if self.is::<T>() {
121 unsafe { Some(self.downcast_ref_unchecked()) }
125 } else {
126 None
127 }
128 }
129
130 #[inline]
133 pub fn downcast_mut<T: NativeObject>(&mut self) -> Option<&mut T> {
134 if self.is::<T>() {
135 unsafe { Some(self.downcast_mut_unchecked()) }
137 } else {
138 None
139 }
140 }
141
142 #[inline]
149 pub unsafe fn downcast_ref_unchecked<T: NativeObject>(&self) -> &T {
150 debug_assert!(self.is::<T>());
151 let ptr: *const dyn NativeObject = self;
152 unsafe { &*ptr.cast::<T>() }
154 }
155
156 #[inline]
163 pub unsafe fn downcast_mut_unchecked<T: NativeObject>(&mut self) -> &mut T {
164 debug_assert!(self.is::<T>());
165 let ptr: *mut dyn NativeObject = self;
167 unsafe { &mut *ptr.cast::<T>() }
168 }
169}
170
171#[derive(Debug, Finalize, Trace)]
173#[boa_gc(unsafe_no_drop)]
175pub struct Object<T: ?Sized> {
176 pub(crate) properties: PropertyMap,
178 pub(crate) extensible: bool,
180 private_elements: ThinVec<(PrivateName, PrivateElement)>,
182 pub(crate) data: T,
184}
185
186impl<T: Default> Default for Object<T> {
187 fn default() -> Self {
188 Self {
189 properties: PropertyMap::default(),
190 extensible: true,
191 private_elements: ThinVec::new(),
192 data: T::default(),
193 }
194 }
195}
196
197#[derive(Clone, Debug, PartialEq, Eq, Trace, Finalize)]
199pub struct PrivateName {
200 description: JsString,
202
203 id: usize,
205}
206
207impl PrivateName {
208 pub(crate) const fn new(description: JsString, id: usize) -> Self {
210 Self { description, id }
211 }
212}
213
214#[derive(Clone, Debug, Trace, Finalize)]
216pub enum PrivateElement {
217 Field(JsValue),
219
220 Method(JsObject),
222
223 Accessor {
225 getter: Option<JsObject>,
227
228 setter: Option<JsObject>,
230 },
231}
232
233impl<T: ?Sized> Object<T> {
234 #[must_use]
236 pub const fn shape(&self) -> &Shape {
237 &self.properties.shape
238 }
239
240 #[inline]
242 #[must_use]
243 pub const fn data(&self) -> &T {
244 &self.data
245 }
246
247 #[inline]
249 #[must_use]
250 pub fn data_mut(&mut self) -> &mut T {
251 &mut self.data
252 }
253
254 #[inline]
256 #[must_use]
257 pub fn prototype(&self) -> JsPrototype {
258 self.properties.shape.prototype()
259 }
260
261 #[track_caller]
267 pub fn set_prototype<O: Into<JsPrototype>>(&mut self, prototype: O) -> bool {
268 let prototype = prototype.into();
269 if self.extensible {
270 self.properties.shape = self.properties.shape.change_prototype_transition(prototype);
271 true
272 } else {
273 self.prototype() == prototype
276 }
277 }
278
279 #[inline]
281 #[must_use]
282 pub const fn properties(&self) -> &PropertyMap {
283 &self.properties
284 }
285
286 #[inline]
287 pub(crate) fn properties_mut(&mut self) -> &mut PropertyMap {
288 &mut self.properties
289 }
290
291 pub(crate) fn insert<K, P>(&mut self, key: K, property: P) -> bool
296 where
297 K: Into<PropertyKey>,
298 P: Into<PropertyDescriptor>,
299 {
300 self.properties.insert(&key.into(), property.into())
301 }
302
303 #[inline]
307 pub(crate) fn remove(&mut self, key: &PropertyKey) -> bool {
308 self.properties.remove(key)
309 }
310
311 pub(crate) fn append_private_element(&mut self, name: PrivateName, element: PrivateElement) {
313 if let PrivateElement::Accessor { getter, setter } = &element {
314 for (key, value) in &mut self.private_elements {
315 if name == *key {
316 if let PrivateElement::Accessor {
317 getter: existing_getter,
318 setter: existing_setter,
319 } = value
320 {
321 if existing_getter.is_none() {
322 existing_getter.clone_from(getter);
323 }
324 if existing_setter.is_none() {
325 existing_setter.clone_from(setter);
326 }
327 return;
328 }
329 }
330 }
331 }
332
333 self.private_elements.push((name, element));
334 }
335}
336
337impl Object<dyn NativeObject> {
338 #[must_use]
340 pub fn is<T: NativeObject>(&self) -> bool {
341 self.data.is::<T>()
342 }
343
344 #[must_use]
347 pub fn downcast_ref<T: NativeObject>(&self) -> Option<&T> {
348 self.data.downcast_ref::<T>()
349 }
350
351 pub fn downcast_mut<T: NativeObject>(&mut self) -> Option<&mut T> {
354 self.data.downcast_mut::<T>()
355 }
356
357 pub(crate) fn is_arguments(&self) -> bool {
359 self.is::<UnmappedArguments>() || self.is::<MappedArguments>()
360 }
361
362 #[inline]
364 #[must_use]
365 pub fn is_typed_uint8_array(&self) -> bool {
366 if let Some(int) = self.downcast_ref::<TypedArray>() {
367 matches!(int.kind(), TypedArrayKind::Uint8)
368 } else {
369 false
370 }
371 }
372
373 #[inline]
375 #[must_use]
376 pub fn is_typed_int8_array(&self) -> bool {
377 if let Some(int) = self.downcast_ref::<TypedArray>() {
378 matches!(int.kind(), TypedArrayKind::Int8)
379 } else {
380 false
381 }
382 }
383
384 #[inline]
386 #[must_use]
387 pub fn is_typed_uint16_array(&self) -> bool {
388 if let Some(int) = self.downcast_ref::<TypedArray>() {
389 matches!(int.kind(), TypedArrayKind::Uint16)
390 } else {
391 false
392 }
393 }
394
395 #[inline]
397 #[must_use]
398 pub fn is_typed_int16_array(&self) -> bool {
399 if let Some(int) = self.downcast_ref::<TypedArray>() {
400 matches!(int.kind(), TypedArrayKind::Int16)
401 } else {
402 false
403 }
404 }
405
406 #[inline]
408 #[must_use]
409 pub fn is_typed_uint32_array(&self) -> bool {
410 if let Some(int) = self.downcast_ref::<TypedArray>() {
411 matches!(int.kind(), TypedArrayKind::Uint32)
412 } else {
413 false
414 }
415 }
416
417 #[inline]
419 #[must_use]
420 pub fn is_typed_int32_array(&self) -> bool {
421 if let Some(int) = self.downcast_ref::<TypedArray>() {
422 matches!(int.kind(), TypedArrayKind::Int32)
423 } else {
424 false
425 }
426 }
427
428 #[inline]
430 #[must_use]
431 pub fn is_typed_float32_array(&self) -> bool {
432 if let Some(int) = self.downcast_ref::<TypedArray>() {
433 matches!(int.kind(), TypedArrayKind::Float32)
434 } else {
435 false
436 }
437 }
438
439 #[inline]
441 #[must_use]
442 pub fn is_typed_float64_array(&self) -> bool {
443 if let Some(int) = self.downcast_ref::<TypedArray>() {
444 matches!(int.kind(), TypedArrayKind::Float64)
445 } else {
446 false
447 }
448 }
449}
450
451#[derive(Debug, Clone)]
465pub struct FunctionBinding {
466 pub(crate) binding: PropertyKey,
467 pub(crate) name: JsString,
468}
469
470impl From<JsString> for FunctionBinding {
471 #[inline]
472 fn from(name: JsString) -> Self {
473 Self {
474 binding: name.clone().into(),
475 name,
476 }
477 }
478}
479
480impl From<JsSymbol> for FunctionBinding {
481 #[inline]
482 fn from(binding: JsSymbol) -> Self {
483 Self {
484 name: binding.fn_name(),
485 binding: binding.into(),
486 }
487 }
488}
489
490impl<B, N> From<(B, N)> for FunctionBinding
491where
492 B: Into<PropertyKey>,
493 N: Into<JsString>,
494{
495 fn from((binding, name): (B, N)) -> Self {
496 Self {
497 binding: binding.into(),
498 name: name.into(),
499 }
500 }
501}
502
503#[derive(Debug)]
505pub struct FunctionObjectBuilder<'realm> {
506 realm: &'realm Realm,
507 function: NativeFunction,
508 constructor: Option<ConstructorKind>,
509 name: JsString,
510 length: usize,
511}
512
513impl<'realm> FunctionObjectBuilder<'realm> {
514 #[inline]
516 #[must_use]
517 pub fn new(realm: &'realm Realm, function: NativeFunction) -> Self {
518 Self {
519 realm,
520 function,
521 constructor: None,
522 name: js_string!(),
523 length: 0,
524 }
525 }
526
527 #[must_use]
531 pub fn name<N>(mut self, name: N) -> Self
532 where
533 N: Into<JsString>,
534 {
535 self.name = name.into();
536 self
537 }
538
539 #[inline]
545 #[must_use]
546 pub const fn length(mut self, length: usize) -> Self {
547 self.length = length;
548 self
549 }
550
551 #[must_use]
555 pub fn constructor(mut self, yes: bool) -> Self {
556 self.constructor = yes.then_some(ConstructorKind::Base);
557 self
558 }
559
560 #[must_use]
562 pub fn build(self) -> JsFunction {
563 let object = self.realm.intrinsics().templates().function().create(
564 NativeFunctionObject {
565 f: self.function,
566 constructor: self.constructor,
567 realm: Some(self.realm.clone()),
568 },
569 vec![self.length.into(), self.name.into()],
570 );
571
572 JsFunction::from_object_unchecked(object)
573 }
574}
575
576#[derive(Debug)]
610pub struct ObjectInitializer<'ctx> {
611 context: &'ctx mut Context,
612 object: JsObject,
613}
614
615impl<'ctx> ObjectInitializer<'ctx> {
616 #[inline]
618 pub fn new(context: &'ctx mut Context) -> Self {
619 let object = JsObject::with_object_proto(context.intrinsics());
620 Self { context, object }
621 }
622
623 pub fn with_native_data<T: NativeObject>(data: T, context: &'ctx mut Context) -> Self {
625 let object = JsObject::from_proto_and_data_with_shared_shape(
626 context.root_shape(),
627 context.intrinsics().constructors().object().prototype(),
628 data,
629 );
630 Self { context, object }
631 }
632
633 pub fn with_native_data_and_proto<T: NativeObject>(
635 data: T,
636 proto: JsObject,
637 context: &'ctx mut Context,
638 ) -> Self {
639 let object =
640 JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), proto, data);
641 Self { context, object }
642 }
643
644 pub fn function<B>(&mut self, function: NativeFunction, binding: B, length: usize) -> &mut Self
646 where
647 B: Into<FunctionBinding>,
648 {
649 let binding = binding.into();
650 let function = FunctionObjectBuilder::new(self.context.realm(), function)
651 .name(binding.name)
652 .length(length)
653 .constructor(false)
654 .build();
655
656 self.object.borrow_mut().insert(
657 binding.binding,
658 PropertyDescriptor::builder()
659 .value(function)
660 .writable(true)
661 .enumerable(false)
662 .configurable(true),
663 );
664 self
665 }
666
667 pub fn property<K, V>(&mut self, key: K, value: V, attribute: Attribute) -> &mut Self
669 where
670 K: Into<PropertyKey>,
671 V: Into<JsValue>,
672 {
673 let property = PropertyDescriptor::builder()
674 .value(value)
675 .writable(attribute.writable())
676 .enumerable(attribute.enumerable())
677 .configurable(attribute.configurable());
678 self.object.borrow_mut().insert(key, property);
679 self
680 }
681
682 pub fn accessor<K>(
688 &mut self,
689 key: K,
690 get: Option<JsFunction>,
691 set: Option<JsFunction>,
692 attribute: Attribute,
693 ) -> &mut Self
694 where
695 K: Into<PropertyKey>,
696 {
697 assert!(set.is_some() || get.is_some());
699
700 let property = PropertyDescriptor::builder()
701 .maybe_get(get)
702 .maybe_set(set)
703 .enumerable(attribute.enumerable())
704 .configurable(attribute.configurable());
705 self.object.borrow_mut().insert(key, property);
706 self
707 }
708
709 #[inline]
711 pub fn build(&mut self) -> JsObject {
712 self.object.clone()
713 }
714
715 #[inline]
717 pub fn context(&mut self) -> &mut Context {
718 self.context
719 }
720}
721
722#[derive(Debug)]
724pub struct ConstructorBuilder<'ctx> {
725 context: &'ctx mut Context,
726 function: NativeFunction,
727 constructor_object: Object<OrdinaryObject>,
728 has_prototype_property: bool,
729 prototype: Object<OrdinaryObject>,
730 name: JsString,
731 length: usize,
732 callable: bool,
733 kind: Option<ConstructorKind>,
734 inherit: Option<JsPrototype>,
735 custom_prototype: Option<JsPrototype>,
736}
737
738impl<'ctx> ConstructorBuilder<'ctx> {
739 #[inline]
741 pub fn new(context: &'ctx mut Context, function: NativeFunction) -> ConstructorBuilder<'ctx> {
742 Self {
743 context,
744 function,
745 constructor_object: Object {
746 data: OrdinaryObject,
747 properties: PropertyMap::default(),
748 extensible: true,
749 private_elements: ThinVec::new(),
750 },
751 prototype: Object {
752 data: OrdinaryObject,
753 properties: PropertyMap::default(),
754 extensible: true,
755 private_elements: ThinVec::new(),
756 },
757 length: 0,
758 name: js_string!(),
759 callable: true,
760 kind: Some(ConstructorKind::Base),
761 inherit: None,
762 custom_prototype: None,
763 has_prototype_property: true,
764 }
765 }
766
767 pub fn method<B>(&mut self, function: NativeFunction, binding: B, length: usize) -> &mut Self
769 where
770 B: Into<FunctionBinding>,
771 {
772 let binding = binding.into();
773 let function = FunctionObjectBuilder::new(self.context.realm(), function)
774 .name(binding.name)
775 .length(length)
776 .constructor(false)
777 .build();
778
779 self.prototype.insert(
780 binding.binding,
781 PropertyDescriptor::builder()
782 .value(function)
783 .writable(true)
784 .enumerable(false)
785 .configurable(true),
786 );
787 self
788 }
789
790 pub fn static_method<B>(
792 &mut self,
793 function: NativeFunction,
794 binding: B,
795 length: usize,
796 ) -> &mut Self
797 where
798 B: Into<FunctionBinding>,
799 {
800 let binding = binding.into();
801 let function = FunctionObjectBuilder::new(self.context.realm(), function)
802 .name(binding.name)
803 .length(length)
804 .constructor(false)
805 .build();
806
807 self.constructor_object.insert(
808 binding.binding,
809 PropertyDescriptor::builder()
810 .value(function)
811 .writable(true)
812 .enumerable(false)
813 .configurable(true),
814 );
815 self
816 }
817
818 pub fn property<K, V>(&mut self, key: K, value: V, attribute: Attribute) -> &mut Self
820 where
821 K: Into<PropertyKey>,
822 V: Into<JsValue>,
823 {
824 let property = PropertyDescriptor::builder()
825 .value(value)
826 .writable(attribute.writable())
827 .enumerable(attribute.enumerable())
828 .configurable(attribute.configurable());
829 self.prototype.insert(key, property);
830 self
831 }
832
833 pub fn static_property<K, V>(&mut self, key: K, value: V, attribute: Attribute) -> &mut Self
835 where
836 K: Into<PropertyKey>,
837 V: Into<JsValue>,
838 {
839 let property = PropertyDescriptor::builder()
840 .value(value)
841 .writable(attribute.writable())
842 .enumerable(attribute.enumerable())
843 .configurable(attribute.configurable());
844 self.constructor_object.insert(key, property);
845 self
846 }
847
848 pub fn accessor<K>(
850 &mut self,
851 key: K,
852 get: Option<JsFunction>,
853 set: Option<JsFunction>,
854 attribute: Attribute,
855 ) -> &mut Self
856 where
857 K: Into<PropertyKey>,
858 {
859 let property = PropertyDescriptor::builder()
860 .maybe_get(get)
861 .maybe_set(set)
862 .enumerable(attribute.enumerable())
863 .configurable(attribute.configurable());
864 self.prototype.insert(key, property);
865 self
866 }
867
868 pub fn static_accessor<K>(
870 &mut self,
871 key: K,
872 get: Option<JsFunction>,
873 set: Option<JsFunction>,
874 attribute: Attribute,
875 ) -> &mut Self
876 where
877 K: Into<PropertyKey>,
878 {
879 let property = PropertyDescriptor::builder()
880 .maybe_get(get)
881 .maybe_set(set)
882 .enumerable(attribute.enumerable())
883 .configurable(attribute.configurable());
884 self.constructor_object.insert(key, property);
885 self
886 }
887
888 pub fn property_descriptor<K, P>(&mut self, key: K, property: P) -> &mut Self
890 where
891 K: Into<PropertyKey>,
892 P: Into<PropertyDescriptor>,
893 {
894 let property = property.into();
895 self.prototype.insert(key, property);
896 self
897 }
898
899 pub fn static_property_descriptor<K, P>(&mut self, key: K, property: P) -> &mut Self
901 where
902 K: Into<PropertyKey>,
903 P: Into<PropertyDescriptor>,
904 {
905 let property = property.into();
906 self.constructor_object.insert(key, property);
907 self
908 }
909
910 #[inline]
914 pub fn length(&mut self, length: usize) -> &mut Self {
915 self.length = length;
916 self
917 }
918
919 pub fn name<N>(&mut self, name: N) -> &mut Self
923 where
924 N: AsRef<str>,
925 {
926 self.name = name.as_ref().into();
927 self
928 }
929
930 #[inline]
934 pub fn callable(&mut self, callable: bool) -> &mut Self {
935 self.callable = callable;
936 self
937 }
938
939 #[inline]
943 pub fn constructor(&mut self, constructor: bool) -> &mut Self {
944 self.kind = constructor.then_some(ConstructorKind::Base);
945 self
946 }
947
948 pub fn inherit<O: Into<JsPrototype>>(&mut self, prototype: O) -> &mut Self {
953 self.inherit = Some(prototype.into());
954 self
955 }
956
957 pub fn custom_prototype<O: Into<JsPrototype>>(&mut self, prototype: O) -> &mut Self {
961 self.custom_prototype = Some(prototype.into());
962 self
963 }
964
965 #[inline]
969 pub fn has_prototype_property(&mut self, has_prototype_property: bool) -> &mut Self {
970 self.has_prototype_property = has_prototype_property;
971 self
972 }
973
974 #[inline]
976 pub fn context(&mut self) -> &mut Context {
977 self.context
978 }
979
980 #[must_use]
982 pub fn build(mut self) -> StandardConstructor {
983 let length = PropertyDescriptor::builder()
984 .value(self.length)
985 .writable(false)
986 .enumerable(false)
987 .configurable(true);
988 let name = PropertyDescriptor::builder()
989 .value(self.name.clone())
990 .writable(false)
991 .enumerable(false)
992 .configurable(true);
993
994 let prototype = {
995 if let Some(proto) = self.inherit.take() {
996 self.prototype.set_prototype(proto);
997 } else {
998 self.prototype.set_prototype(
999 self.context
1000 .intrinsics()
1001 .constructors()
1002 .object()
1003 .prototype(),
1004 );
1005 }
1006
1007 JsObject::from_object_and_vtable(self.prototype, &ORDINARY_INTERNAL_METHODS)
1008 };
1009
1010 let constructor = {
1011 let mut constructor = Object {
1012 properties: self.constructor_object.properties,
1013 extensible: self.constructor_object.extensible,
1014 private_elements: self.constructor_object.private_elements,
1015 data: NativeFunctionObject {
1016 f: self.function,
1017 constructor: self.kind,
1018 realm: Some(self.context.realm().clone()),
1019 },
1020 };
1021
1022 constructor.insert(StaticJsStrings::LENGTH, length);
1023 constructor.insert(js_string!("name"), name);
1024
1025 if let Some(proto) = self.custom_prototype.take() {
1026 constructor.set_prototype(proto);
1027 } else {
1028 constructor.set_prototype(
1029 self.context
1030 .intrinsics()
1031 .constructors()
1032 .function()
1033 .prototype(),
1034 );
1035 }
1036
1037 if self.has_prototype_property {
1038 constructor.insert(
1039 PROTOTYPE,
1040 PropertyDescriptor::builder()
1041 .value(prototype.clone())
1042 .writable(false)
1043 .enumerable(false)
1044 .configurable(false),
1045 );
1046 }
1047
1048 let internal_methods = constructor.data.internal_methods();
1049 JsObject::from_object_and_vtable(constructor, internal_methods)
1050 };
1051
1052 {
1053 let mut prototype = prototype.borrow_mut();
1054 prototype.insert(
1055 CONSTRUCTOR,
1056 PropertyDescriptor::builder()
1057 .value(constructor.clone())
1058 .writable(true)
1059 .enumerable(false)
1060 .configurable(true),
1061 );
1062 }
1063
1064 StandardConstructor::new(JsFunction::from_object_unchecked(constructor), prototype)
1065 }
1066}