1use boa_engine::object::{FunctionObjectBuilder, JsObject};
4use boa_engine::property::{Attribute, PropertyDescriptor};
5use boa_engine::{
6 Context, Finalize, JsData, JsError, JsNativeError, JsResult, JsString, JsSymbol, JsValue,
7 NativeFunction, Trace, js_string,
8};
9
10#[derive(Trace, Finalize, JsData)]
11struct ExceptionPrototype {
12 prototype: JsObject,
13}
14
15#[derive(Trace, Finalize, JsData)]
16struct ExceptionRef {
17 name: JsString,
18 message: JsString,
19}
20
21const LEGACY: &[(&str, &str, u32)] = &[
22 ("INDEX_SIZE_ERR", "IndexSizeError", 1),
23 ("DOMSTRING_SIZE_ERR", "", 2),
24 ("HIERARCHY_REQUEST_ERR", "HierarchyRequestError", 3),
25 ("WRONG_DOCUMENT_ERR", "WrongDocumentError", 4),
26 ("INVALID_CHARACTER_ERR", "InvalidCharacterError", 5),
27 ("NO_DATA_ALLOWED_ERR", "", 6),
28 (
29 "NO_MODIFICATION_ALLOWED_ERR",
30 "NoModificationAllowedError",
31 7,
32 ),
33 ("NOT_FOUND_ERR", "NotFoundError", 8),
34 ("NOT_SUPPORTED_ERR", "NotSupportedError", 9),
35 ("INUSE_ATTRIBUTE_ERR", "InUseAttributeError", 10),
36 ("INVALID_STATE_ERR", "InvalidStateError", 11),
37 ("SYNTAX_ERR", "SyntaxError", 12),
38 ("INVALID_MODIFICATION_ERR", "InvalidModificationError", 13),
39 ("NAMESPACE_ERR", "NamespaceError", 14),
40 ("INVALID_ACCESS_ERR", "InvalidAccessError", 15),
41 ("VALIDATION_ERR", "", 16),
42 ("TYPE_MISMATCH_ERR", "TypeMismatchError", 17),
43 ("SECURITY_ERR", "SecurityError", 18),
44 ("NETWORK_ERR", "NetworkError", 19),
45 ("ABORT_ERR", "AbortError", 20),
46 ("URL_MISMATCH_ERR", "URLMismatchError", 21),
47 ("QUOTA_EXCEEDED_ERR", "QuotaExceededError", 22),
48 ("TIMEOUT_ERR", "TimeoutError", 23),
49 ("INVALID_NODE_TYPE_ERR", "InvalidNodeTypeError", 24),
50 ("DATA_CLONE_ERR", "DataCloneError", 25),
51];
52
53fn create(prototype: JsObject, name: JsString, message: JsString) -> JsObject {
54 JsObject::from_proto_and_data(Some(prototype), ExceptionRef { name, message })
55}
56
57pub fn is_instance(value: &JsValue) -> bool {
59 value
60 .as_object()
61 .is_some_and(|object| object.downcast_ref::<ExceptionRef>().is_some())
62}
63
64pub fn error(name: &str, message: &str, context: &mut Context) -> JsError {
66 register(context);
67 let prototype = context
68 .get_data::<ExceptionPrototype>()
69 .expect("DOMException prototype")
70 .prototype
71 .clone();
72 JsError::from_opaque(create(prototype, JsString::from(name), JsString::from(message)).into())
73}
74
75fn construct(new_target: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
76 let target = new_target
77 .as_object()
78 .filter(|object| object.is_constructor())
79 .ok_or_else(|| JsNativeError::typ().with_message("DOMException requires 'new'"))?;
80 let message = match args.first() {
81 Some(value) if !value.is_undefined() => value.to_string(context)?,
82 _ => js_string!(""),
83 };
84 let name = match args.get(1) {
85 Some(value) if !value.is_undefined() => value.to_string(context)?,
86 _ => js_string!("Error"),
87 };
88 let prototype = target
89 .get(js_string!("prototype"), context)?
90 .as_object()
91 .unwrap_or_else(|| {
92 context
93 .get_data::<ExceptionPrototype>()
94 .expect("DOMException prototype")
95 .prototype
96 .clone()
97 });
98 Ok(create(prototype, name, message).into())
99}
100
101pub fn register(context: &mut Context) {
103 if context.get_data::<ExceptionPrototype>().is_some() {
104 return;
105 }
106 let prototype = JsObject::from_proto_and_data(
107 Some(context.intrinsics().constructors().error().prototype()),
108 (),
109 );
110 for field in ["name", "message", "code"] {
111 let getter = FunctionObjectBuilder::new(
112 context.realm(),
113 NativeFunction::from_copy_closure(move |this, _, _| {
114 let object = this
115 .as_object()
116 .ok_or_else(|| JsNativeError::typ().with_message("Illegal invocation"))?;
117 let data = object
118 .downcast_ref::<ExceptionRef>()
119 .ok_or_else(|| JsNativeError::typ().with_message("Illegal invocation"))?;
120 Ok(match field {
121 "name" => data.name.clone().into(),
122 "message" => data.message.clone().into(),
123 _ => LEGACY
124 .iter()
125 .find(|(_, name, _)| !name.is_empty() && data.name == *name)
126 .map_or(0, |(_, _, code)| *code)
127 .into(),
128 })
129 }),
130 )
131 .name(JsString::from(format!("get {field}")))
132 .length(0)
133 .build();
134 prototype
135 .define_property_or_throw(
136 JsString::from(field),
137 PropertyDescriptor::builder()
138 .get(getter)
139 .enumerable(true)
140 .configurable(true),
141 context,
142 )
143 .expect("DOMException accessor");
144 }
145 prototype
146 .define_property_or_throw(
147 JsSymbol::to_string_tag(),
148 PropertyDescriptor::builder()
149 .value(js_string!("DOMException"))
150 .configurable(true),
151 context,
152 )
153 .expect("DOMException tag");
154 let constructor =
155 FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(construct))
156 .name(js_string!("DOMException"))
157 .length(0)
158 .constructor(true)
159 .build();
160 constructor
161 .define_property_or_throw(
162 js_string!("prototype"),
163 PropertyDescriptor::builder().value(prototype.clone()),
164 context,
165 )
166 .expect("DOMException constructor prototype");
167 prototype
168 .define_property_or_throw(
169 js_string!("constructor"),
170 PropertyDescriptor::builder()
171 .value(constructor.clone())
172 .writable(true)
173 .configurable(true),
174 context,
175 )
176 .expect("DOMException prototype constructor");
177 for &(constant, _, code) in LEGACY {
178 for object in [&prototype, &constructor] {
179 object
180 .define_property_or_throw(
181 JsString::from(constant),
182 PropertyDescriptor::builder().value(code).enumerable(true),
183 context,
184 )
185 .expect("DOMException constant");
186 }
187 }
188 context.insert_data(ExceptionPrototype { prototype });
189 context
190 .register_global_property(
191 js_string!("DOMException"),
192 constructor,
193 Attribute::WRITABLE | Attribute::CONFIGURABLE,
194 )
195 .expect("DOMException global");
196}