Skip to content
File

Blob: src/workerd/jsg/dom-exception.c++

4.8 KB
1// Copyright (c) 2017-2022 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5#include "dom-exception.h"
6 
7#include "jsvalue.h"
8#include "ser.h"
9 
10#include <workerd/jsg/memory.h>
11 
12#include <kj/string.h>
13 
14#include <map>
15 
16namespace workerd::jsg {
17 
18Ref<DOMException> DOMException::constructor(const v8::FunctionCallbackInfo<v8::Value>& args,
19 Optional<kj::String> message,
20 Optional<kj::String> name) {
21 Lock& js = Lock::from(args.GetIsolate());
22 kj::String errMessage = kj::mv(message).orDefault([&] { return kj::String(); });
23 
24 // V8 gives Error objects a non-standard (but widely known) `stack` property, and Web IDL
25 // requires that DOMException get any non-standard properties that Error gets. Chrome honors
26 // this requirement only for runtime-generated DOMExceptions -- script-generated DOMExceptions
27 // don't get `stack`, even though script-generated Errors do. It's more convenient and, IMO,
28 // more conformant to just give all DOMExceptions a `stack` property.
29 jsg::check(v8::Exception::CaptureStackTrace(js.v8Context(), args.This()));
30 
31 // This part is a bit of a hack. By default, the various properties on JavaScript errors
32 // are not enumerable. However, our implementation of DOMException has always defined
33 // them as enumerable, which means just setting the stack above would be a breaking change.
34 // To maintain backwards compat we have to define the stack as enumerable here.
35 v8::PropertyDescriptor prop;
36 prop.set_enumerable(true);
37 v8::Local<v8::String> stackName = js.str("stack"_kjc);
38 jsg::check(args.This()->DefineProperty(js.v8Context(), stackName, prop));
39 
40 return js.alloc<DOMException>(
41 kj::mv(errMessage), kj::mv(name).orDefault([] { return kj::str("Error"); }));
42}
43 
44kj::StringPtr DOMException::getName() const {
45 return name;
46}
47 
48kj::StringPtr DOMException::getMessage() const {
49 return message;
50}
51 
52int DOMException::getCode() const {
53 static const std::map<kj::StringPtr, int> legacyCodes{
54#define MAP_ENTRY(name, code, friendlyName) {friendlyName, code},
55 JSG_DOM_EXCEPTION_FOR_EACH_ERROR_NAME(MAP_ENTRY)
56#undef MAP_ENTRY
57 };
58 auto code = legacyCodes.find(name);
59 if (code != legacyCodes.end()) {
60 return code->second;
61 }
62 return 0;
63}
64 
65void DOMException::serialize(jsg::Lock& js, jsg::Serializer& serializer) {
66 serializer.writeLengthDelimited(name);
67 serializer.writeLengthDelimited(message);
68 
69 // It's a bit unfortunate that the stack here ends up also including the name and message
70 // so we end up duplicating some of the information here, but that's OK. It's better to
71 // keep this implementation simple rather than to implement any kind of deduplication.
72 KJ_IF_SOME(stack, this->stack.get(js, "stack")) {
73 serializer.writeLengthDelimited(stack);
74 } else {
75 // This branch shouldn't really be taken in the typical case. It's only here
76 // to handle the case where the stack property could not be unwrapped for some
77 // reason. We don't need to treat it as an error case, just set the stack to
78 // the empty string and move on.
79 serializer.writeLengthDelimited(""_kj);
80 }
81}
82 
83jsg::Ref<DOMException> DOMException::deserialize(
84 jsg::Lock& js, uint tag, jsg::Deserializer& deserializer) {
85 switch (tag) {
86 case SERIALIZATION_TAG_V2: {
87 kj::String name = deserializer.readLengthDelimitedString();
88 kj::String message = deserializer.readLengthDelimitedString();
89 kj::String stack = deserializer.readLengthDelimitedString();
90 return js.domException(kj::mv(name), kj::mv(message), kj::mv(stack));
91 }
92 case SERIALIZATION_TAG: {
93 // This is the original serialization of DOMException. It was only
94 // used for a very short period of time (a matter of weeks) but there's
95 // still a remote chance that someone might use it in some persisted state
96 // somewhere. So let's go ahead and support it.
97 kj::String name = deserializer.readLengthDelimitedString();
98 auto errorForStack = KJ_UNWRAP_OR(deserializer.readValue(js).tryCast<JsObject>(), {
99 JSG_FAIL_REQUIRE(
100 DOMDataCloneError, "Deserialization failed: DOMException payload is not an object");
101 });
102 auto messageJs = KJ_UNWRAP_OR(errorForStack.get(js, "message"_kj).tryCast<JsString>(), {
103 JSG_FAIL_REQUIRE(
104 DOMDataCloneError, "Deserialization failed: DOMException message is not a string");
105 });
106 auto stackJs = KJ_UNWRAP_OR(errorForStack.get(js, "stack").tryCast<JsString>(), {
107 JSG_FAIL_REQUIRE(
108 DOMDataCloneError, "Deserialization failed: DOMException stack is not a string");
109 });
110 kj::String message = messageJs.toString(js);
111 kj::String stack = stackJs.toString(js);
112 return js.domException(kj::mv(message), kj::mv(name), kj::mv(stack));
113 }
114 default:
115 KJ_UNREACHABLE;
116 }
117}
118 
119} // namespace workerd::jsg