Skip to content

Commit 4ef0ca4

Browse files
committed
Add DomExceptionName and wpt for DomException
1 parent 0b5172e commit 4ef0ca4

File tree

7 files changed

+653
-57
lines changed

7 files changed

+653
-57
lines changed

modules/llrt_abort/src/abort_signal.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use std::sync::{Arc, RwLock};
44

55
use llrt_events::{Emitter, EventEmitter, EventList};
6-
use llrt_exceptions::DOMException;
6+
use llrt_exceptions::{DOMException, DOMExceptionName};
77
use llrt_utils::mc_oneshot;
88
use rquickjs::{
99
class::{Trace, Tracer},
@@ -160,7 +160,11 @@ impl<'js> AbortSignal<'js> {
160160
pub fn send_aborted(this: This<Class<'js, Self>>, ctx: Ctx<'js>) -> Result<()> {
161161
let mut borrow = this.borrow_mut();
162162
borrow.aborted = true;
163-
let reason = get_reason_or_dom_exception(&ctx, borrow.reason.as_ref(), "AbortError")?;
163+
let reason = get_reason_or_dom_exception(
164+
&ctx,
165+
borrow.reason.as_ref(),
166+
DOMExceptionName::AbortError,
167+
)?;
164168
borrow.reason = Some(reason.clone());
165169
borrow.sender.send(reason);
166170
drop(borrow);
@@ -179,7 +183,8 @@ impl<'js> AbortSignal<'js> {
179183

180184
#[qjs(static)]
181185
pub fn timeout(ctx: Ctx<'js>, milliseconds: u64) -> Result<Class<'js, Self>> {
182-
let timeout_error = get_reason_or_dom_exception(&ctx, None, "TimeoutError")?;
186+
let timeout_error =
187+
get_reason_or_dom_exception(&ctx, None, DOMExceptionName::TimeoutError)?;
183188

184189
let signal = Self::new();
185190
let signal_instance = Class::instance(ctx.clone(), signal)?;
@@ -221,12 +226,12 @@ impl<'js> AbortSignal<'js> {
221226
fn get_reason_or_dom_exception<'js>(
222227
ctx: &Ctx<'js>,
223228
reason: Option<&Value<'js>>,
224-
name: &str,
229+
name: DOMExceptionName,
225230
) -> Result<Value<'js>> {
226231
let reason = if let Some(reason) = reason {
227232
reason.clone()
228233
} else {
229-
let ex = DOMException::new(ctx.clone(), Opt(None), Opt(Some(name.into())))?;
234+
let ex = DOMException::new_with_name(ctx, name, String::new())?;
230235
Class::instance(ctx.clone(), ex)?.into_value()
231236
};
232237
Ok(reason)

modules/llrt_exceptions/src/lib.rs

Lines changed: 242 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,162 @@
1-
use llrt_utils::primordials::{BasePrimordials, Primordial};
21
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
32
// SPDX-License-Identifier: Apache-2.0
4-
use rquickjs::{atom::PredefinedAtom, function::Opt, Class, Ctx, Object, Result};
3+
use llrt_utils::{
4+
option::Undefined,
5+
primordials::{BasePrimordials, Primordial},
6+
};
7+
use rquickjs::{
8+
atom::PredefinedAtom,
9+
class::JsClass,
10+
function::{Constructor, Opt},
11+
object::Property,
12+
prelude::This,
13+
Class, Coerced, Ctx, Exception, IntoJs, Object, Result, Value,
14+
};
515

6-
#[rquickjs::class]
716
#[derive(rquickjs::class::Trace, rquickjs::JsLifetime)]
817
pub struct DOMException {
9-
message: String,
1018
name: String,
19+
message: String,
1120
stack: String,
1221
code: u8,
1322
}
1423

24+
fn add_constants(obj: &Object<'_>) -> Result<()> {
25+
const CONSTANTS: [(&str, u8); 25] = [
26+
("INDEX_SIZE_ERR", 1),
27+
("DOMSTRING_SIZE_ERR", 2),
28+
("HIERARCHY_REQUEST_ERR", 3),
29+
("WRONG_DOCUMENT_ERR", 4),
30+
("INVALID_CHARACTER_ERR", 5),
31+
("NO_DATA_ALLOWED_ERR", 6),
32+
("NO_MODIFICATION_ALLOWED_ERR", 7),
33+
("NOT_FOUND_ERR", 8),
34+
("NOT_SUPPORTED_ERR", 9),
35+
("INUSE_ATTRIBUTE_ERR", 10),
36+
("INVALID_STATE_ERR", 11),
37+
("SYNTAX_ERR", 12),
38+
("INVALID_MODIFICATION_ERR", 13),
39+
("NAMESPACE_ERR", 14),
40+
("INVALID_ACCESS_ERR", 15),
41+
("VALIDATION_ERR", 16),
42+
("TYPE_MISMATCH_ERR", 17),
43+
("SECURITY_ERR", 18),
44+
("NETWORK_ERR", 19),
45+
("ABORT_ERR", 20),
46+
("URL_MISMATCH_ERR", 21),
47+
("QUOTA_EXCEEDED_ERR", 22),
48+
("TIMEOUT_ERR", 23),
49+
("INVALID_NODE_TYPE_ERR", 24),
50+
("DATA_CLONE_ERR", 25),
51+
];
52+
53+
for (key, value) in CONSTANTS {
54+
obj.prop(key, Property::from(value).enumerable())?;
55+
}
56+
57+
Ok(())
58+
}
59+
60+
impl<'js> JsClass<'js> for DOMException {
61+
const NAME: &'static str = "DOMException";
62+
type Mutable = rquickjs::class::Writable;
63+
fn prototype(ctx: &Ctx<'js>) -> rquickjs::Result<Option<Object<'js>>> {
64+
use rquickjs::class::impl_::{MethodImpl, MethodImplementor};
65+
let proto = Object::new(ctx.clone())?;
66+
let implementor = MethodImpl::<Self>::new();
67+
implementor.implement(&proto)?;
68+
add_constants(&proto)?;
69+
70+
Ok(Some(proto))
71+
}
72+
fn constructor(ctx: &Ctx<'js>) -> Result<Option<Constructor<'js>>> {
73+
use rquickjs::class::impl_::{ConstructorCreate, ConstructorCreator};
74+
let implementor = ConstructorCreate::<Self>::new();
75+
let constructor = implementor
76+
.create_constructor(ctx)?
77+
.expect("DOMException must have a constructor");
78+
add_constants(&constructor)?;
79+
80+
Ok(Some(constructor))
81+
}
82+
}
83+
84+
impl<'js> IntoJs<'js> for DOMException {
85+
fn into_js(self, ctx: &rquickjs::Ctx<'js>) -> Result<Value<'js>> {
86+
let cls = Class::<Self>::instance(ctx.clone(), self)?;
87+
rquickjs::IntoJs::into_js(cls, ctx)
88+
}
89+
}
90+
91+
impl<'js> rquickjs::FromJs<'js> for DOMException
92+
where
93+
for<'a> rquickjs::class::impl_::CloneWrapper<'a, Self>:
94+
rquickjs::class::impl_::CloneTrait<Self>,
95+
{
96+
fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
97+
use rquickjs::class::impl_::{CloneTrait, CloneWrapper};
98+
let value = Class::<Self>::from_js(ctx, value)?;
99+
let borrow = value.try_borrow()?;
100+
Ok(CloneWrapper(&*borrow).wrap_clone())
101+
}
102+
}
103+
15104
#[rquickjs::methods]
16105
impl DOMException {
17106
#[qjs(constructor)]
18-
pub fn new(ctx: Ctx<'_>, message: Opt<String>, name: Opt<String>) -> Result<Self> {
19-
let primordials = BasePrimordials::get(&ctx)?;
107+
pub fn new(
108+
ctx: Ctx<'_>,
109+
this: This<Value<'_>>,
110+
message: Opt<Undefined<Coerced<String>>>,
111+
name: Opt<Undefined<Coerced<String>>>,
112+
) -> Result<Self> {
113+
if this.0.is_undefined() {
114+
return Err(Exception::throw_type(
115+
&ctx,
116+
"Cannot call the DOMException constructor without 'new'",
117+
));
118+
}
119+
120+
let message = match message.0 {
121+
Some(Undefined(Some(message))) => message.0,
122+
_ => String::new(),
123+
};
124+
125+
let name = match name.0 {
126+
Some(Undefined(Some(message))) => DOMExceptionName::from(message.0),
127+
_ => DOMExceptionName::Error,
128+
};
129+
130+
Self::new_with_name(&ctx, name, message)
131+
}
132+
133+
#[qjs(skip)]
134+
pub fn new_with_name(ctx: &Ctx<'_>, name: DOMExceptionName, message: String) -> Result<Self> {
135+
let primordials = BasePrimordials::get(ctx)?;
20136

21137
let new: Object = primordials
22138
.constructor_error
23139
.construct((message.clone(),))?;
24140

25-
let message = message.0.unwrap_or(String::from(""));
26-
let name = name.0.unwrap_or(String::from("Error"));
27-
28-
// https://webidl.spec.whatwg.org/#dfn-error-names-table
29-
let code = match name.as_str() {
30-
"IndexSizeError" => 1,
31-
"HierarchyRequestError" => 3,
32-
"WrongDocumentError" => 4,
33-
"InvalidCharacterError" => 5,
34-
"NoModificationAllowedError" => 7,
35-
"NotFoundError" => 8,
36-
"NotSupportedError" => 9,
37-
"InUseAttributeError" => 10,
38-
"InvalidStateError" => 11,
39-
"SyntaxError" => 12,
40-
"InvalidModificationError" => 13,
41-
"NamespaceError" => 14,
42-
"InvalidAccessError" => 15,
43-
"TypeMismatchError" => 17,
44-
"SecurityError" => 18,
45-
"NetworkError" => 19,
46-
"AbortError" => 20,
47-
"URLMismatchError" => 21,
48-
"QuotaExceededError" => 22,
49-
"TimeoutError" => 23,
50-
"InvalidNodeTypeError" => 24,
51-
"DataCloneError" => 25,
52-
_ => 0,
53-
};
54-
55141
Ok(Self {
142+
name: name.as_str().to_string(),
143+
code: name.code(),
56144
message,
57-
name,
58-
code,
59145
stack: new.get::<_, String>(PredefinedAtom::Stack)?,
60146
})
61147
}
62148

63-
#[qjs(get)]
64-
fn message(&self) -> String {
65-
self.message.clone()
149+
#[qjs(get, enumerable, configurable)]
150+
fn message(&self) -> &str {
151+
self.message.as_str()
66152
}
67153

68-
#[qjs(get)]
69-
pub fn name(&self) -> String {
70-
self.name.clone()
154+
#[qjs(get, enumerable, configurable)]
155+
pub fn name(&self) -> &str {
156+
self.name.as_str()
71157
}
72158

73-
#[qjs(get)]
159+
#[qjs(get, enumerable, configurable)]
74160
pub fn code(&self) -> u8 {
75161
self.code
76162
}
@@ -80,20 +166,124 @@ impl DOMException {
80166
self.stack.clone()
81167
}
82168

83-
#[allow(clippy::inherent_to_string)]
84-
#[qjs(rename = PredefinedAtom::ToString)]
85-
pub fn to_string(&self) -> String {
86-
if self.message.is_empty() {
87-
return self.name.clone();
169+
#[qjs(get, rename = PredefinedAtom::SymbolToStringTag)]
170+
pub fn to_string_tag(&self) -> &str {
171+
"DOMException"
172+
}
173+
}
174+
175+
macro_rules! create_dom_exception {
176+
($name:ident, $($variant:ident),+ $(,)?) => {
177+
#[derive(Debug)]
178+
pub enum $name {
179+
$(
180+
$variant,
181+
)+
182+
Other(String),
183+
}
184+
185+
impl $name {
186+
pub fn as_str(&self) -> &str {
187+
match self {
188+
$(
189+
Self::$variant => stringify!($variant),
190+
)+
191+
Self::Other(value) => value,
192+
}
193+
}
88194
}
89195

90-
[self.name.as_str(), self.message.as_str()].join(": ")
196+
impl From<String> for $name {
197+
fn from(value: String) -> Self {
198+
match value.as_str() {
199+
$(
200+
stringify!($variant) => Self::$variant,
201+
)+
202+
_ => Self::Other(value),
203+
}
204+
}
205+
}
206+
};
207+
}
208+
209+
// https://webidl.spec.whatwg.org/#dfn-error-names-table
210+
create_dom_exception!(
211+
DOMExceptionName,
212+
IndexSizeError,
213+
HierarchyRequestError,
214+
WrongDocumentError,
215+
InvalidCharacterError,
216+
NoModificationAllowedError,
217+
NotFoundError,
218+
NotSupportedError,
219+
InUseAttributeError,
220+
InvalidStateError,
221+
SyntaxError,
222+
InvalidModificationError,
223+
NamespaceError,
224+
InvalidAccessError,
225+
TypeMismatchError,
226+
SecurityError,
227+
NetworkError,
228+
AbortError,
229+
URLMismatchError,
230+
QuotaExceededError,
231+
TimeoutError,
232+
InvalidNodeTypeError,
233+
DataCloneError,
234+
EncodingError,
235+
NotReadableError,
236+
UnknownError,
237+
ConstraintError,
238+
DataError,
239+
TransactionInactiveError,
240+
ReadOnlyError,
241+
VersionError,
242+
OperationError,
243+
NotAllowedError,
244+
Error,
245+
);
246+
247+
impl DOMExceptionName {
248+
fn code(&self) -> u8 {
249+
match self {
250+
DOMExceptionName::IndexSizeError => 1,
251+
DOMExceptionName::HierarchyRequestError => 3,
252+
DOMExceptionName::WrongDocumentError => 4,
253+
DOMExceptionName::InvalidCharacterError => 5,
254+
DOMExceptionName::NoModificationAllowedError => 7,
255+
DOMExceptionName::NotFoundError => 8,
256+
DOMExceptionName::NotSupportedError => 9,
257+
DOMExceptionName::InUseAttributeError => 10,
258+
DOMExceptionName::InvalidStateError => 11,
259+
DOMExceptionName::SyntaxError => 12,
260+
DOMExceptionName::InvalidModificationError => 13,
261+
DOMExceptionName::NamespaceError => 14,
262+
DOMExceptionName::InvalidAccessError => 15,
263+
DOMExceptionName::TypeMismatchError => 17,
264+
DOMExceptionName::SecurityError => 18,
265+
DOMExceptionName::NetworkError => 19,
266+
DOMExceptionName::AbortError => 20,
267+
DOMExceptionName::URLMismatchError => 21,
268+
DOMExceptionName::QuotaExceededError => 22,
269+
DOMExceptionName::TimeoutError => 23,
270+
DOMExceptionName::InvalidNodeTypeError => 24,
271+
DOMExceptionName::DataCloneError => 25,
272+
_ => 0,
273+
}
91274
}
92275
}
93276

94277
pub fn init(ctx: &Ctx<'_>) -> Result<()> {
95278
let globals = ctx.globals();
96-
Class::<DOMException>::define(&globals)?;
279+
280+
if let Some(constructor) = Class::<DOMException>::create_constructor(ctx)? {
281+
// the wpt tests expect this particular property descriptor
282+
globals.prop(
283+
DOMException::NAME,
284+
Property::from(constructor).writable().configurable(),
285+
)?;
286+
}
97287

98288
let dom_ex_proto = Class::<DOMException>::prototype(ctx)?.unwrap();
99289
let error_prototype = &BasePrimordials::get(ctx)?.prototype_error;

0 commit comments

Comments
 (0)