Skip to content

Commit

Permalink
Make DOMException serializable/cloneable (#1876)
Browse files Browse the repository at this point in the history
  • Loading branch information
jasnell committed Apr 5, 2024
1 parent 3023c7e commit 789638f
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 0 deletions.
19 changes: 19 additions & 0 deletions src/workerd/api/tests/js-rpc-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1226,3 +1226,22 @@ export let logging = {
assert.strictEqual(await stub.increment(1), 3);
}
}

// DOMException is structured cloneable
export let domExceptionClone = {
test() {
const de1 = new DOMException("hello", "NotAllowedError");

// custom own properties on the instance are not preserved...
de1.foo = "ignored";

const de2 = structuredClone(de1);
assert.strictEqual(de1.name, de2.name);
assert.strictEqual(de1.message, de2.message);
assert.strictEqual(de1.stack, de2.stack);
assert.strictEqual(de1.code, de2.code);
assert.notStrictEqual(de1, de2);
assert.notStrictEqual(de1.foo, de2.foo);
assert.strictEqual(de2.foo, undefined);
}
}
5 changes: 5 additions & 0 deletions src/workerd/io/worker-interface.capnp
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,11 @@ enum SerializationTag {
headers @4;
request @5;
response @6;

domException @7;
# Keep this value in sync with the DOMException::SERIALIZATION_TAG in
# /src/workerd/jsg/dom-exception (but we can't actually change this value
# without breaking things).
}

enum StreamEncoding {
Expand Down
26 changes: 26 additions & 0 deletions src/workerd/jsg/dom-exception.c++
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// https://opensource.org/licenses/Apache-2.0

#include "dom-exception.h"
#include "ser.h"
#include <workerd/jsg/memory.h>
#include <kj/string.h>
#include <map>
Expand Down Expand Up @@ -49,4 +50,29 @@ void DOMException::visitForGc(GcVisitor& visitor) {
visitor.visit(errorForStack);
}

void DOMException::serialize(jsg::Lock& js, jsg::Serializer& serializer) {
// TODO(cleanup): The `errorForStack` field is a bit unfortunate. It is an extraneous
// error object that we have to create currently in order to get the stack field because
// v8 currently does not provide a way to attach the stack to the JS wrapper object
// from C++. Sadly, the `errorForStack` ends up duplicating both the `name` and `message`
// properties. Ideally in the future, however, we can do away with errorForStack and
// instead just attach the stack property to the JS wrapper object directly. Doing so,
// we may be able to optimize things a bit more but for now, we'll just serialize the
// name and errorForStack and pull the message off the deserialized error on the other
// side.
serializer.writeLengthDelimited(name);
serializer.write(js, JsValue(errorForStack.getHandle(js)));
}

jsg::Ref<DOMException> DOMException::deserialize(
jsg::Lock& js, uint tag, jsg::Deserializer& deserializer) {
kj::String name = deserializer.readLengthDelimitedString();
auto errorForStack = KJ_ASSERT_NONNULL(deserializer.readValue(js).tryCast<JsObject>());
kj::String message = KJ_ASSERT_NONNULL(errorForStack.get(js, "message"_kj)
.tryCast<JsString>()).toString(js);
return jsg::alloc<DOMException>(kj::mv(message),
kj::mv(name),
js.v8Ref<v8::Object>(errorForStack));
}

} // namespace workerd::jsg
12 changes: 12 additions & 0 deletions src/workerd/jsg/dom-exception.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#pragma once

#include "jsg.h"
#include "ser.h"

#define JSG_DOM_EXCEPTION_FOR_EACH_ERROR_NAME(f) \
f(INDEX_SIZE_ERR, 1, "IndexSizeError") \
Expand Down Expand Up @@ -112,6 +113,17 @@ class DOMException: public Object {
tracker.trackField("errorForStack", errorForStack);
}

// TODO(cleanup): The value is taken from worker-interface.capnp, which we can't
// depend on directly here because we cannot introduce the dependency into JSG.
// Therefore we have to set it manually. A better solution long term is to actually
// move DOMException into workerd/api, but we'll do that separately.
static const uint SERIALIZATION_TAG = 7;
JSG_SERIALIZABLE(SERIALIZATION_TAG);

void serialize(jsg::Lock& js, jsg::Serializer& serializer);
static jsg::Ref<DOMException> deserialize(
jsg::Lock& js, uint tag, jsg::Deserializer& deserializer);

private:
kj::String message;
kj::String name;
Expand Down

0 comments on commit 789638f

Please sign in to comment.