Skip to content

Commit 58b8086

Browse files
Jake ChampionJakeChampion
authored andcommitted
feat: add DOMException class
1 parent 96ac02d commit 58b8086

File tree

12 files changed

+718
-228
lines changed

12 files changed

+718
-228
lines changed

integration-tests/js-compute/fixtures/crypto/bin/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ routes.set("/crypto.subtle", async () => {
167167
let error = await assertRejects(async () => {
168168
delete publicJsonWebKeyData.e;
169169
await crypto.subtle.importKey('jwk', publicJsonWebKeyData, jsonWebKeyAlgorithm, publicJsonWebKeyData.ext, publicJsonWebKeyData.key_ops)
170-
}, Error, "Data provided to an operation does not meet requirements")
170+
}, DOMException, "Data provided to an operation does not meet requirements")
171171
if (error) { return error; }
172172
return pass('ok');
173173
});

runtime/js-compute-runtime/builtins/crypto-algorithm.cpp

Lines changed: 80 additions & 199 deletions
Large diffs are not rendered by default.
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
#include "dom-exception.h"
2+
#include "builtin.h"
3+
#include "js-compute-builtins.h"
4+
#include "js/Context.h"
5+
6+
namespace builtins {
7+
8+
bool DOMException::name_get(JSContext *cx, unsigned argc, JS::Value *vp) {
9+
METHOD_HEADER(0);
10+
if (self == proto_obj) {
11+
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
12+
JSBuiltinErrNum::JSMSG_INVALID_INTERFACE, "name get", "DOMException");
13+
return false;
14+
}
15+
args.rval().setString(JS::GetReservedSlot(self, Slots::Name).toString());
16+
return true;
17+
}
18+
19+
bool DOMException::message_get(JSContext *cx, unsigned argc, JS::Value *vp) {
20+
METHOD_HEADER(0);
21+
if (self == proto_obj) {
22+
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
23+
JSBuiltinErrNum::JSMSG_INVALID_INTERFACE, "name get", "DOMException");
24+
return false;
25+
}
26+
args.rval().setString(JS::GetReservedSlot(self, Slots::Message).toString());
27+
return true;
28+
}
29+
30+
bool DOMException::code_get(JSContext *cx, unsigned argc, JS::Value *vp) {
31+
METHOD_HEADER(0);
32+
if (self == proto_obj) {
33+
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
34+
JSBuiltinErrNum::JSMSG_INVALID_INTERFACE, "name get", "DOMException");
35+
return false;
36+
}
37+
size_t length;
38+
JS::RootedString name_string(cx, JS::GetReservedSlot(self, Slots::Name).toString());
39+
auto chars = encode(cx, name_string, &length);
40+
if (!chars) {
41+
return false;
42+
}
43+
std::string_view name(chars.get(), length);
44+
int32_t code = 0;
45+
if (name == "IndexSizeError") {
46+
code = 1;
47+
} else if (name == "HierarchyRequestError") {
48+
code = 3;
49+
} else if (name == "WrongDocumentError") {
50+
code = 4;
51+
} else if (name == "InvalidCharacterError") {
52+
code = 5;
53+
} else if (name == "NoModificationAllowedError") {
54+
code = 7;
55+
} else if (name == "NotFoundError") {
56+
code = 8;
57+
} else if (name == "NotSupportedError") {
58+
code = 9;
59+
} else if (name == "InUseAttributeError") {
60+
code = 10;
61+
} else if (name == "InvalidStateError") {
62+
code = 11;
63+
} else if (name == "SyntaxError") {
64+
code = 12;
65+
} else if (name == "InvalidModificationError") {
66+
code = 13;
67+
} else if (name == "NamespaceError") {
68+
code = 14;
69+
} else if (name == "InvalidAccessError") {
70+
code = 15;
71+
} else if (name == "TypeMismatchError") {
72+
code = 17;
73+
} else if (name == "SecurityError") {
74+
code = 18;
75+
} else if (name == "NetworkError") {
76+
code = 19;
77+
} else if (name == "AbortError") {
78+
code = 20;
79+
} else if (name == "URLMismatchError") {
80+
code = 21;
81+
} else if (name == "QuotaExceededError") {
82+
code = 22;
83+
} else if (name == "TimeoutError") {
84+
code = 23;
85+
} else if (name == "InvalidNodeTypeError") {
86+
code = 24;
87+
} else if (name == "DataCloneError") {
88+
code = 25;
89+
}
90+
91+
args.rval().setInt32(code);
92+
return true;
93+
}
94+
95+
const JSFunctionSpec DOMException::methods[] = {JS_FS_END};
96+
const JSFunctionSpec DOMException::static_methods[] = {JS_FS_END};
97+
98+
const JSPropertySpec DOMException::properties[] = {
99+
JS_PSG("code", code_get, JSPROP_ENUMERATE),
100+
JS_PSG("message", message_get, JSPROP_ENUMERATE),
101+
JS_PSG("name", name_get, JSPROP_ENUMERATE),
102+
JS_INT32_PS("INDEX_SIZE_ERR", 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
103+
JS_INT32_PS("DOMSTRING_SIZE_ERR", 2, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
104+
JS_INT32_PS("HIERARCHY_REQUEST_ERR", 3, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
105+
JS_INT32_PS("WRONG_DOCUMENT_ERR", 4, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
106+
JS_INT32_PS("INVALID_CHARACTER_ERR", 5, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
107+
JS_INT32_PS("NO_DATA_ALLOWED_ERR", 6, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
108+
JS_INT32_PS("NO_MODIFICATION_ALLOWED_ERR", 7,
109+
JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
110+
JS_INT32_PS("NOT_FOUND_ERR", 8, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
111+
JS_INT32_PS("NOT_SUPPORTED_ERR", 9, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
112+
JS_INT32_PS("INUSE_ATTRIBUTE_ERR", 10, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
113+
JS_INT32_PS("INVALID_STATE_ERR", 11, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
114+
JS_INT32_PS("SYNTAX_ERR", 12, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
115+
JS_INT32_PS("INVALID_MODIFICATION_ERR", 13,
116+
JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
117+
JS_INT32_PS("NAMESPACE_ERR", 14, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
118+
JS_INT32_PS("INVALID_ACCESS_ERR", 15, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
119+
JS_INT32_PS("VALIDATION_ERR", 16, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
120+
JS_INT32_PS("TYPE_MISMATCH_ERR", 17, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
121+
JS_INT32_PS("SECURITY_ERR", 18, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
122+
JS_INT32_PS("NETWORK_ERR", 19, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
123+
JS_INT32_PS("ABORT_ERR", 20, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
124+
JS_INT32_PS("URL_MISMATCH_ERR", 21, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
125+
JS_INT32_PS("QUOTA_EXCEEDED_ERR", 22, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
126+
JS_INT32_PS("TIMEOUT_ERR", 23, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
127+
JS_INT32_PS("INVALID_NODE_TYPE_ERR", 24, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
128+
JS_INT32_PS("DATA_CLONE_ERR", 25, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
129+
JS_STRING_SYM_PS(toStringTag, "DOMException",
130+
JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
131+
JS_PS_END};
132+
const JSPropertySpec DOMException::static_properties[] = {
133+
JS_INT32_PS("INDEX_SIZE_ERR", 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
134+
JS_INT32_PS("DOMSTRING_SIZE_ERR", 2, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
135+
JS_INT32_PS("HIERARCHY_REQUEST_ERR", 3, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
136+
JS_INT32_PS("WRONG_DOCUMENT_ERR", 4, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
137+
JS_INT32_PS("INVALID_CHARACTER_ERR", 5, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
138+
JS_INT32_PS("NO_DATA_ALLOWED_ERR", 6, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
139+
JS_INT32_PS("NO_MODIFICATION_ALLOWED_ERR", 7,
140+
JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
141+
JS_INT32_PS("NOT_FOUND_ERR", 8, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
142+
JS_INT32_PS("NOT_SUPPORTED_ERR", 9, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
143+
JS_INT32_PS("INUSE_ATTRIBUTE_ERR", 10, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
144+
JS_INT32_PS("INVALID_STATE_ERR", 11, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
145+
JS_INT32_PS("SYNTAX_ERR", 12, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
146+
JS_INT32_PS("INVALID_MODIFICATION_ERR", 13,
147+
JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
148+
JS_INT32_PS("NAMESPACE_ERR", 14, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
149+
JS_INT32_PS("INVALID_ACCESS_ERR", 15, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
150+
JS_INT32_PS("VALIDATION_ERR", 16, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
151+
JS_INT32_PS("TYPE_MISMATCH_ERR", 17, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
152+
JS_INT32_PS("SECURITY_ERR", 18, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
153+
JS_INT32_PS("NETWORK_ERR", 19, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
154+
JS_INT32_PS("ABORT_ERR", 20, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
155+
JS_INT32_PS("URL_MISMATCH_ERR", 21, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
156+
JS_INT32_PS("QUOTA_EXCEEDED_ERR", 22, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
157+
JS_INT32_PS("TIMEOUT_ERR", 23, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
158+
JS_INT32_PS("INVALID_NODE_TYPE_ERR", 24, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
159+
JS_INT32_PS("DATA_CLONE_ERR", 25, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT),
160+
JS_PS_END};
161+
162+
JSObject *DOMException::create(JSContext *cx, std::string_view message, std::string_view name) {
163+
JS::RootedValueArray<2> args(cx);
164+
args[0].setString(JS_NewStringCopyN(cx, message.data(), message.size()));
165+
args[1].setString(JS_NewStringCopyN(cx, name.data(), name.size()));
166+
JS::RootedObject instance(cx);
167+
JS::RootedObject ctorObj(cx, JS_GetConstructor(cx, proto_obj));
168+
JS::RootedValue ctor(cx, JS::ObjectValue(*ctorObj));
169+
if (!JS::Construct(cx, ctor, args, &instance)) {
170+
return nullptr;
171+
}
172+
if (!instance) {
173+
return nullptr;
174+
}
175+
auto message_str = JS_NewStringCopyN(cx, message.data(), message.size());
176+
if (!message_str) {
177+
return nullptr;
178+
}
179+
JS::SetReservedSlot(instance, Slots::Message, JS::StringValue(message_str));
180+
auto name_str = JS_NewStringCopyN(cx, name.data(), name.size());
181+
if (!name_str) {
182+
return nullptr;
183+
}
184+
JS::SetReservedSlot(instance, Slots::Name, JS::StringValue(name_str));
185+
186+
return instance;
187+
}
188+
189+
void DOMException::raise(JSContext *cx, std::string_view message, std::string_view name) {
190+
JS::RootedObject errorObj(cx);
191+
errorObj.set(DOMException::create(cx, message, name));
192+
JS::RootedValue er(cx, JS::ObjectValue(*errorObj));
193+
JS_SetPendingException(cx, er);
194+
}
195+
196+
// constructor(optional DOMString message = "", optional DOMString name = "Error");
197+
bool DOMException::constructor(JSContext *cx, unsigned argc, JS::Value *vp) {
198+
CTOR_HEADER("DOMException", 0);
199+
200+
JS::RootedObject instance(cx, JS_NewObjectForConstructor(cx, &class_, args));
201+
if (!instance) {
202+
return false;
203+
}
204+
if (args.hasDefined(0)) {
205+
auto message = JS::ToString(cx, args.get(0));
206+
if (!message) {
207+
return false;
208+
}
209+
JS::SetReservedSlot(instance, Slots::Message, JS::StringValue(message));
210+
} else {
211+
JS::SetReservedSlot(instance, Slots::Message, JS_GetEmptyStringValue(cx));
212+
}
213+
if (args.hasDefined(1)) {
214+
auto name = JS::ToString(cx, args.get(1));
215+
if (!name) {
216+
return false;
217+
}
218+
JS::SetReservedSlot(instance, Slots::Name, JS::StringValue(name));
219+
} else {
220+
JS::SetReservedSlot(instance, Slots::Name, JS::StringValue(JS_NewStringCopyZ(cx, "Error")));
221+
}
222+
223+
args.rval().setObject(*instance);
224+
return true;
225+
}
226+
227+
bool DOMException::init_class(JSContext *cx, JS::HandleObject global) {
228+
JS::RootedObject proto(cx, JS::GetRealmErrorPrototype(cx));
229+
if (!proto) {
230+
return false;
231+
}
232+
233+
return init_class_impl(cx, global, proto);
234+
}
235+
236+
} // namespace builtins
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#ifndef JS_COMPUTE_RUNTIME_BUILTIN_DOM_EXCEPTION_H
2+
#define JS_COMPUTE_RUNTIME_BUILTIN_DOM_EXCEPTION_H
3+
4+
#include "builtin.h"
5+
6+
namespace builtins {
7+
8+
class DOMException : public BuiltinImpl<DOMException> {
9+
private:
10+
public:
11+
static constexpr const char *class_name = "DOMException";
12+
enum Slots { Name, Message, Code, Count };
13+
static const JSFunctionSpec static_methods[];
14+
static const JSPropertySpec static_properties[];
15+
static const JSFunctionSpec methods[];
16+
static const JSPropertySpec properties[];
17+
18+
static bool code_get(JSContext *cx, unsigned argc, JS::Value *vp);
19+
static bool message_get(JSContext *cx, unsigned argc, JS::Value *vp);
20+
static bool name_get(JSContext *cx, unsigned argc, JS::Value *vp);
21+
22+
static const unsigned ctor_length = 0;
23+
24+
static bool init_class(JSContext *cx, JS::HandleObject global);
25+
static bool constructor(JSContext *cx, unsigned argc, JS::Value *vp);
26+
static JSObject *create(JSContext *cx, std::string_view message, std::string_view name);
27+
static void raise(JSContext *cx, std::string_view message, std::string_view name);
28+
};
29+
30+
} // namespace builtins
31+
32+
#endif

runtime/js-compute-runtime/builtins/subtle-crypto.cpp

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,9 @@
1-
21
#include "subtle-crypto.h"
2+
#include "builtins/shared/dom-exception.h"
33
#include "js-compute-builtins.h"
44

55
namespace builtins {
66

7-
namespace {
8-
void convertErrorToInvalidAccessError(JSContext *cx) {
9-
MOZ_ASSERT(JS_IsExceptionPending(cx));
10-
JS::RootedValue exn(cx);
11-
if (!JS_GetPendingException(cx, &exn)) {
12-
return;
13-
}
14-
MOZ_ASSERT(exn.isObject());
15-
JS::RootedObject error(cx, &exn.toObject());
16-
JS::RootedValue name(cx, JS::StringValue(JS_NewStringCopyZ(cx, "InvalidAccessError")));
17-
JS_SetProperty(cx, error, "name", name);
18-
JS::RootedValue code(cx, JS::NumberValue(15));
19-
JS_SetProperty(cx, error, "code", code);
20-
}
21-
} // namespace
227
// digest(algorithm, data)
238
// https://w3c.github.io/webcrypto/#SubtleCrypto-method-digest
249
bool SubtleCrypto::digest(JSContext *cx, unsigned argc, JS::Value *vp) {
@@ -243,22 +228,19 @@ bool SubtleCrypto::sign(JSContext *cx, unsigned argc, JS::Value *vp) {
243228
auto identifier = normalizedAlgorithm->identifier();
244229
auto match_result = CryptoKey::is_algorithm(cx, key, identifier);
245230
if (match_result.isErr()) {
246-
JS_ReportErrorUTF8(cx, "CryptoKey doesn't match AlgorithmIdentifier");
247-
convertErrorToInvalidAccessError(cx);
231+
DOMException::raise(cx, "CryptoKey doesn't match AlgorithmIdentifier", "InvalidAccessError");
248232
return RejectPromiseWithPendingError(cx, promise);
249233
}
250234

251235
if (match_result.unwrap() == false) {
252-
JS_ReportErrorUTF8(cx, "CryptoKey doesn't match AlgorithmIdentifier");
253-
convertErrorToInvalidAccessError(cx);
236+
DOMException::raise(cx, "CryptoKey doesn't match AlgorithmIdentifier", "InvalidAccessError");
254237
return RejectPromiseWithPendingError(cx, promise);
255238
}
256239

257240
// 9. If the [[usages]] internal slot of key does not contain an entry that is "sign", then throw
258241
// an InvalidAccessError.
259242
if (!CryptoKey::canSign(key)) {
260-
JS_ReportErrorLatin1(cx, "CryptoKey doesn't support signing");
261-
convertErrorToInvalidAccessError(cx);
243+
DOMException::raise(cx, "CryptoKey doesn't support signing", "InvalidAccessError");
262244
return RejectPromiseWithPendingError(cx, promise);
263245
}
264246

@@ -349,15 +331,13 @@ bool SubtleCrypto::verify(JSContext *cx, unsigned argc, JS::Value *vp) {
349331
auto identifier = normalizedAlgorithm->identifier();
350332
auto match_result = CryptoKey::is_algorithm(cx, key, identifier);
351333
if (match_result.isErr() || match_result.unwrap() == false) {
352-
JS_ReportErrorUTF8(cx, "CryptoKey doesn't match AlgorithmIdentifier");
353-
convertErrorToInvalidAccessError(cx);
334+
DOMException::raise(cx, "CryptoKey doesn't match AlgorithmIdentifier", "InvalidAccessError");
354335
return RejectPromiseWithPendingError(cx, promise);
355336
}
356337
// 10. If the [[usages]] internal slot of key does not contain an entry that is "verify", then
357338
// throw an InvalidAccessError.
358339
if (!CryptoKey::canVerify(key)) {
359-
JS_ReportErrorUTF8(cx, "CryptoKey doesn't support verification");
360-
convertErrorToInvalidAccessError(cx);
340+
DOMException::raise(cx, "CryptoKey doesn't support verification", "InvalidAccessError");
361341
return RejectPromiseWithPendingError(cx, promise);
362342
}
363343
// 11. Let result be the result of performing the verify operation specified by

runtime/js-compute-runtime/js-compute-builtins.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
#include "builtins/request-response.h"
5858
#include "builtins/secret-store.h"
5959
#include "builtins/shared/console.h"
60+
#include "builtins/shared/dom-exception.h"
6061
#include "builtins/shared/performance.h"
6162
#include "builtins/shared/text-decoder.h"
6263
#include "builtins/shared/text-encoder.h"
@@ -1282,6 +1283,9 @@ bool define_fastly_sys(JSContext *cx, HandleObject global, FastlyOptions options
12821283
if (!GlobalProperties::init(cx, global))
12831284
return false;
12841285

1286+
if (!builtins::DOMException::init_class(cx, global)) {
1287+
return false;
1288+
}
12851289
if (!builtins::Backend::init_class(cx, global))
12861290
return false;
12871291
if (!builtins::Fastly::create(cx, global, options))

tests/wpt-harness/expectations/url/urlsearchparams-constructor.any.js.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"status": "PASS"
1010
},
1111
"URLSearchParams constructor, DOMException as argument": {
12-
"status": "FAIL"
12+
"status": "PASS"
1313
},
1414
"URLSearchParams constructor, empty string as argument": {
1515
"status": "PASS"

0 commit comments

Comments
 (0)