Skip to content
File

Blob: src/workerd/jsg/jsvalue.h

cpp1109 lines
1#pragma once
2 
3#include "jsg.h"
4 
5#include <v8-container.h>
6#include <v8-date.h>
7#include <v8-external.h>
8#include <v8-proxy.h>
9 
10namespace workerd::jsg {
11 
12constexpr uint64_t MAX_SAFE_INTEGER = (1ull << 53) - 1;
13 
14inline void requireOnStack(void* self) {
15#ifdef KJ_DEBUG
16 kj::requireOnStack(self, "JsValue types must be allocated on stack");
17#endif
18}
19 
20// The types listed in the JS_IS_TYPES macro are translated into is{Name}()
21// methods on the JsValue type. These correspond directly to equivalent v8::Value
22// types and therefore must be kept in sync.
23#define JS_IS_TYPES(V) \
24 V(Undefined) \
25 V(Null) \
26 V(NullOrUndefined) \
27 V(True) \
28 V(False) \
29 V(ArgumentsObject) \
30 V(NativeError) \
31 V(Name) \
32 V(AsyncFunction) \
33 V(GeneratorFunction) \
34 V(GeneratorObject) \
35 V(WeakMap) \
36 V(WeakSet) \
37 V(WeakRef) \
38 V(WasmNull) \
39 V(ModuleNamespaceObject) \
40 V(MapIterator) \
41 V(SetIterator) \
42 V(External) \
43 V(BigIntObject) \
44 V(BooleanObject) \
45 V(NumberObject) \
46 V(StringObject) \
47 V(SymbolObject) \
48 V(TypedArray) \
49 V(Uint8ClampedArray) \
50 V(Int8Array) \
51 V(Uint16Array) \
52 V(Int16Array) \
53 V(Uint32Array) \
54 V(Int32Array) \
55 V(Float16Array) \
56 V(Float32Array) \
57 V(Float64Array) \
58 V(BigInt64Array) \
59 V(BigUint64Array) \
60 V(DataView) \
61 V(SharedArrayBuffer) \
62 V(WasmMemoryObject) \
63 V(WasmModuleObject) \
64 JS_TYPE_CLASSES(V)
65 
66struct JsValueWrapper;
67 
68// Filters for `JsObject::getPropertyNames()`
69enum PropertyFilter {
70 ALL_PROPERTIES = 0,
71 ONLY_WRITABLE = 1,
72 ONLY_ENUMERABLE = 2,
73 ONLY_CONFIGURABLE = 4,
74 SKIP_STRINGS = 8,
75 SKIP_SYMBOLS = 16
76};
77enum KeyCollectionFilter { OWN_ONLY, INCLUDE_PROTOTYPES };
78enum IndexFilter { INCLUDE_INDICES, SKIP_INDICES };
79 
80enum PromiseState { PENDING, FULFILLED, REJECTED };
81 
82// A JsValue is an abstraction for a JavaScript value that has not been mapped
83// to a C++ type. It wraps an underlying v8::Local<T> in order to avoid direct
84// use of the v8 API in many cases. The JsValue (and JsRef<T>) are meant to
85// fully replace (eventually) the use of jsg::V8Ref<T> and jsg::Value in
86// addition to replacing direct use of v8::Local<T>.
87//
88// JsValue types (including the related JsBoolean, JsArray, JsObject, etc) can
89// only be stack allocated and are not suitable for persistent storage of the
90// value. To persist the JavaScript value, use JsRef<T>.
91//
92// The jsg::Lock instance is used to create instances of the Js* types. For
93// example:
94//
95// auto& js = Lock::from(isolate);
96// js.withinHandleScope([&] {
97// JsString str = js.str("foo");
98// JsNumber num = js.num(123);
99// JsArray arr = js.arr(js.str("foo"), js.num(123));
100// JsObject obj = js.obj();
101// obj.set(js, "foo", js.str("bar"));
102// });
103//
104// Note that the `js.withinHandleScope()` is only necessary if the code is not
105// already running within a handle scope (which jsg mapped methods on jsg::Object
106// instances always are).
107//
108// All of the Js* types can be trivially cast to JsValue via assignment.
109//
110// JsValue val = js.str("foo");
111//
112// A JsValue can be trivially cast to a more specific type if the underlying
113// JS type is compatible.
114//
115// JsValue val = js.str("foo");
116// KJ_IF_SOME(str, val.tryCast<JsString>()) {
117// // str is a JsString
118// }
119// KJ_IF_SOME(num, val.tryCast<JsNumber>()) {
120// // never happens since val is not a number
121// }
122//
123// Because JsValue types are trivially assignable to v8::Local<v8::Value>
124// they can be used together with TypeHandler<T> to convert to specific C++
125// types:
126//
127// auto obj = js.obj();
128// const TypeHandler<MyStruct>& handler = // ...
129// MyStruct v = KJ_ASSERT_NONNULL(handler.tryUnwrap(js, obj));
130class JsValue final {
131 public:
132 template <typename T>
133 kj::Maybe<T> tryCast() const KJ_WARN_UNUSED_RESULT;
134 
135 operator v8::Local<v8::Value>() const {
136 return inner;
137 }
138 
139 bool operator==(const JsValue& other) const;
140 bool strictEquals(const JsValue& other) const;
141 
142 bool isTruthy(Lock& js) const KJ_WARN_UNUSED_RESULT;
143 kj::String toString(Lock& js) const KJ_WARN_UNUSED_RESULT;
144 kj::String typeOf(Lock& js) const KJ_WARN_UNUSED_RESULT;
145 JsString toJsString(Lock& js) const KJ_WARN_UNUSED_RESULT;
146 
147#define V(Type) bool is##Type() const KJ_WARN_UNUSED_RESULT;
148 JS_IS_TYPES(V)
149#undef V
150 
151 kj::String toJson(Lock& js) const KJ_WARN_UNUSED_RESULT;
152 static JsValue fromJson(Lock& js, kj::ArrayPtr<const char> input) KJ_WARN_UNUSED_RESULT;
153 static JsValue fromJson(Lock& js, const JsValue& input) KJ_WARN_UNUSED_RESULT;
154 
155 JsRef<JsValue> addRef(Lock& js) KJ_WARN_UNUSED_RESULT;
156 
157 JsValue structuredClone(
158 Lock& js, kj::Maybe<kj::Array<JsValue>> maybeTransfers = kj::none) KJ_WARN_UNUSED_RESULT;
159 
160 template <typename T>
161 static kj::Maybe<T&> tryGetExternal(Lock& js, const JsValue& value) KJ_WARN_UNUSED_RESULT;
162 
163 explicit JsValue(v8::Local<v8::Value> inner);
164 
165 private:
166 v8::Local<v8::Value> inner;
167 friend class Lock;
168 friend struct JsValueWrapper;
169 template <typename T, typename Self>
170 friend class JsBase;
171 template <typename T>
172 friend class JsRef;
173 
174#define V(Name) friend class Js##Name;
175 JS_TYPE_CLASSES(V)
176#undef V
177};
178 
179template <typename T, typename Self>
180class JsBase {
181 public:
182 operator v8::Local<v8::Value>() const {
183 return inner;
184 }
185 // Only provide the typed conversion when T is not already v8::Value,
186 // to avoid a duplicate operator signature (e.g. for JsBufferSource).
187 operator v8::Local<T>() const
188 requires(!kj::isSameType<T, v8::Value>())
189 {
190 return inner;
191 }
192 operator JsValue() const {
193 return JsValue(inner.template As<v8::Value>());
194 }
195 bool operator==(const JsValue& other) const KJ_WARN_UNUSED_RESULT {
196 return inner == other.inner;
197 }
198 bool operator==(const JsBase& other) const KJ_WARN_UNUSED_RESULT {
199 return inner == other.inner;
200 }
201 explicit JsBase(v8::Local<T> inner): inner(inner) {
202 requireOnStack(this);
203 }
204 JsRef<Self> addRef(Lock& js) KJ_WARN_UNUSED_RESULT;
205 
206 private:
207 v8::Local<T> inner;
208 friend class Lock;
209 friend class JsValue;
210#define V(Name) friend class Js##Name;
211 JS_TYPE_CLASSES(V)
212#undef V
213 friend struct JsValueWrapper;
214 template <typename U>
215 friend class JsRef;
216};
217 
218class JsBoolean final: public JsBase<v8::Boolean, JsBoolean> {
219 public:
220 bool value(Lock& js) const KJ_WARN_UNUSED_RESULT;
221 
222 using JsBase<v8::Boolean, JsBoolean>::JsBase;
223};
224 
225class JsArray final: public JsBase<v8::Array, JsArray> {
226 public:
227 operator JsObject() const;
228 uint32_t size() const KJ_WARN_UNUSED_RESULT;
229 JsValue get(Lock& js, uint32_t i) const KJ_WARN_UNUSED_RESULT;
230 void add(Lock& js, const JsValue& value);
231 
232 using JsBase<v8::Array, JsArray>::JsBase;
233};
234 
235class JsArrayBuffer final: public JsBase<v8::ArrayBuffer, JsArrayBuffer> {
236 public:
237 static JsArrayBuffer create(Lock& js, size_t length);
238 
239 // Allocate and copy data from the given ArrayPtr in a single step.
240 static JsArrayBuffer create(Lock& js, kj::ArrayPtr<const kj::byte> data);
241 
242 static JsArrayBuffer create(Lock& js, std::unique_ptr<v8::BackingStore> backingStore);
243 
244 JsArrayBuffer slice(Lock& js, size_t newLength) const;
245 
246 kj::ArrayPtr<kj::byte> asArrayPtr();
247 kj::ArrayPtr<const kj::byte> asArrayPtr() const;
248 
249 size_t size() const;
250 
251 // Return a copy of this buffer's data as a kj::Array.
252 kj::Array<kj::byte> copy();
253 
254 using JsBase<v8::ArrayBuffer, JsArrayBuffer>::JsBase;
255};
256 
257class JsArrayBufferView final: public JsBase<v8::ArrayBufferView, JsArrayBufferView> {
258 public:
259 template <typename T = kj::byte>
260 kj::ArrayPtr<T> asArrayPtr() {
261 v8::Local<v8::ArrayBufferView> inner = *this;
262 auto buf = inner->Buffer();
263 if (buf->WasDetached()) [[unlikely]] {
264 return nullptr;
265 }
266 auto byteLength = inner->ByteLength();
267 T* data = reinterpret_cast<T*>(static_cast<kj::byte*>(buf->Data()) + inner->ByteOffset());
268 return kj::ArrayPtr(data, byteLength / sizeof(T));
269 }
270 
271 size_t size() const;
272 
273 // Returns true if the underlying view is an integer-typed TypedArray
274 // (e.g. Uint8Array, Int32Array, BigUint64Array) as opposed to a float-typed
275 // TypedArray or DataView.
276 bool isIntegerType() const;
277 
278 using JsBase<v8::ArrayBufferView, JsArrayBufferView>::JsBase;
279};
280 
281class JsUint8Array final: public JsBase<v8::Uint8Array, JsUint8Array> {
282 public:
283 static JsUint8Array create(Lock& js, size_t length);
284 
285 // Allocate and copy data from the given ArrayPtr in a single step.
286 static JsUint8Array create(Lock& js, kj::ArrayPtr<const kj::byte> data);
287 
288 // Create a Uint8Array view over the given ArrayBuffer.
289 static JsUint8Array create(Lock& js, JsArrayBuffer& buffer);
290 
291 static JsUint8Array create(
292 Lock& js, std::unique_ptr<v8::BackingStore> backingStore, size_t byteOffset, size_t length);
293 
294 JsUint8Array slice(Lock& js, size_t newLength) const;
295 
296 template <typename T = kj::byte>
297 kj::ArrayPtr<T> asArrayPtr() {
298 v8::Local<v8::Uint8Array> inner = *this;
299 auto buf = inner->Buffer();
300 if (buf->WasDetached()) [[unlikely]] {
301 return nullptr;
302 }
303 auto byteLength = inner->ByteLength();
304 T* data = reinterpret_cast<T*>(static_cast<kj::byte*>(buf->Data()) + inner->ByteOffset());
305 return kj::ArrayPtr(data, byteLength / sizeof(T));
306 }
307 
308 kj::ArrayPtr<const kj::byte> asArrayPtr() const;
309 
310 size_t size() const;
311 
312 // Return a copy of this buffer's data as a kj::Array.
313 kj::Array<kj::byte> copy();
314 
315 using JsBase<v8::Uint8Array, JsUint8Array>::JsBase;
316};
317 
318// A lightweight wrapper for ArrayBuffer | ArrayBufferView (the Web IDL "BufferSource"
319// type). Unlike jsg::BufferSource, this does NOT maintain a BackingStore, does NOT
320// support detach, and is stack-only. Use JsRef<JsBufferSource> for persistent storage.
321//
322// This type is based on v8::Value (not a specific V8 type) because there is no single
323// V8 type that represents both ArrayBuffer and ArrayBufferView. It is NOT included in
324// JS_TYPE_CLASSES; instead, JsValue::tryCast and JsValueWrapper handle it specially.
325class JsBufferSource final: public JsBase<v8::Value, JsBufferSource> {
326 public:
327 JsBufferSource(JsArrayBuffer& buffer): JsBase(static_cast<v8::Local<v8::Value>>(buffer)) {}
328 JsBufferSource(JsUint8Array& buffer): JsBase(static_cast<v8::Local<v8::Value>>(buffer)) {}
329 JsBufferSource(JsArrayBufferView& buffer): JsBase(static_cast<v8::Local<v8::Value>>(buffer)) {}
330 JsBufferSource(v8::Local<v8::SharedArrayBuffer> buffer)
331 : JsBase(static_cast<v8::Local<v8::Value>>(buffer)) {}
332 
333 kj::ArrayPtr<kj::byte> asArrayPtr();
334 
335 size_t size() const;
336 
337 // Returns true if the underlying value is an integer-typed TypedArray.
338 bool isIntegerType() const;
339 
340 bool isSharedArrayBuffer() const;
341 bool isArrayBuffer() const;
342 bool isArrayBufferView() const;
343 bool isResizable() const;
344 
345 // Return a copy of this buffer's data as a kj::Array.
346 kj::Array<kj::byte> copy();
347 
348 using JsBase<v8::Value, JsBufferSource>::JsBase;
349};
350 
351class JsString final: public JsBase<v8::String, JsString> {
352 public:
353 int length(Lock& js) const KJ_WARN_UNUSED_RESULT;
354 size_t utf8Length(Lock& js) const KJ_WARN_UNUSED_RESULT;
355 kj::String toString(Lock& js) const KJ_WARN_UNUSED_RESULT;
356 jsg::USVString toUSVString(Lock& js) const KJ_WARN_UNUSED_RESULT;
357 jsg::DOMString toDOMString(Lock& js) const KJ_WARN_UNUSED_RESULT;
358 
359 int hashCode() const;
360 
361 bool isFlat() const;
362 bool isOneByte(Lock& js) const KJ_WARN_UNUSED_RESULT;
363 bool containsOnlyOneByte() const;
364 
365 bool operator==(const JsString& other) const;
366 
367 // "Internalize" the string. Returns a string with the same content but which is identity-equal
368 // to all other internalized strings with the same content. If the string is already
369 // internalized, this returns the same value. Note that strings originating from literals in the
370 // code are always internalized.
371 JsString internalize(Lock& js) const;
372 
373 static JsString concat(Lock& js, const JsString& one, const JsString& two) KJ_WARN_UNUSED_RESULT;
374 
375 enum WriteFlags {
376 NONE = v8::String::WriteFlags::kNone,
377 NULL_TERMINATION = v8::String::WriteFlags::kNullTerminate,
378 REPLACE_INVALID_UTF8 = v8::String::WriteFlags::kReplaceInvalidUtf8,
379 };
380 
381 template <typename T>
382 kj::Array<T> toArray(Lock& js, WriteFlags options = WriteFlags::NONE) const KJ_WARN_UNUSED_RESULT;
383 
384 struct WriteIntoStatus {
385 // The number of elements (e.g. char, byte, uint16_t) read from this string.
386 size_t read;
387 // The number of elements (e.g. char, byte, uint16_t) written to the buffer.
388 size_t written;
389 };
390 
391 // Copy string contents into a provided buffer (off-heap memory).
392 //
393 // IMPORTANT: This method does NOT flatten the V8 string or hold V8 heap locks. It safely
394 // copies data out of V8's heap into your buffer. This makes it safe to use before calling
395 // GC-triggering operations like Lock::allocBackingStore().
396 WriteIntoStatus writeInto(
397 Lock& js, kj::ArrayPtr<char> buffer, WriteFlags options = WriteFlags::NONE) const;
398 WriteIntoStatus writeInto(
399 Lock& js, kj::ArrayPtr<kj::byte> buffer, WriteFlags options = WriteFlags::NONE) const;
400 WriteIntoStatus writeInto(
401 Lock& js, kj::ArrayPtr<uint16_t> buffer, WriteFlags options = WriteFlags::NONE) const;
402 
403 using JsBase<v8::String, JsString>::JsBase;
404};
405 
406class JsRegExp final: public JsBase<v8::RegExp, JsRegExp> {
407 public:
408 kj::Maybe<JsArray> operator()(Lock& js, const JsString& input) const KJ_WARN_UNUSED_RESULT;
409 kj::Maybe<JsArray> operator()(Lock& js, kj::StringPtr input) const KJ_WARN_UNUSED_RESULT;
410 using JsBase<v8::RegExp, JsRegExp>::JsBase;
411 
412 bool match(Lock& js, kj::StringPtr input);
413};
414 
415class JsDate final: public JsBase<v8::Date, JsDate> {
416 public:
417 kj::String toUTCString(Lock& js) const;
418 kj::String toISOString(Lock& js) const;
419 operator kj::Date() const;
420 using JsBase<v8::Date, JsDate>::JsBase;
421};
422 
423// Note `jsg::JsPromise` and `jsg::Promise` are not the same things.
424//
425// `jsg::JsPromise` wraps an arbitrary `v8::Local<v8::Promise>` to avoid direct use of the V8 API.
426// They have the same restrictions as other `JsValue`s (e.g. can only be stack allocated).
427// `jsg::JsPromise` cannot be awaited in C++. They are opaque references to JavaScript promises.
428//
429// `jsg::Promise<T>` wraps an JavaScript promise to an instantiable C++ type `T` with syntax that
430// makes it natural and ergonomic to consume within C++ (e.g. they provide a `then()` C++ method).
431//
432// You'll usually want to use `jsg::Promise<T>`. `jsg::JsPromise` should only be used when you need
433// direct access to the promise state (e.g. the promise state or its fulfilled value).
434class JsPromise final: public JsBase<v8::Promise, JsPromise> {
435 public:
436 PromiseState state();
437 JsValue result();
438 using JsBase<v8::Promise, JsPromise>::JsBase;
439};
440 
441class JsProxy final: public JsBase<v8::Proxy, JsProxy> {
442 public:
443 JsValue target();
444 JsValue handler();
445 using JsBase<v8::Proxy, JsProxy>::JsBase;
446};
447 
448class JsSymbol final: public JsBase<v8::Symbol, JsSymbol> {
449 public:
450 kj::String description(Lock& js) const KJ_WARN_UNUSED_RESULT;
451 
452 using JsBase<v8::Symbol, JsSymbol>::JsBase;
453};
454 
455class JsSet final: public JsBase<v8::Set, JsSet> {
456 public:
457 void add(Lock& js, const JsValue& value);
458 bool has(Lock& js, const JsValue& value) const;
459 bool delete_(Lock& js, const JsValue& value);
460 void clear();
461 size_t size() const;
462 
463 template <IsJsValue... Args>
464 void addAll(Lock& js, Args... args) {
465 (check(inner->Add(js.v8Context(), args.inner)), ...);
466 }
467 
468 void addAll(Lock& js, kj::ArrayPtr<const JsValue> values);
469 
470 operator JsArray() const;
471 
472 using JsBase<v8::Set, JsSet>::JsBase;
473};
474 
475class JsBigInt final: public JsBase<v8::BigInt, JsBigInt> {
476 public:
477 // If the BigInt value does not fit in int64_t, returns kj::none
478 // and schedules an exception on the isolate.
479 kj::Maybe<int64_t> toInt64(Lock& js) const KJ_WARN_UNUSED_RESULT;
480 
481 // If the BigInt value does not fit in int64_t, returns kj::none
482 // and schedules an exception on the isolate.
483 kj::Maybe<uint64_t> toUint64(Lock& js) const KJ_WARN_UNUSED_RESULT;
484 
485 using JsBase<v8::BigInt, JsBigInt>::JsBase;
486};
487 
488class JsInt32 final: public JsBase<v8::Int32, JsInt32> {
489 public:
490 kj::Maybe<int32_t> value(Lock& js) const KJ_WARN_UNUSED_RESULT;
491 
492 using JsBase<v8::Int32, JsInt32>::JsBase;
493};
494 
495class JsUint32 final: public JsBase<v8::Uint32, JsUint32> {
496 public:
497 kj::Maybe<uint32_t> value(Lock& js) const KJ_WARN_UNUSED_RESULT;
498 
499 using JsBase<v8::Uint32, JsUint32>::JsBase;
500};
501 
502class JsNumber final: public JsBase<v8::Number, JsNumber> {
503 public:
504 kj::Maybe<double> value(Lock& js) const KJ_WARN_UNUSED_RESULT;
505 bool isSafeInteger(Lock& js) const KJ_WARN_UNUSED_RESULT;
506 kj::Maybe<double> toSafeInteger(Lock& js) const KJ_WARN_UNUSED_RESULT;
507 
508 using JsBase<v8::Number, JsNumber>::JsBase;
509};
510 
511class JsObject final: public JsBase<v8::Object, JsObject> {
512 public:
513 template <typename T>
514 bool isInstanceOf(Lock& js) {
515 return js.getInstance(inner, typeid(T)) != kj::none;
516 }
517 
518 template <typename T>
519 kj::Maybe<jsg::Ref<T>> tryUnwrapAs(Lock& js) {
520 KJ_IF_SOME(ins, js.getInstance(inner, typeid(T))) {
521 return _jsgThis(static_cast<T*>(&ins));
522 } else {
523 return kj::none;
524 }
525 }
526 
527 void set(Lock& js, const JsValue& name, const JsValue& value);
528 void set(Lock& js, kj::StringPtr name, const JsValue& value);
529 void setReadOnly(Lock& js, kj::StringPtr name, const JsValue& value);
530 void setNonEnumerable(Lock& js, const JsSymbol& name, const JsValue& value);
531 
532 // Like set but uses the defineProperty API instead in order to override
533 // the default property attributes. This is useful for defining properties
534 // that otherwise would not be normally settable, such as the name of an
535 // error object.
536 void defineProperty(Lock& js, kj::StringPtr name, const JsValue& value);
537 
538 JsValue get(Lock& js, const JsValue& name) KJ_WARN_UNUSED_RESULT;
539 JsValue get(Lock& js, kj::StringPtr name) KJ_WARN_UNUSED_RESULT;
540 
541 enum class HasOption {
542 NONE,
543 OWN,
544 };
545 
546 bool has(Lock& js, const JsValue& name, HasOption option = HasOption::NONE) KJ_WARN_UNUSED_RESULT;
547 bool has(Lock& js, kj::StringPtr name, HasOption option = HasOption::NONE) KJ_WARN_UNUSED_RESULT;
548 void delete_(Lock& js, const JsValue& name);
549 void delete_(Lock& js, kj::StringPtr name);
550 
551 void setPrivate(Lock& js, kj::StringPtr name, const JsValue& value);
552 JsValue getPrivate(Lock& js, kj::StringPtr name) KJ_WARN_UNUSED_RESULT;
553 bool hasPrivate(Lock& js, kj::StringPtr name) KJ_WARN_UNUSED_RESULT;
554 
555 int hashCode() const;
556 
557 kj::String getConstructorName() KJ_WARN_UNUSED_RESULT;
558 JsArray getPropertyNames(Lock& js,
559 KeyCollectionFilter keyFilter,
560 PropertyFilter propertyFilter,
561 IndexFilter indexFilter) KJ_WARN_UNUSED_RESULT;
562 JsArray previewEntries(bool* isKeyValue) KJ_WARN_UNUSED_RESULT;
563 
564 // Returns the object's prototype, i.e. the property `__proto__`.
565 //
566 // Note that when called on a class constructor, this does NOT return `.prototype`, it still
567 // returns `.__proto__`. Usefully, though, a class constructor's `__proto__` is always the
568 // parent class's constructor.
569 JsValue getPrototype(Lock& js) KJ_WARN_UNUSED_RESULT;
570 
571 using JsBase<v8::Object, JsObject>::JsBase;
572 
573 void recursivelyFreeze(Lock&);
574 void seal(Lock&);
575 JsObject jsonClone(Lock&);
576};
577 
578// Defined here because `JsObject` is an incomplete type in `jsg.h`.
579template <typename T>
580inline JsObject Lock::getPrototypeFor() {
581 return JsObject(getPrototypeFor(typeid(T)));
582}
583 
584class JsMap final: public JsBase<v8::Map, JsMap> {
585 public:
586 operator JsObject();
587 
588 void set(Lock& js, const JsValue& name, const JsValue& value);
589 void set(Lock& js, kj::StringPtr name, const JsValue& value);
590 JsValue get(Lock& js, const JsValue& name) KJ_WARN_UNUSED_RESULT;
591 JsValue get(Lock& js, kj::StringPtr name) KJ_WARN_UNUSED_RESULT;
592 bool has(Lock& js, const JsValue& name) KJ_WARN_UNUSED_RESULT;
593 bool has(Lock& js, kj::StringPtr name) KJ_WARN_UNUSED_RESULT;
594 void delete_(Lock& js, const JsValue& name);
595 void delete_(Lock& js, kj::StringPtr name);
596 
597 int hashCode() const;
598 
599 using JsBase<v8::Map, JsMap>::JsBase;
600};
601 
602template <typename T>
603inline kj::Maybe<T> JsValue::tryCast() const {
604 if constexpr (kj::isSameType<T, JsValue>()) {
605 return JsValue(inner);
606 }
607#define V(Name) \
608 else if constexpr (kj::isSameType<T, Js##Name>()) { \
609 if (!inner->Is##Name()) return kj::none; \
610 return T(inner.template As<v8::Name>()); \
611 }
612 JS_TYPE_CLASSES(V)
613#undef V
614 // JsBufferSource is not in JS_TYPE_CLASSES because there is no
615 // v8::Value::IsBufferSource() method. Handle it explicitly.
616 else if constexpr (kj::isSameType<T, JsBufferSource>()) {
617 if (!inner->IsArrayBuffer() && !inner->IsArrayBufferView()) return kj::none;
618 return T(inner);
619 }
620 else {
621 return kj::none;
622 }
623}
624 
625template <typename T>
626inline kj::Maybe<T&> JsValue::tryGetExternal(Lock& js, const JsValue& value) {
627 if (!value.isExternal()) return kj::none;
628 return kj::Maybe<T&>(
629 *static_cast<T*>(value.inner.As<v8::External>()->Value(v8::kExternalPointerTypeTagDefault)));
630}
631 
632template <typename T>
633inline kj::Array<T> JsString::toArray(Lock& js, WriteFlags options) const {
634 if constexpr (kj::isSameType<T, kj::byte>()) {
635 KJ_DASSERT(inner->ContainsOnlyOneByte());
636 auto buf = kj::heapArray<kj::byte>(inner->Length());
637 inner->WriteOneByteV2(js.v8Isolate, 0, buf.size(), buf.begin(), options);
638 return kj::mv(buf);
639 } else {
640 auto buf = kj::heapArray<uint16_t>(inner->Length());
641 inner->WriteV2(js.v8Isolate, 0, buf.size(), buf.begin(), options);
642 return kj::mv(buf);
643 }
644}
645 
646template <typename... Args>
647 requires(std::assignable_from<JsValue&, Args> && ...)
648inline JsArray Lock::arr(const Args&... args) {
649 v8::Local<v8::Value> values[] = {args...};
650 return JsArray(v8::Array::New(v8Isolate, &values[0], sizeof...(Args)));
651}
652 
653template <typename T, typename Func>
654inline JsArray Lock::arr(kj::ArrayPtr<T> values, Func fn) {
655 v8::LocalVector<v8::Value> vec(v8Isolate);
656 vec.reserve(values.size());
657 for (const T& val: values) {
658 vec.push_back(fn(*this, val));
659 }
660 return JsArray(v8::Array::New(v8Isolate, vec.data(), vec.size()));
661}
662 
663template <typename... Args>
664 requires(std::assignable_from<JsValue&, Args> && ...)
665inline JsSet Lock::set(const Args&... args) {
666 auto set = v8::Set::New(v8Isolate);
667 (check(set->Add(v8Context(), args.inner)), ...);
668 return JsSet(set);
669}
670 
671template <typename T>
672inline JsObject Lock::opaque(T&& inner) {
673 auto wrapped = wrapOpaque(v8Context(), kj::mv(inner));
674 KJ_ASSERT(!wrapped.IsEmpty());
675 KJ_ASSERT(wrapped->IsObject());
676 return JsObject(wrapped.template As<v8::Object>());
677}
678 
679class JsFunction final: public JsBase<v8::Function, JsFunction> {
680 public:
681 using JsBase<v8::Function, JsFunction>::JsBase;
682 
683 // Calls the function with the given receiver and arguments.
684 template <IsJsValue... Args>
685 JsValue call(Lock& js, const JsValue& recv, Args... args) const {
686 v8::Local<v8::Function> fn = *this;
687 v8::Local<v8::Value> argv[] = {args...};
688 return JsValue(check(fn->Call(js.v8Context(), recv, sizeof...(Args), argv)));
689 }
690 
691 // Calls the function with a null receiver and arguments.
692 template <IsJsValue... Args>
693 JsValue callNoReceiver(Lock& js, Args... args) const {
694 return call(js, js.null(), kj::fwd<Args...>(args...));
695 }
696 
697 // Calls the function with the given receiver and arguments.
698 JsValue call(Lock& js, const JsValue& recv, v8::LocalVector<v8::Value>& args) const;
699 
700 // Calls the function with a null receiver and arguments. When null is passed
701 // as the receiver, the global object is used instead.
702 JsValue callNoReceiver(Lock& js, v8::LocalVector<v8::Value>& args) const;
703 
704 // Gets the function's length property.
705 size_t length(Lock& js) const;
706 
707 // Gets the function's name property.
708 JsString name(Lock& js) const;
709 
710 // Not guaranteed to be unique, but will be the same for the same function.
711 // Use the JsValue strictEquals() method for true identity comparison.
712 uint hashCode() const;
713 
714 operator JsObject() const {
715 return JsObject(inner);
716 }
717};
718 
719// A persistent handle for a Js* type suitable for storage and GC visitable.
720//
721// For example,
722//
723// class Foo : public jsg::Object {
724// public:
725// void setStored(jsg::Lock& js, jsg::JsValue value) {
726// stored = value.addRef(js);
727// }
728// JsValue getStored(jsg::Lock& js) {
729// return stored.getHandle(js);
730// }
731// JSG_RESOURCE_TYPE(Foo) {
732// JSG_PROTOTYPE_PROPERTY(stored, getStored, setStored);
733// }
734// private:
735// jsg::JsRef<JsValue> stored;
736//
737// void visitForGc(GcVisitor& visitor) { visitor.visit(stored); }
738// };
739template <typename T>
740class JsRef final {
741 static_assert(std::is_assignable_v<JsValue, T>, "JsRef<T>, T must be assignable to type JsValue");
742 
743 public:
744 JsRef(): JsRef(nullptr) {}
745 JsRef(decltype(nullptr)): value(nullptr) {}
746 JsRef(Lock& js, const T& value): value(js.v8Isolate, value.inner) {}
747 JsRef(JsRef<T>& other) = delete;
748 JsRef(JsRef<T>&& other) = default;
749 template <typename U>
750 JsRef(Lock& js, V8Ref<U>&& v8Value)
751 : value(js.v8Isolate, v8Value.getHandle(js).template As<v8::Value>()) {}
752 JsRef& operator=(JsRef<T>& other) = delete;
753 JsRef& operator=(JsRef<T>&& other) = default;
754 
755 T getHandle(Lock& js) const KJ_WARN_UNUSED_RESULT {
756 JsValue handle(value.getHandle(js));
757 return KJ_ASSERT_NONNULL(handle.tryCast<T>());
758 }
759 
760 JsRef<T> addRef(Lock& js) KJ_WARN_UNUSED_RESULT {
761 return JsRef<T>(js, getHandle(js));
762 }
763 
764 bool operator==(const JsRef<T>& other) {
765 return value == other.value;
766 }
767 
768 void visitForGc(GcVisitor& visitor) {
769 visitor.visit(value);
770 }
771 
772 // Supported only to allow for an easier transition for code that still
773 // requires V8Ref types.
774 template <typename U>
775 V8Ref<U> addV8Ref(Lock& js) KJ_WARN_UNUSED_RESULT {
776 return value.addRef(js);
777 }
778 
779 // Supported only to allow for an easier transition for code that still
780 // requires V8Ref types.
781 template <typename U>
782 operator V8Ref<U>() && {
783 return kj::mv(value).template cast<U>(jsg::Lock::current());
784 }
785 
786 JSG_MEMORY_INFO(JsRef) {
787 tracker.trackField("value", value);
788 }
789 
790 private:
791 Value value;
792 friend class JsValue;
793#define V(Name) friend class Js##Name;
794 JS_TYPE_CLASSES(V)
795#undef V
796 
797 friend class MemoryTracker;
798};
799 
800template <typename T, typename Self>
801inline JsRef<Self> JsBase<T, Self>::addRef(Lock& js) {
802 return JsRef<Self>(js, *static_cast<Self*>(this));
803}
804 
805inline kj::String KJ_STRINGIFY(const JsValue& value) {
806 return value.toString(jsg::Lock::current());
807}
808 
809template <typename T>
810concept JsValueType = std::is_assignable_v<JsValue, T>;
811 
812struct JsValueWrapper {
813#define TYPES_TO_WRAP(V) \
814 V(Value) \
815 JS_TYPE_CLASSES(V)
816 
817 template <JsValueType T>
818 static constexpr const std::type_info& getName(T*) {
819 return typeid(T);
820 }
821 
822 template <JsValueType T>
823 static constexpr const std::type_info& getName(JsRef<T>*) {
824 return typeid(T);
825 }
826 
827#define V(Name) \
828 v8::Local<v8::Name> wrap(jsg::Lock& js, v8::Local<v8::Context> context, \
829 kj::Maybe<v8::Local<v8::Object>> creator, Js##Name value) { \
830 return value; \
831 } \
832 v8::Local<v8::Name> wrap(jsg::Lock& js, v8::Local<v8::Context> context, \
833 kj::Maybe<v8::Local<v8::Object>> creator, JsRef<Js##Name> value) { \
834 return value.getHandle(js); \
835 }
836 
837 TYPES_TO_WRAP(V)
838#undef V
839 
840 // Manual wrap overloads for JsBufferSource which is not in JS_TYPE_CLASSES.
841 // The underlying V8 type is v8::Value since BufferSource spans both
842 // ArrayBuffer and ArrayBufferView.
843 v8::Local<v8::Value> wrap(jsg::Lock& js,
844 v8::Local<v8::Context> context,
845 kj::Maybe<v8::Local<v8::Object>> creator,
846 JsBufferSource value) {
847 return value;
848 }
849 v8::Local<v8::Value> wrap(jsg::Lock& js,
850 v8::Local<v8::Context> context,
851 kj::Maybe<v8::Local<v8::Object>> creator,
852 JsRef<JsBufferSource> value) {
853 return value.getHandle(js);
854 }
855 
856 template <JsValueType T>
857 kj::Maybe<T> tryUnwrap(Lock& js,
858 v8::Local<v8::Context> context,
859 v8::Local<v8::Value> handle,
860 T*,
861 kj::Maybe<v8::Local<v8::Object>> parentObject) {
862 if constexpr (kj::isSameType<T, JsString>()) {
863 return T(check(handle->ToString(context)));
864 } else if constexpr (kj::isSameType<T, JsBoolean>()) {
865 return T(handle->ToBoolean(js.v8Isolate));
866 } else if constexpr (kj::isSameType<T, JsNumber>()) {
867 return T(check(handle->ToNumber(context)));
868 } else {
869 JsValue value(handle);
870 KJ_IF_SOME(t, value.tryCast<T>()) {
871 return t;
872 }
873 return kj::none;
874 }
875 }
876 
877 template <JsValueType T>
878 kj::Maybe<JsRef<T>> tryUnwrap(Lock& js,
879 v8::Local<v8::Context> context,
880 v8::Local<v8::Value> handle,
881 JsRef<T>*,
882 kj::Maybe<v8::Local<v8::Object>> parentObject) {
883 KJ_IF_SOME(result, tryUnwrap(js, context, handle, (T*)nullptr, parentObject)) {
884 return JsRef(js, result);
885 }
886 return kj::none;
887 }
888};
889 
890class JsMessage final {
891 public:
892 static JsMessage create(Lock& js, const JsValue& exception);
893 explicit inline JsMessage() {
894 requireOnStack(this);
895 }
896 explicit inline JsMessage(v8::Local<v8::Message> inner): inner(inner) {
897 requireOnStack(this);
898 }
899 operator v8::Local<v8::Message>() const {
900 return inner;
901 }
902 
903 // Is it possible for the underlying v8::Local<v8::Message> to be
904 // empty, in which case the bool() operator will return false.
905 operator bool() const {
906 return !inner.IsEmpty();
907 }
908 
909 // Adds the JS Stack associated with this JsMessage to the given
910 // kj::Vector.
911 void addJsStackTrace(Lock& js, kj::Vector<kj::String>& lines);
912 
913 private:
914 v8::Local<v8::Message> inner;
915};
916 
917inline JsObject Lock::global() {
918 return JsObject(v8Context()->Global());
919}
920 
921inline JsValue Lock::undefined() {
922 return JsValue(v8::Undefined(v8Isolate));
923}
924 
925inline JsValue Lock::null() {
926 return JsValue(v8::Null(v8Isolate));
927}
928 
929inline JsBoolean Lock::boolean(bool val) {
930 return JsBoolean(v8::Boolean::New(v8Isolate, val));
931}
932 
933inline JsNumber Lock::num(double val) {
934 return JsNumber(v8::Number::New(v8Isolate, val));
935}
936 
937inline JsNumber Lock::num(float val) {
938 return JsNumber(v8::Number::New(v8Isolate, val));
939}
940 
941inline JsInt32 Lock::num(int8_t val) {
942 return JsInt32(v8::Integer::New(v8Isolate, val).As<v8::Int32>());
943}
944 
945inline JsInt32 Lock::num(int16_t val) {
946 return JsInt32(v8::Integer::New(v8Isolate, val).As<v8::Int32>());
947}
948 
949inline JsInt32 Lock::num(int32_t val) {
950 return JsInt32(v8::Integer::New(v8Isolate, val).As<v8::Int32>());
951}
952 
953inline JsBigInt Lock::bigInt(int64_t val) {
954 return JsBigInt(v8::BigInt::New(v8Isolate, val));
955}
956 
957inline JsUint32 Lock::num(uint8_t val) {
958 return JsUint32(v8::Integer::NewFromUnsigned(v8Isolate, val).As<v8::Uint32>());
959}
960 
961inline JsUint32 Lock::num(uint16_t val) {
962 return JsUint32(v8::Integer::NewFromUnsigned(v8Isolate, val).As<v8::Uint32>());
963}
964 
965inline JsUint32 Lock::num(uint32_t val) {
966 return JsUint32(v8::Integer::NewFromUnsigned(v8Isolate, val).As<v8::Uint32>());
967}
968 
969inline JsBigInt Lock::bigInt(uint64_t val) {
970 return JsBigInt(v8::BigInt::NewFromUnsigned(v8Isolate, val));
971}
972 
973inline JsString Lock::str() {
974 return JsString(v8::String::Empty(v8Isolate));
975}
976 
977inline JsString Lock::str(kj::ArrayPtr<const char16_t> str) {
978 return JsString(check(v8::String::NewFromTwoByte(v8Isolate,
979 reinterpret_cast<const uint16_t*>(str.begin()), v8::NewStringType::kNormal, str.size())));
980}
981 
982inline JsString Lock::str(kj::ArrayPtr<const uint16_t> str) {
983 return JsString(check(
984 v8::String::NewFromTwoByte(v8Isolate, str.begin(), v8::NewStringType::kNormal, str.size())));
985}
986 
987inline JsString Lock::str(kj::ArrayPtr<const char> str) {
988 return JsString(check(
989 v8::String::NewFromUtf8(v8Isolate, str.begin(), v8::NewStringType::kNormal, str.size())));
990}
991 
992inline JsString Lock::str(kj::ArrayPtr<const kj::byte> str) {
993 return JsString(check(
994 v8::String::NewFromOneByte(v8Isolate, str.begin(), v8::NewStringType::kNormal, str.size())));
995}
996 
997inline JsString Lock::strIntern(kj::StringPtr str) {
998 return JsString(check(v8::String::NewFromUtf8(
999 v8Isolate, str.begin(), v8::NewStringType::kInternalized, str.size())));
1000}
1001 
1002inline JsString Lock::strExtern(kj::ArrayPtr<const char> str) {
1003 return JsString(newExternalOneByteString(*this, str));
1004}
1005 
1006inline JsString Lock::strExtern(kj::ArrayPtr<const uint16_t> str) {
1007 return JsString(newExternalTwoByteString(*this, str));
1008}
1009 
1010inline JsObject Lock::obj() {
1011 return JsObject(v8::Object::New(v8Isolate));
1012}
1013 
1014inline JsObject Lock::objNoProto() {
1015 return JsObject(v8::Object::New(v8Isolate, v8::Null(v8Isolate), nullptr, nullptr, 0));
1016}
1017 
1018inline JsMap Lock::map() {
1019 return JsMap(v8::Map::New(v8Isolate));
1020}
1021 
1022inline JsValue Lock::external(void* ptr) {
1023 return JsValue(v8::External::New(v8Isolate, ptr, v8::kExternalPointerTypeTagDefault));
1024}
1025 
1026inline JsValue Lock::error(kj::StringPtr message) {
1027 return JsValue(v8::Exception::Error(v8Str(v8Isolate, message)));
1028}
1029 
1030inline JsValue Lock::typeError(kj::StringPtr message) {
1031 return JsValue(v8::Exception::TypeError(v8Str(v8Isolate, message)));
1032}
1033 
1034inline JsValue Lock::rangeError(kj::StringPtr message) {
1035 return JsValue(v8::Exception::RangeError(v8Str(v8Isolate, message)));
1036}
1037 
1038inline JsSymbol Lock::symbol(kj::StringPtr str) {
1039 return JsSymbol(v8::Symbol::New(v8Isolate, v8StrIntern(v8Isolate, str)));
1040}
1041 
1042inline JsSymbol Lock::symbolShared(kj::StringPtr str) {
1043 return JsSymbol(v8::Symbol::For(v8Isolate, v8StrIntern(v8Isolate, str)));
1044}
1045 
1046inline JsSymbol Lock::symbolInternal(kj::StringPtr str) {
1047 return JsSymbol(v8::Symbol::ForApi(v8Isolate, v8StrIntern(v8Isolate, str)));
1048}
1049 
1050inline JsDate Lock::date(double timestamp) {
1051 return JsDate(check(v8::Date::New(v8Context(), timestamp)).As<v8::Date>());
1052}
1053 
1054inline JsDate Lock::date(kj::Date date) {
1055 return JsDate(jsg::check(v8::Date::New(v8Context(), (date - kj::UNIX_EPOCH) / kj::MILLISECONDS))
1056 .As<v8::Date>());
1057}
1058 
1059inline void JsObject::set(Lock& js, const JsValue& name, const JsValue& value) {
1060 check(inner->Set(js.v8Context(), name.inner, value.inner));
1061}
1062 
1063inline void JsObject::set(Lock& js, kj::StringPtr name, const JsValue& value) {
1064 set(js, js.strIntern(name), value);
1065}
1066 
1067inline JsValue JsObject::get(Lock& js, const JsValue& name) {
1068 return JsValue(check(inner->Get(js.v8Context(), name.inner)));
1069}
1070 
1071inline JsValue JsObject::get(Lock& js, kj::StringPtr name) {
1072 return get(js, js.strIntern(name));
1073}
1074 
1075inline bool JsObject::has(Lock& js, const JsValue& name, HasOption option) {
1076 if (option == HasOption::OWN) {
1077 KJ_ASSERT(name.inner->IsName());
1078 return check(inner->HasOwnProperty(js.v8Context(), name.inner.As<v8::Name>()));
1079 } else {
1080 return check(inner->Has(js.v8Context(), name.inner));
1081 }
1082}
1083 
1084inline bool JsObject::has(Lock& js, kj::StringPtr name, HasOption option) {
1085 return has(js, js.strIntern(name), option);
1086}
1087 
1088inline void JsObject::delete_(Lock& js, const JsValue& name) {
1089 check(inner->Delete(js.v8Context(), name.inner));
1090}
1091 
1092inline void JsObject::delete_(Lock& js, kj::StringPtr name) {
1093 delete_(js, js.strIntern(name));
1094}
1095 
1096inline int JsString::length(jsg::Lock& js) const {
1097 return inner->Length();
1098}
1099 
1100inline bool JsString::isOneByte(jsg::Lock& js) const {
1101 return inner->IsOneByte();
1102}
1103 
1104inline size_t JsString::utf8Length(jsg::Lock& js) const {
1105 return inner->Utf8LengthV2(js.v8Isolate);
1106}
1107 
1108} // namespace workerd::jsg