Skip to main content

blitz_script/
dom_exception.rs

1//! DOMException is shared by document and document-free worker realms.
2
3use 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
57/// Native brand check, including instances made through subclass constructors.
58pub fn is_instance(value: &JsValue) -> bool {
59    value
60        .as_object()
61        .is_some_and(|object| object.downcast_ref::<ExceptionRef>().is_some())
62}
63
64/// Construct a platform exception without consulting mutable JavaScript globals.
65pub 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
101/// Install once per context. No DOM context or host resources are required.
102pub 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}