1use core::fmt;
4use std::fmt::Debug;
5
6use crate::utils::{
7 object::define_subclass,
8 option::Undefined,
9 primordials::{BasePrimordials, Primordial},
10};
11use rquickjs::{
12 atom::PredefinedAtom,
13 class::{
14 impl_::{CloneTrait, CloneWrapper},
15 JsClass, Trace,
16 },
17 function::{Constructor, Opt},
18 object::{Accessor, Property},
19 prelude::{Func, This},
20 qjs, Class, Coerced, Ctx, Error, Exception, FromJs, IntoJs, JsLifetime, Object, Result, Value,
21};
22
23#[derive(JsLifetime)]
24struct ExceptionPrimordials<'js> {
25 constructor_dom_exception: Constructor<'js>,
26 constructor_quota_exceeded_error: Constructor<'js>,
27}
28
29impl<'js> Primordial<'js> for ExceptionPrimordials<'js> {
30 fn new(ctx: &Ctx<'js>) -> Result<Self> {
31 let globals = ctx.globals();
32 Ok(Self {
33 constructor_dom_exception: globals.get(DOMException::NAME)?,
34 constructor_quota_exceeded_error: globals.get("QuotaExceededError")?,
35 })
36 }
37}
38
39#[derive(Trace, JsLifetime, Debug)]
40pub struct DOMException {
41 name: String,
42 message: String,
43 stack: String,
44 code: u8,
45}
46
47impl fmt::Display for DOMException {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 f.debug_struct("DOMException")
50 .field("name", &self.name())
51 .field("message", &self.message())
52 .field("stack", &self.stack)
53 .finish()
54 }
55}
56
57fn add_constants(obj: &Object<'_>) -> Result<()> {
58 const CONSTANTS: [(&str, u8); 25] = [
59 ("INDEX_SIZE_ERR", 1),
60 ("DOMSTRING_SIZE_ERR", 2),
61 ("HIERARCHY_REQUEST_ERR", 3),
62 ("WRONG_DOCUMENT_ERR", 4),
63 ("INVALID_CHARACTER_ERR", 5),
64 ("NO_DATA_ALLOWED_ERR", 6),
65 ("NO_MODIFICATION_ALLOWED_ERR", 7),
66 ("NOT_FOUND_ERR", 8),
67 ("NOT_SUPPORTED_ERR", 9),
68 ("INUSE_ATTRIBUTE_ERR", 10),
69 ("INVALID_STATE_ERR", 11),
70 ("SYNTAX_ERR", 12),
71 ("INVALID_MODIFICATION_ERR", 13),
72 ("NAMESPACE_ERR", 14),
73 ("INVALID_ACCESS_ERR", 15),
74 ("VALIDATION_ERR", 16),
75 ("TYPE_MISMATCH_ERR", 17),
76 ("SECURITY_ERR", 18),
77 ("NETWORK_ERR", 19),
78 ("ABORT_ERR", 20),
79 ("URL_MISMATCH_ERR", 21),
80 ("QUOTA_EXCEEDED_ERR", 22),
81 ("TIMEOUT_ERR", 23),
82 ("INVALID_NODE_TYPE_ERR", 24),
83 ("DATA_CLONE_ERR", 25),
84 ];
85
86 for (key, value) in CONSTANTS {
87 obj.prop(key, Property::from(value).enumerable())?;
88 }
89
90 Ok(())
91}
92
93impl<'js> JsClass<'js> for DOMException {
94 const NAME: &'static str = "DOMException";
95 type Mutable = rquickjs::class::Writable;
96 fn prototype(ctx: &Ctx<'js>) -> rquickjs::Result<Option<Object<'js>>> {
97 use rquickjs::class::impl_::{MethodImpl, MethodImplementor};
98 let proto = Object::new(ctx.clone())?;
99 let implementor = MethodImpl::<Self>::new();
100 implementor.implement(&proto)?;
101 add_constants(&proto)?;
102
103 Ok(Some(proto))
104 }
105 fn constructor(ctx: &Ctx<'js>) -> Result<Option<Constructor<'js>>> {
106 use rquickjs::class::impl_::{ConstructorCreate, ConstructorCreator};
107 let implementor = ConstructorCreate::<Self>::new();
108 let constructor = implementor
109 .create_constructor(ctx)?
110 .expect("DOMException must have a constructor");
111 add_constants(&constructor)?;
112
113 Ok(Some(constructor))
114 }
115}
116
117impl<'js> IntoJs<'js> for DOMException {
118 fn into_js(self, ctx: &rquickjs::Ctx<'js>) -> Result<Value<'js>> {
119 let cls = Class::<Self>::instance(ctx.clone(), self)?;
120 IntoJs::into_js(cls, ctx)
121 }
122}
123
124impl<'js> FromJs<'js> for DOMException
125where
126 for<'a> CloneWrapper<'a, Self>: CloneTrait<Self>,
127{
128 fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
129 let value = Class::<Self>::from_js(ctx, value)?;
130 let borrow = value.try_borrow()?;
131 Ok(CloneWrapper(&*borrow).wrap_clone())
132 }
133}
134
135#[rquickjs::methods]
136impl DOMException {
137 #[qjs(constructor)]
138 pub fn new<'js>(
139 ctx: Ctx<'js>,
140 this: This<Value<'js>>,
141 message: Opt<Undefined<Coerced<String>>>,
142 name: Opt<Undefined<Coerced<String>>>,
143 ) -> Result<Self> {
144 if this.0.as_function().is_none() {
147 return Err(Exception::throw_type(
148 &ctx,
149 "Cannot call the DOMException constructor without 'new'",
150 ));
151 }
152
153 let message = match message.0 {
154 Some(Undefined(Some(message))) => message.0,
155 _ => String::new(),
156 };
157
158 let name = match name.0 {
159 Some(Undefined(Some(message))) => DOMExceptionName::from(message.0),
160 _ => DOMExceptionName::Error,
161 };
162
163 Self::new_with_name(&ctx, name, message)
164 }
165
166 #[qjs(skip)]
167 pub fn new_with_name(ctx: &Ctx<'_>, name: DOMExceptionName, message: String) -> Result<Self> {
168 let primordials = BasePrimordials::get(ctx)?;
169
170 let new: Object = primordials
171 .constructor_error
172 .construct((message.clone(),))?;
173
174 Ok(Self {
175 name: name.as_str().to_string(),
176 code: name.code(),
177 message,
178 stack: new.get::<_, String>(PredefinedAtom::Stack)?,
179 })
180 }
181
182 #[qjs(get, enumerable, configurable)]
183 fn message(&self) -> &str {
184 self.message.as_str()
185 }
186
187 #[qjs(get, enumerable, configurable)]
188 pub fn name(&self) -> &str {
189 self.name.as_str()
190 }
191
192 #[qjs(get, enumerable, configurable)]
193 pub fn code(&self) -> u8 {
194 self.code
195 }
196
197 #[qjs(prop, rename = PredefinedAtom::SymbolToStringTag, configurable)]
198 pub fn to_string_tag() -> &'static str {
199 stringify!(DOMException)
200 }
201}
202
203impl<'js> DOMException {
204 fn create(
205 ctx: &Ctx<'js>,
206 name: DOMExceptionName,
207 message: impl Into<String>,
208 ) -> Result<Value<'js>> {
209 let primordials = ExceptionPrimordials::get(ctx)?;
210 let ctor = match name {
211 DOMExceptionName::QuotaExceededError => &primordials.constructor_quota_exceeded_error,
212 _ => &primordials.constructor_dom_exception,
213 };
214 ctor.construct((message.into(), name.as_str()))
215 }
216
217 fn throw_value(ctx: &Ctx<'js>, value: Value<'js>) -> Error {
218 unsafe {
219 let dup = qjs::JS_DupValue(ctx.as_raw().as_ptr(), value.as_raw());
220 qjs::JS_Throw(ctx.as_raw().as_ptr(), dup);
221 }
222 Error::Exception
223 }
224
225 fn create_error(ctx: &Ctx<'js>, name: DOMExceptionName, message: impl Into<String>) -> Error {
226 let value = Self::create(ctx, name, message).expect("failed to create DOMException");
227 Self::throw_value(ctx, value)
228 }
229
230 pub fn not_supported_error(ctx: &Ctx<'js>, message: impl Into<String>) -> Error {
231 Self::create_error(ctx, DOMExceptionName::NotSupportedError, message)
232 }
233
234 pub fn type_mismatch_error(ctx: &Ctx<'js>, message: impl Into<String>) -> Error {
235 Self::create_error(ctx, DOMExceptionName::TypeMismatchError, message)
236 }
237
238 pub fn operation_error(ctx: &Ctx<'js>, message: impl Into<String>) -> Error {
239 Self::create_error(ctx, DOMExceptionName::OperationError, message)
240 }
241
242 pub fn quota_exceeded_error(ctx: &Ctx<'js>, message: impl Into<String>) -> Error {
243 Self::create_error(ctx, DOMExceptionName::QuotaExceededError, message)
244 }
245
246 pub fn data_error(ctx: &Ctx<'js>, message: impl Into<String>) -> Error {
247 Self::create_error(ctx, DOMExceptionName::DataError, message)
248 }
249
250 pub fn invalid_access_error(ctx: &Ctx<'js>, message: impl Into<String>) -> Error {
251 Self::create_error(ctx, DOMExceptionName::InvalidAccessError, message)
252 }
253
254 pub fn syntax_error(ctx: &Ctx<'js>, message: impl Into<String>) -> Error {
255 Self::create_error(ctx, DOMExceptionName::SyntaxError, message)
256 }
257
258 fn define_quota_exceeded_error(ctx: &Ctx<'js>) -> Result<()> {
259 let dom_exception: Constructor = ctx.globals().get(Self::NAME)?;
260 let quota_exceeded_error = define_subclass(
261 ctx,
262 "QuotaExceededError",
263 &dom_exception,
264 |ctx, message: Opt<Undefined<Coerced<String>>>| {
265 let message = match message.0 {
266 Some(Undefined(Some(m))) => m.0,
267 _ => String::new(),
268 };
269 Self::new_with_name(&ctx, DOMExceptionName::QuotaExceededError, message)
270 },
271 )?;
272 let null = Value::new_null(ctx.clone());
273 let proto: Object = quota_exceeded_error.get(PredefinedAtom::Prototype)?;
274 proto.prop(
275 "requested",
276 Property::from(null.clone()).enumerable().configurable(),
277 )?;
278 proto.prop("quota", Property::from(null).enumerable().configurable())?;
279 ctx.globals().prop(
280 "QuotaExceededError",
281 Property::from(quota_exceeded_error)
282 .writable()
283 .configurable(),
284 )
285 }
286}
287
288macro_rules! create_dom_exception {
289 ($name:ident, $($variant:ident),+ $(,)?) => {
290 #[derive(Debug)]
291 pub enum $name {
292 $(
293 $variant,
294 )+
295 Other(String),
296 }
297
298 impl $name {
299 pub fn as_str(&self) -> &str {
300 match self {
301 $(
302 Self::$variant => stringify!($variant),
303 )+
304 Self::Other(value) => value,
305 }
306 }
307 }
308
309 impl From<String> for $name {
310 fn from(value: String) -> Self {
311 match value.as_str() {
312 $(
313 stringify!($variant) => Self::$variant,
314 )+
315 _ => Self::Other(value),
316 }
317 }
318 }
319 };
320}
321
322create_dom_exception!(
324 DOMExceptionName,
325 IndexSizeError,
326 HierarchyRequestError,
327 WrongDocumentError,
328 InvalidCharacterError,
329 NoModificationAllowedError,
330 NotFoundError,
331 NotSupportedError,
332 InUseAttributeError,
333 InvalidStateError,
334 SyntaxError,
335 InvalidModificationError,
336 NamespaceError,
337 InvalidAccessError,
338 TypeMismatchError,
339 SecurityError,
340 NetworkError,
341 AbortError,
342 URLMismatchError,
343 QuotaExceededError,
344 TimeoutError,
345 InvalidNodeTypeError,
346 DataCloneError,
347 EncodingError,
348 NotReadableError,
349 UnknownError,
350 ConstraintError,
351 DataError,
352 TransactionInactiveError,
353 ReadOnlyError,
354 VersionError,
355 OperationError,
356 NotAllowedError,
357 Error,
358);
359
360impl DOMExceptionName {
361 fn code(&self) -> u8 {
362 match self {
363 DOMExceptionName::IndexSizeError => 1,
364 DOMExceptionName::HierarchyRequestError => 3,
365 DOMExceptionName::WrongDocumentError => 4,
366 DOMExceptionName::InvalidCharacterError => 5,
367 DOMExceptionName::NoModificationAllowedError => 7,
368 DOMExceptionName::NotFoundError => 8,
369 DOMExceptionName::NotSupportedError => 9,
370 DOMExceptionName::InUseAttributeError => 10,
371 DOMExceptionName::InvalidStateError => 11,
372 DOMExceptionName::SyntaxError => 12,
373 DOMExceptionName::InvalidModificationError => 13,
374 DOMExceptionName::NamespaceError => 14,
375 DOMExceptionName::InvalidAccessError => 15,
376 DOMExceptionName::TypeMismatchError => 17,
377 DOMExceptionName::SecurityError => 18,
378 DOMExceptionName::NetworkError => 19,
379 DOMExceptionName::AbortError => 20,
380 DOMExceptionName::URLMismatchError => 21,
381 DOMExceptionName::QuotaExceededError => 22,
382 DOMExceptionName::TimeoutError => 23,
383 DOMExceptionName::InvalidNodeTypeError => 24,
384 DOMExceptionName::DataCloneError => 25,
385 _ => 0,
386 }
387 }
388}
389
390pub fn init(ctx: &Ctx<'_>) -> Result<()> {
391 let globals = ctx.globals();
392
393 BasePrimordials::init(ctx)?;
394
395 if let Some(constructor) = Class::<DOMException>::create_constructor(ctx)? {
396 globals.prop(
398 DOMException::NAME,
399 Property::from(constructor).writable().configurable(),
400 )?;
401 }
402
403 let dom_ex_proto = Class::<DOMException>::prototype(ctx)?.unwrap();
404 dom_ex_proto.set_prototype(Some(&BasePrimordials::get(ctx)?.prototype_error))?;
405
406 DOMException::define_quota_exceeded_error(ctx)?;
407 ExceptionPrimordials::init(ctx)?;
408
409 BasePrimordials::get(ctx)?
416 .constructor_error
417 .set("isError", Func::from(is_error))?;
418
419 define_error_stack_accessor(ctx)?;
420
421 Ok(())
422}
423
424fn define_error_stack_accessor<'js>(ctx: &Ctx<'js>) -> Result<()> {
437 let dom_ex_proto = Class::<DOMException>::prototype(ctx)?
438 .expect("DOMException prototype is defined by the call above");
439 dom_ex_proto.prop(
440 PredefinedAtom::Stack,
441 Accessor::new(
442 |this: This<Value<'js>>| -> Result<String> {
443 let stack = Class::<DOMException>::from_value(&this.0)
444 .ok()
445 .map(|cls| cls.borrow().stack.clone());
446 Ok(stack.unwrap_or_default())
447 },
448 |ctx: Ctx<'js>, this: This<Value<'js>>, value: Value<'js>| -> Result<()> {
449 let Some(obj) = this.0.as_object() else {
452 return Ok(());
453 };
454 if let Ok(Some(home)) = Class::<DOMException>::prototype(&ctx) {
455 if *obj == home {
456 return Ok(());
457 }
458 }
459 obj.prop(
460 PredefinedAtom::Stack,
461 Property::from(value).writable().enumerable().configurable(),
462 )
463 },
464 )
465 .configurable(),
466 )
467}
468
469fn is_error<'js>(ctx: Ctx<'js>, value: Value<'js>) -> Result<bool> {
470 if value.is_error() {
471 return Ok(true);
472 }
473 let Some(obj) = value.as_object() else {
474 return Ok(false);
475 };
476 let dom_exception: Value = ctx.globals().get(DOMException::NAME)?;
477 Ok(obj.is_instance_of(&dom_exception))
478}