From 5bc1f4d42dcaaa823d0ec0504acfa2e1644fa40e Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Mon, 10 Jul 2023 13:21:31 -0700 Subject: [PATCH 01/14] copy `SerializedScriptValue` --- .../webcore/SerializedScriptValue.cpp | 5013 +++++++++++++++++ .../bindings/webcore/SerializedScriptValue.h | 281 + 2 files changed, 5294 insertions(+) create mode 100644 src/bun.js/bindings/webcore/SerializedScriptValue.cpp create mode 100644 src/bun.js/bindings/webcore/SerializedScriptValue.h diff --git a/src/bun.js/bindings/webcore/SerializedScriptValue.cpp b/src/bun.js/bindings/webcore/SerializedScriptValue.cpp new file mode 100644 index 000000000000..beadb3d0c339 --- /dev/null +++ b/src/bun.js/bindings/webcore/SerializedScriptValue.cpp @@ -0,0 +1,5013 @@ +/* + * Copyright (C) 2009-2023 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "config.h" +#include "SerializedScriptValue.h" + +#include "BlobRegistry.h" +#include "ByteArrayPixelBuffer.h" +#include "CryptoKeyAES.h" +#include "CryptoKeyEC.h" +#include "CryptoKeyHMAC.h" +#include "CryptoKeyOKP.h" +#include "CryptoKeyRSA.h" +#include "CryptoKeyRSAComponents.h" +#include "CryptoKeyRaw.h" +#include "IDBValue.h" +#include "ImageBitmapBacking.h" +#include "JSBlob.h" +#include "JSCryptoKey.h" +#include "JSDOMBinding.h" +#include "JSDOMConvertBufferSource.h" +#include "JSDOMException.h" +#include "JSDOMGlobalObject.h" +#include "JSDOMMatrix.h" +#include "JSDOMPoint.h" +#include "JSDOMQuad.h" +#include "JSDOMRect.h" +#include "JSExecState.h" +#include "JSFile.h" +#include "JSFileList.h" +#include "JSIDBSerializationGlobalObject.h" +#include "JSImageBitmap.h" +#include "JSImageData.h" +#include "JSMessagePort.h" +#include "JSNavigator.h" +#include "JSRTCCertificate.h" +#include "JSRTCDataChannel.h" +#include "JSWebCodecsEncodedVideoChunk.h" +#include "JSWebCodecsVideoFrame.h" +#include "ScriptExecutionContext.h" +#include "SharedBuffer.h" +#include "WebCodecsEncodedVideoChunk.h" +#include "WebCoreJSClientData.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if USE(CG) +#include +#endif + +#if PLATFORM(COCOA) +#include +#endif + +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) +#include "JSOffscreenCanvas.h" +#include "OffscreenCanvas.h" +#endif + +#if CPU(BIG_ENDIAN) || CPU(MIDDLE_ENDIAN) || CPU(NEEDS_ALIGNED_ACCESS) +#define ASSUME_LITTLE_ENDIAN 0 +#else +#define ASSUME_LITTLE_ENDIAN 1 +#endif + +namespace WebCore { + +using namespace JSC; + +DEFINE_ALLOCATOR_WITH_HEAP_IDENTIFIER(SerializedScriptValue); + +static constexpr unsigned maximumFilterRecursion = 40000; +static constexpr uint64_t autoLengthMarker = UINT64_MAX; + +enum class SerializationReturnCode { + SuccessfullyCompleted, + StackOverflowError, + InterruptedExecutionError, + ValidationError, + ExistingExceptionError, + DataCloneError, + UnspecifiedError +}; + +enum WalkerState { StateUnknown, ArrayStartState, ArrayStartVisitMember, ArrayEndVisitMember, + ObjectStartState, ObjectStartVisitMember, ObjectEndVisitMember, + MapDataStartVisitEntry, MapDataEndVisitKey, MapDataEndVisitValue, + SetDataStartVisitEntry, SetDataEndVisitKey }; + +// These can't be reordered, and any new types must be added to the end of the list +// When making changes to these lists please cover your new type(s) in the API test "IndexedDB.StructuredCloneBackwardCompatibility" +enum SerializationTag { + ArrayTag = 1, + ObjectTag = 2, + UndefinedTag = 3, + NullTag = 4, + IntTag = 5, + ZeroTag = 6, + OneTag = 7, + FalseTag = 8, + TrueTag = 9, + DoubleTag = 10, + DateTag = 11, + FileTag = 12, + FileListTag = 13, + ImageDataTag = 14, + BlobTag = 15, + StringTag = 16, + EmptyStringTag = 17, + RegExpTag = 18, + ObjectReferenceTag = 19, + MessagePortReferenceTag = 20, + ArrayBufferTag = 21, + ArrayBufferViewTag = 22, + ArrayBufferTransferTag = 23, + TrueObjectTag = 24, + FalseObjectTag = 25, + StringObjectTag = 26, + EmptyStringObjectTag = 27, + NumberObjectTag = 28, + SetObjectTag = 29, + MapObjectTag = 30, + NonMapPropertiesTag = 31, + NonSetPropertiesTag = 32, +#if ENABLE(WEB_CRYPTO) + CryptoKeyTag = 33, +#endif + SharedArrayBufferTag = 34, +#if ENABLE(WEBASSEMBLY) + WasmModuleTag = 35, +#endif + DOMPointReadOnlyTag = 36, + DOMPointTag = 37, + DOMRectReadOnlyTag = 38, + DOMRectTag = 39, + DOMMatrixReadOnlyTag = 40, + DOMMatrixTag = 41, + DOMQuadTag = 42, + ImageBitmapTransferTag = 43, +#if ENABLE(WEB_RTC) + RTCCertificateTag = 44, +#endif + ImageBitmapTag = 45, +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + OffscreenCanvasTransferTag = 46, +#endif + BigIntTag = 47, + BigIntObjectTag = 48, +#if ENABLE(WEBASSEMBLY) + WasmMemoryTag = 49, +#endif +#if ENABLE(WEB_RTC) + RTCDataChannelTransferTag = 50, +#endif + DOMExceptionTag = 51, +#if ENABLE(WEB_CODECS) + WebCodecsEncodedVideoChunkTag = 52, + WebCodecsVideoFrameTag = 53, +#endif + ResizableArrayBufferTag = 54, + ErrorTag = 255 +}; + +enum ArrayBufferViewSubtag { + DataViewTag = 0, + Int8ArrayTag = 1, + Uint8ArrayTag = 2, + Uint8ClampedArrayTag = 3, + Int16ArrayTag = 4, + Uint16ArrayTag = 5, + Int32ArrayTag = 6, + Uint32ArrayTag = 7, + Float32ArrayTag = 8, + Float64ArrayTag = 9, + BigInt64ArrayTag = 10, + BigUint64ArrayTag = 11, +}; + +static unsigned typedArrayElementSize(ArrayBufferViewSubtag tag) +{ + switch (tag) { + case DataViewTag: + case Int8ArrayTag: + case Uint8ArrayTag: + case Uint8ClampedArrayTag: + return 1; + case Int16ArrayTag: + case Uint16ArrayTag: + return 2; + case Int32ArrayTag: + case Uint32ArrayTag: + case Float32ArrayTag: + return 4; + case Float64ArrayTag: + case BigInt64ArrayTag: + case BigUint64ArrayTag: + return 8; + default: + return 0; + } +} + +enum class PredefinedColorSpaceTag : uint8_t { + SRGB = 0 +#if ENABLE(PREDEFINED_COLOR_SPACE_DISPLAY_P3) + , DisplayP3 = 1 +#endif +}; + +enum DestinationColorSpaceTag { + DestinationColorSpaceSRGBTag = 0, +#if ENABLE(DESTINATION_COLOR_SPACE_LINEAR_SRGB) + DestinationColorSpaceLinearSRGBTag = 1, +#endif +#if ENABLE(DESTINATION_COLOR_SPACE_DISPLAY_P3) + DestinationColorSpaceDisplayP3Tag = 2, +#endif +#if PLATFORM(COCOA) + DestinationColorSpaceCGColorSpaceNameTag = 3, + DestinationColorSpaceCGColorSpacePropertyListTag = 4, +#endif +}; + +#if ENABLE(WEBASSEMBLY) +static String agentClusterIDFromGlobalObject(JSGlobalObject& globalObject) +{ + if (!globalObject.inherits()) + return JSDOMGlobalObject::defaultAgentClusterID(); + return jsCast(&globalObject)->agentClusterID(); +} +#endif + +#if ENABLE(WEB_CRYPTO) + +const uint32_t currentKeyFormatVersion = 1; + +enum class CryptoKeyClassSubtag { + HMAC = 0, + AES = 1, + RSA = 2, + EC = 3, + Raw = 4, + OKP = 5, +}; +const uint8_t cryptoKeyClassSubtagMaximumValue = 5; + +enum class CryptoKeyAsymmetricTypeSubtag { + Public = 0, + Private = 1 +}; +const uint8_t cryptoKeyAsymmetricTypeSubtagMaximumValue = 1; + +enum class CryptoKeyUsageTag { + Encrypt = 0, + Decrypt = 1, + Sign = 2, + Verify = 3, + DeriveKey = 4, + DeriveBits = 5, + WrapKey = 6, + UnwrapKey = 7 +}; +const uint8_t cryptoKeyUsageTagMaximumValue = 7; + +enum class CryptoAlgorithmIdentifierTag { + RSAES_PKCS1_v1_5 = 0, + RSASSA_PKCS1_v1_5 = 1, + RSA_PSS = 2, + RSA_OAEP = 3, + ECDSA = 4, + ECDH = 5, + AES_CTR = 6, + AES_CBC = 7, + AES_GCM = 9, + AES_CFB = 10, + AES_KW = 11, + HMAC = 12, + SHA_1 = 14, + SHA_224 = 15, + SHA_256 = 16, + SHA_384 = 17, + SHA_512 = 18, + HKDF = 20, + PBKDF2 = 21, + ED25519 = 22, +}; + +const uint8_t cryptoAlgorithmIdentifierTagMaximumValue = 22; + +static unsigned countUsages(CryptoKeyUsageBitmap usages) +{ + // Fast bit count algorithm for sparse bit maps. + unsigned count = 0; + while (usages) { + usages = usages & (usages - 1); + ++count; + } + return count; +} + +enum class CryptoKeyOKPOpNameTag { + X25519 = 0, + ED25519 = 1, +}; +const uint8_t cryptoKeyOKPOpNameTagMaximumValue = 1; + + +#endif + +/* CurrentVersion tracks the serialization version so that persistent stores + * are able to correctly bail out in the case of encountering newer formats. + * + * Initial version was 1. + * Version 2. added the ObjectReferenceTag and support for serialization of cyclic graphs. + * Version 3. added the FalseObjectTag, TrueObjectTag, NumberObjectTag, StringObjectTag + * and EmptyStringObjectTag for serialization of Boolean, Number and String objects. + * Version 4. added support for serializing non-index properties of arrays. + * Version 5. added support for Map and Set types. + * Version 6. added support for 8-bit strings. + * Version 7. added support for File's lastModified attribute. + * Version 8. added support for ImageData's colorSpace attribute. + * Version 9. added support for ImageBitmap color space. + * Version 10. changed the length (and offsets) of ArrayBuffers (and ArrayBufferViews) from 32 to 64 bits. + * Version 11. added support for Blob's memory cost. + * Version 12. added support for agent cluster ID. + */ +static const unsigned CurrentVersion = 12; +static const unsigned TerminatorTag = 0xFFFFFFFF; +static const unsigned StringPoolTag = 0xFFFFFFFE; +static const unsigned NonIndexPropertiesTag = 0xFFFFFFFD; + +// The high bit of a StringData's length determines the character size. +static const unsigned StringDataIs8BitFlag = 0x80000000; + +/* + * Object serialization is performed according to the following grammar, all tags + * are recorded as a single uint8_t. + * + * IndexType (used for the object pool and StringData's constant pool) is the + * minimum sized unsigned integer type required to represent the maximum index + * in the constant pool. + * + * SerializedValue :- Value + * Value :- Array | Object | Map | Set | Terminal + * + * Array :- + * ArrayTag ()* TerminatorTag + * + * Object :- + * ObjectTag ()* TerminatorTag + * + * Map :- MapObjectTag MapData + * + * Set :- SetObjectTag SetData + * + * MapData :- ()* NonMapPropertiesTag ()* TerminatorTag + * SetData :- ()* NonSetPropertiesTag ()* TerminatorTag + * + * Terminal :- + * UndefinedTag + * | NullTag + * | IntTag + * | ZeroTag + * | OneTag + * | FalseTag + * | TrueTag + * | FalseObjectTag + * | TrueObjectTag + * | DoubleTag + * | NumberObjectTag + * | DateTag + * | String + * | EmptyStringTag + * | EmptyStringObjectTag + * | BigInt + * | File + * | FileList + * | ImageData + * | Blob + * | ObjectReference + * | MessagePortReferenceTag + * | ArrayBuffer + * | ArrayBufferViewTag ArrayBufferViewSubtag (ArrayBuffer | ObjectReference) + * | CryptoKeyTag + * | DOMPoint + * | DOMRect + * | DOMMatrix + * | DOMQuad + * | ImageBitmapTransferTag + * | RTCCertificateTag + * | ImageBitmapTag DestinationColorSpace () + * | OffscreenCanvasTransferTag + * | WasmMemoryTag + * | RTCDataChannelTransferTag + * | DOMExceptionTag + * | WebCodecsEncodedVideoChunkTag + * + * Inside certificate, data is serialized in this format as per spec: + * + * + * We also add fingerprints to make sure we expose to JavaScript the same information. + * + * Inside wrapped crypto key, data is serialized in this format: + * + * CryptoKeyClassSubtag (CryptoKeyHMAC | CryptoKeyAES | CryptoKeyRSA) + * + * String :- + * EmptyStringTag + * StringTag StringData + * + * StringObject: + * EmptyStringObjectTag + * StringObjectTag StringData + * + * StringData :- + * StringPoolTag + * (not (TerminatorTag | StringPoolTag)) // Added to constant pool when seen, string length 0xFFFFFFFF is disallowed + * + * BigInt :- + * BigIntTag BigIntData + * BigIntObjectTag BigIntData + * + * BigIntData :- + * + * + * File :- + * FileTag FileData + * + * FileData :- + * + * + * FileList :- + * FileListTag (){length} + * + * ImageData :- + * ImageDataTag + * + * Blob :- + * BlobTag + * + * RegExp :- + * RegExpTag + * + * ObjectReference :- + * ObjectReferenceTag + * + * ArrayBuffer :- + * ArrayBufferTag + * ResizableArrayBufferTag + * ArrayBufferTransferTag + * SharedArrayBufferTag + * + * CryptoKeyHMAC :- + * CryptoAlgorithmIdentifierTag // Algorithm tag inner hash function. + * + * CryptoKeyAES :- + * CryptoAlgorithmIdentifierTag + * + * CryptoKeyRSA :- + * CryptoAlgorithmIdentifierTag CryptoAlgorithmIdentifierTag? CryptoKeyAsymmetricTypeSubtag CryptoKeyRSAPublicComponents CryptoKeyRSAPrivateComponents? + * + * CryptoKeyRSAPublicComponents :- + * + * + * CryptoKeyRSAPrivateComponents :- + * FirstPrimeInfo? PrimeInfo{primeCount - 1} + * + * // CRT data could be computed from prime factors. It is only serialized to reuse a code path that's needed for JWK. + * FirstPrimeInfo :- + * + * + * PrimeInfo :- + * + * + * CryptoKeyEC :- + * CryptoAlgorithmIdentifierTag CryptoKeyAsymmetricTypeSubtag + * + * CryptoKeyRaw :- + * CryptoAlgorithmIdentifierTag + * + * DOMPoint :- + * DOMPointReadOnlyTag DOMPointData + * | DOMPointTag DOMPointData + * + * DOMPointData :- + * + * + * DOMRect :- + * DOMRectReadOnlyTag DOMRectData + * | DOMRectTag DOMRectData + * + * DOMRectData :- + * + * + * DOMMatrix :- + * DOMMatrixReadOnlyTag DOMMatrixData + * | DOMMatrixTag DOMMatrixData + * + * DOMMatrixData :- + * + * | + * + * DOMQuad :- + * DOMQuadTag DOMQuadData + * + * DOMQuadData :- + * + * + * DestinationColorSpace :- + * DestinationColorSpaceSRGBTag + * | DestinationColorSpaceLinearSRGBTag + * | DestinationColorSpaceDisplayP3Tag + * | DestinationColorSpaceCGColorSpaceNameTag {nameDataLength} + * | DestinationColorSpaceCGColorSpacePropertyListTag {propertyListDataLength} + */ + +using DeserializationResult = std::pair; + +class CloneBase { + WTF_FORBID_HEAP_ALLOCATION; +protected: + CloneBase(JSGlobalObject* lexicalGlobalObject) + : m_lexicalGlobalObject(lexicalGlobalObject) + , m_failed(false) + { + } + + void fail() + { + m_failed = true; + } + + JSGlobalObject* const m_lexicalGlobalObject; + bool m_failed; + MarkedArgumentBuffer m_gcBuffer; +}; + +#if ENABLE(WEB_CRYPTO) +static bool wrapCryptoKey(JSGlobalObject* lexicalGlobalObject, const Vector& key, Vector& wrappedKey) +{ + auto context = executionContext(lexicalGlobalObject); + return context && context->wrapCryptoKey(key, wrappedKey); +} + +static bool unwrapCryptoKey(JSGlobalObject* lexicalGlobalObject, const Vector& wrappedKey, Vector& key) +{ + auto context = executionContext(lexicalGlobalObject); + return context && context->unwrapCryptoKey(wrappedKey, key); +} +#endif + +#if ASSUME_LITTLE_ENDIAN +template static void writeLittleEndian(Vector& buffer, T value) +{ + buffer.append(reinterpret_cast(&value), sizeof(value)); +} +#else +template static void writeLittleEndian(Vector& buffer, T value) +{ + for (unsigned i = 0; i < sizeof(T); i++) { + buffer.append(value & 0xFF); + value >>= 8; + } +} +#endif + +template <> void writeLittleEndian(Vector& buffer, uint8_t value) +{ + buffer.append(value); +} + +template static bool writeLittleEndian(Vector& buffer, const T* values, uint32_t length) +{ + if (length > std::numeric_limits::max() / sizeof(T)) + return false; + +#if ASSUME_LITTLE_ENDIAN + buffer.append(reinterpret_cast(values), length * sizeof(T)); +#else + for (unsigned i = 0; i < length; i++) { + T value = values[i]; + for (unsigned j = 0; j < sizeof(T); j++) { + buffer.append(static_cast(value & 0xFF)); + value >>= 8; + } + } +#endif + return true; +} + +template <> bool writeLittleEndian(Vector& buffer, const uint8_t* values, uint32_t length) +{ + buffer.append(values, length); + return true; +} + +class CloneSerializer : CloneBase { + WTF_FORBID_HEAP_ALLOCATION; +public: + static SerializationReturnCode serialize(JSGlobalObject* lexicalGlobalObject, JSValue value, Vector>& messagePorts, Vector>& arrayBuffers, const Vector>& imageBitmaps, +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + const Vector>& offscreenCanvases, +#endif +#if ENABLE(WEB_RTC) + const Vector>& rtcDataChannels, +#endif +#if ENABLE(WEB_CODECS) + Vector>& serializedVideoChunks, + Vector>& serializedVideoFrames, +#endif +#if ENABLE(WEBASSEMBLY) + WasmModuleArray& wasmModules, + WasmMemoryHandleArray& wasmMemoryHandles, +#endif + Vector& blobHandles, Vector& out, SerializationContext context, ArrayBufferContentsArray& sharedBuffers, + SerializationForStorage forStorage) + { + CloneSerializer serializer(lexicalGlobalObject, messagePorts, arrayBuffers, imageBitmaps, +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + offscreenCanvases, +#endif +#if ENABLE(WEB_RTC) + rtcDataChannels, +#endif +#if ENABLE(WEB_CODECS) + serializedVideoChunks, + serializedVideoFrames, +#endif +#if ENABLE(WEBASSEMBLY) + wasmModules, + wasmMemoryHandles, +#endif + blobHandles, out, context, sharedBuffers, forStorage); + return serializer.serialize(value); + } + + static bool serialize(StringView string, Vector& out) + { + writeLittleEndian(out, CurrentVersion); + if (string.isEmpty()) { + writeLittleEndian(out, EmptyStringTag); + return true; + } + writeLittleEndian(out, StringTag); + if (string.is8Bit()) { + writeLittleEndian(out, string.length() | StringDataIs8BitFlag); + return writeLittleEndian(out, string.characters8(), string.length()); + } + writeLittleEndian(out, string.length()); + return writeLittleEndian(out, string.characters16(), string.length()); + } + +private: + typedef HashMap ObjectPool; + + CloneSerializer(JSGlobalObject* lexicalGlobalObject, Vector>& messagePorts, Vector>& arrayBuffers, const Vector>& imageBitmaps, +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + const Vector>& offscreenCanvases, +#endif +#if ENABLE(WEB_RTC) + const Vector>& rtcDataChannels, +#endif +#if ENABLE(WEB_CODECS) + Vector>& serializedVideoChunks, + Vector>& serializedVideoFrames, +#endif +#if ENABLE(WEBASSEMBLY) + WasmModuleArray& wasmModules, + WasmMemoryHandleArray& wasmMemoryHandles, +#endif + Vector& blobHandles, Vector& out, SerializationContext context, ArrayBufferContentsArray& sharedBuffers, SerializationForStorage forStorage) + : CloneBase(lexicalGlobalObject) + , m_buffer(out) + , m_blobHandles(blobHandles) + , m_emptyIdentifier(Identifier::fromString(lexicalGlobalObject->vm(), emptyString())) + , m_context(context) + , m_sharedBuffers(sharedBuffers) +#if ENABLE(WEBASSEMBLY) + , m_wasmModules(wasmModules) + , m_wasmMemoryHandles(wasmMemoryHandles) +#endif +#if ENABLE(WEB_CODECS) + , m_serializedVideoChunks(serializedVideoChunks) + , m_serializedVideoFrames(serializedVideoFrames) +#endif + , m_forStorage(forStorage) + { + write(CurrentVersion); + fillTransferMap(messagePorts, m_transferredMessagePorts); + fillTransferMap(arrayBuffers, m_transferredArrayBuffers); + fillTransferMap(imageBitmaps, m_transferredImageBitmaps); +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + fillTransferMap(offscreenCanvases, m_transferredOffscreenCanvases); +#endif +#if ENABLE(WEB_RTC) + fillTransferMap(rtcDataChannels, m_transferredRTCDataChannels); +#endif + } + + template + void fillTransferMap(const Vector>& input, ObjectPool& result) + { + if (input.isEmpty()) + return; + JSDOMGlobalObject* globalObject = jsCast(m_lexicalGlobalObject); + for (size_t i = 0; i < input.size(); i++) { + JSC::JSValue value = toJS(m_lexicalGlobalObject, globalObject, input[i].get()); + JSC::JSObject* obj = value.getObject(); + if (obj && !result.contains(obj)) + result.add(obj, i); + } + } + template + void fillTransferMap(const Vector>& input, ObjectPool& result) + { + if (input.isEmpty()) + return; + JSDOMGlobalObject* globalObject = jsCast(m_lexicalGlobalObject); + for (size_t i = 0; i < input.size(); i++) { + JSC::JSValue value = toJS(m_lexicalGlobalObject, globalObject, input[i].get()); + JSC::JSObject* obj = value.getObject(); + if (obj && !result.contains(obj)) + result.add(obj, i); + } + } + + SerializationReturnCode serialize(JSValue in); + + bool isArray(JSValue value) + { + if (!value.isObject()) + return false; + JSObject* object = asObject(value); + return object->inherits(); + } + + bool isMap(JSValue value) + { + if (!value.isObject()) + return false; + JSObject* object = asObject(value); + return object->inherits(); + } + bool isSet(JSValue value) + { + if (!value.isObject()) + return false; + JSObject* object = asObject(value); + return object->inherits(); + } + + bool checkForDuplicate(JSObject* object) + { + // Record object for graph reconstruction + ObjectPool::const_iterator found = m_objectPool.find(object); + + // Handle duplicate references + if (found != m_objectPool.end()) { + write(ObjectReferenceTag); + ASSERT(found->value < m_objectPool.size()); + writeObjectIndex(found->value); + return true; + } + + return false; + } + + void recordObject(JSObject* object) + { + m_objectPool.add(object, m_objectPool.size()); + m_gcBuffer.appendWithCrashOnOverflow(object); + } + + bool startObjectInternal(JSObject* object) + { + if (checkForDuplicate(object)) + return false; + recordObject(object); + return true; + } + + bool startObject(JSObject* object) + { + if (!startObjectInternal(object)) + return false; + write(ObjectTag); + return true; + } + + bool startArray(JSArray* array) + { + if (!startObjectInternal(array)) + return false; + + unsigned length = array->length(); + write(ArrayTag); + write(length); + return true; + } + + bool startSet(JSSet* set) + { + if (!startObjectInternal(set)) + return false; + + write(SetObjectTag); + return true; + } + + bool startMap(JSMap* map) + { + if (!startObjectInternal(map)) + return false; + + write(MapObjectTag); + return true; + } + + void endObject() + { + write(TerminatorTag); + } + + JSValue getProperty(JSObject* object, const Identifier& propertyName) + { + PropertySlot slot(object, PropertySlot::InternalMethodType::Get); + if (object->methodTable()->getOwnPropertySlot(object, m_lexicalGlobalObject, propertyName, slot)) + return slot.getValue(m_lexicalGlobalObject, propertyName); + return JSValue(); + } + + void dumpImmediate(JSValue value, SerializationReturnCode& code) + { + if (value.isNull()) { + write(NullTag); + return; + } + if (value.isUndefined()) { + write(UndefinedTag); + return; + } + if (value.isNumber()) { + if (value.isInt32()) { + if (!value.asInt32()) + write(ZeroTag); + else if (value.asInt32() == 1) + write(OneTag); + else { + write(IntTag); + write(static_cast(value.asInt32())); + } + } else { + write(DoubleTag); + write(value.asDouble()); + } + return; + } + if (value.isBoolean()) { + if (value.isTrue()) + write(TrueTag); + else + write(FalseTag); + return; + } +#if USE(BIGINT32) + if (value.isBigInt32()) { + write(BigIntTag); + dumpBigIntData(value); + return; + } +#endif + + // Make any new primitive extension safe by throwing an error. + code = SerializationReturnCode::DataCloneError; + } + + void dumpString(const String& string) + { + if (string.isEmpty()) + write(EmptyStringTag); + else { + write(StringTag); + write(string); + } + } + + void dumpStringObject(const String& string) + { + if (string.isEmpty()) + write(EmptyStringObjectTag); + else { + write(StringObjectTag); + write(string); + } + } + + void dumpBigIntData(JSValue value) + { + ASSERT(value.isBigInt()); +#if USE(BIGINT32) + if (value.isBigInt32()) { + dumpBigInt32Data(value.bigInt32AsInt32()); + return; + } +#endif + dumpHeapBigIntData(jsCast(value)); + } + +#if USE(BIGINT32) + void dumpBigInt32Data(int32_t integer) + { + write(static_cast(integer < 0)); + if (!integer) { + write(static_cast(0)); // Length-in-uint64_t + return; + } + write(static_cast(1)); // Length-in-uint64_t + int64_t value = static_cast(integer); + if (value < 0) + value = -value; + write(static_cast(value)); + } +#endif + + void dumpHeapBigIntData(JSBigInt* bigInt) + { + write(static_cast(bigInt->sign())); + if constexpr (sizeof(JSBigInt::Digit) == sizeof(uint64_t)) { + write(static_cast(bigInt->length())); + for (unsigned index = 0; index < bigInt->length(); ++index) + write(static_cast(bigInt->digit(index))); + } else { + ASSERT(sizeof(JSBigInt::Digit) == sizeof(uint32_t)); + uint32_t lengthInUint64 = bigInt->length() / 2; + if (bigInt->length() & 0x1) + ++lengthInUint64; + write(lengthInUint64); + uint64_t value = 0; + for (unsigned index = 0; index < bigInt->length(); ++index) { + if (!(index & 0x1)) + value = bigInt->digit(index); + else { + value = (static_cast(bigInt->digit(index)) << 32) | value; + write(static_cast(value)); + value = 0; + } + } + if (bigInt->length() & 0x1) + write(static_cast(value)); + } + } + + JSC::JSValue toJSArrayBuffer(ArrayBuffer& arrayBuffer) + { + auto& vm = m_lexicalGlobalObject->vm(); + auto* globalObject = m_lexicalGlobalObject; + if (globalObject->inherits()) + return toJS(globalObject, jsCast(globalObject), &arrayBuffer); + + if (auto* buffer = arrayBuffer.m_wrapper.get()) + return buffer; + + return JSC::JSArrayBuffer::create(vm, globalObject->arrayBufferStructure(arrayBuffer.sharingMode()), &arrayBuffer); + } + + bool dumpArrayBufferView(JSObject* obj, SerializationReturnCode& code) + { + VM& vm = m_lexicalGlobalObject->vm(); + write(ArrayBufferViewTag); + if (obj->inherits()) + write(DataViewTag); + else if (obj->inherits()) + write(Uint8ClampedArrayTag); + else if (obj->inherits()) + write(Int8ArrayTag); + else if (obj->inherits()) + write(Uint8ArrayTag); + else if (obj->inherits()) + write(Int16ArrayTag); + else if (obj->inherits()) + write(Uint16ArrayTag); + else if (obj->inherits()) + write(Int32ArrayTag); + else if (obj->inherits()) + write(Uint32ArrayTag); + else if (obj->inherits()) + write(Float32ArrayTag); + else if (obj->inherits()) + write(Float64ArrayTag); + else if (obj->inherits()) + write(BigInt64ArrayTag); + else if (obj->inherits()) + write(BigUint64ArrayTag); + else + return false; + + if (UNLIKELY(jsCast(obj)->isOutOfBounds())) { + code = SerializationReturnCode::DataCloneError; + return true; + } + + RefPtr arrayBufferView = toPossiblySharedArrayBufferView(vm, obj); + if (arrayBufferView->isResizableOrGrowableShared()) { + uint64_t byteOffset = arrayBufferView->byteOffsetRaw(); + write(byteOffset); + uint64_t byteLength = arrayBufferView->byteLengthRaw(); + if (arrayBufferView->isAutoLength()) + byteLength = autoLengthMarker; + write(byteLength); + } else { + uint64_t byteOffset = arrayBufferView->byteOffset(); + write(byteOffset); + uint64_t byteLength = arrayBufferView->byteLength(); + write(byteLength); + } + RefPtr arrayBuffer = arrayBufferView->possiblySharedBuffer(); + if (!arrayBuffer) { + code = SerializationReturnCode::ValidationError; + return true; + } + + return dumpIfTerminal(toJSArrayBuffer(*arrayBuffer), code); + } + + void dumpDOMPoint(const DOMPointReadOnly& point) + { + write(point.x()); + write(point.y()); + write(point.z()); + write(point.w()); + } + + void dumpDOMPoint(JSObject* obj) + { + if (obj->inherits()) + write(DOMPointTag); + else + write(DOMPointReadOnlyTag); + + dumpDOMPoint(jsCast(obj)->wrapped()); + } + + void dumpDOMRect(JSObject* obj) + { + if (obj->inherits()) + write(DOMRectTag); + else + write(DOMRectReadOnlyTag); + + auto& rect = jsCast(obj)->wrapped(); + write(rect.x()); + write(rect.y()); + write(rect.width()); + write(rect.height()); + } + + void dumpDOMMatrix(JSObject* obj) + { + if (obj->inherits()) + write(DOMMatrixTag); + else + write(DOMMatrixReadOnlyTag); + + auto& matrix = jsCast(obj)->wrapped(); + bool is2D = matrix.is2D(); + write(static_cast(is2D)); + if (is2D) { + write(matrix.m11()); + write(matrix.m12()); + write(matrix.m21()); + write(matrix.m22()); + write(matrix.m41()); + write(matrix.m42()); + } else { + write(matrix.m11()); + write(matrix.m12()); + write(matrix.m13()); + write(matrix.m14()); + write(matrix.m21()); + write(matrix.m22()); + write(matrix.m23()); + write(matrix.m24()); + write(matrix.m31()); + write(matrix.m32()); + write(matrix.m33()); + write(matrix.m34()); + write(matrix.m41()); + write(matrix.m42()); + write(matrix.m43()); + write(matrix.m44()); + } + } + + void dumpDOMQuad(JSObject* obj) + { + write(DOMQuadTag); + + auto& quad = jsCast(obj)->wrapped(); + dumpDOMPoint(quad.p1()); + dumpDOMPoint(quad.p2()); + dumpDOMPoint(quad.p3()); + dumpDOMPoint(quad.p4()); + } + + void dumpImageBitmap(JSObject* obj, SerializationReturnCode& code) + { + auto index = m_transferredImageBitmaps.find(obj); + if (index != m_transferredImageBitmaps.end()) { + write(ImageBitmapTransferTag); + write(index->value); + return; + } + + auto& imageBitmap = jsCast(obj)->wrapped(); + if (!imageBitmap.originClean()) { + code = SerializationReturnCode::DataCloneError; + return; + } + + auto* buffer = imageBitmap.buffer(); + if (!buffer) { + code = SerializationReturnCode::ValidationError; + return; + } + + // FIXME: We should try to avoid converting pixel format. + PixelBufferFormat format { AlphaPremultiplication::Premultiplied, PixelFormat::RGBA8, buffer->colorSpace() }; + const IntSize& logicalSize = buffer->truncatedLogicalSize(); + auto pixelBuffer = buffer->getPixelBuffer(format, { IntPoint::zero(), logicalSize }); + if (!is(pixelBuffer)) { + code = SerializationReturnCode::ValidationError; + return; + } + + auto arrayBuffer = downcast(*pixelBuffer).data().possiblySharedBuffer(); + if (!arrayBuffer) { + code = SerializationReturnCode::ValidationError; + return; + } + + write(ImageBitmapTag); + write(static_cast(imageBitmap.serializationState().toRaw())); + write(static_cast(logicalSize.width())); + write(static_cast(logicalSize.height())); + write(static_cast(buffer->resolutionScale())); + write(buffer->colorSpace()); + + CheckedUint32 byteLength = arrayBuffer->byteLength(); + if (byteLength.hasOverflowed()) { + code = SerializationReturnCode::ValidationError; + return; + } + write(byteLength); + write(static_cast(arrayBuffer->data()), byteLength); + } + +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + void dumpOffscreenCanvas(JSObject* obj, SerializationReturnCode& code) + { + auto index = m_transferredOffscreenCanvases.find(obj); + if (index != m_transferredOffscreenCanvases.end()) { + write(OffscreenCanvasTransferTag); + write(index->value); + return; + } + + code = SerializationReturnCode::DataCloneError; + } +#endif + +#if ENABLE(WEB_RTC) + void dumpRTCDataChannel(JSObject* obj, SerializationReturnCode& code) + { + auto index = m_transferredRTCDataChannels.find(obj); + if (index != m_transferredRTCDataChannels.end()) { + write(RTCDataChannelTransferTag); + write(index->value); + return; + } + + code = SerializationReturnCode::DataCloneError; + } +#endif +#if ENABLE(WEB_CODECS) + void dumpWebCodecsEncodedVideoChunk(JSObject* obj) + { + auto& videoChunk = jsCast(obj)->wrapped(); + + auto index = m_serializedVideoChunks.find(&videoChunk.storage()); + if (index == notFound) { + index = m_serializedVideoChunks.size(); + m_serializedVideoChunks.append(&videoChunk.storage()); + } + + write(WebCodecsEncodedVideoChunkTag); + write(static_cast(index)); + } + + bool dumpWebCodecsVideoFrame(JSObject* obj) + { + Ref videoFrame = jsCast(obj)->wrapped(); + if (videoFrame->isDetached()) + return false; + + auto index = m_serializedVideoFrames.find(videoFrame.ptr()); + if (index == notFound) { + index = m_serializedVideoChunks.size(); + m_serializedVideoFrames.append(WTFMove(videoFrame)); + } + write(WebCodecsVideoFrameTag); + write(static_cast(index)); + return true; + } +#endif + + void dumpDOMException(JSObject* obj, SerializationReturnCode& code) + { + if (auto* exception = JSDOMException::toWrapped(m_lexicalGlobalObject->vm(), obj)) { + write(DOMExceptionTag); + write(exception->message()); + write(exception->name()); + return; + } + + code = SerializationReturnCode::DataCloneError; + } + + bool dumpIfTerminal(JSValue value, SerializationReturnCode& code) + { + if (!value.isCell()) { + dumpImmediate(value, code); + return true; + } + ASSERT(value.isCell()); + + if (value.isString()) { + dumpString(asString(value)->value(m_lexicalGlobalObject)); + return true; + } + + if (value.isHeapBigInt()) { + write(BigIntTag); + dumpBigIntData(value); + return true; + } + + if (value.isSymbol()) { + code = SerializationReturnCode::DataCloneError; + return true; + } + + VM& vm = m_lexicalGlobalObject->vm(); + if (isArray(value)) + return false; + + if (value.isObject()) { + auto* obj = asObject(value); + if (auto* dateObject = jsDynamicCast(obj)) { + write(DateTag); + write(dateObject->internalNumber()); + return true; + } + if (auto* booleanObject = jsDynamicCast(obj)) { + if (!startObjectInternal(booleanObject)) // handle duplicates + return true; + write(booleanObject->internalValue().toBoolean(m_lexicalGlobalObject) ? TrueObjectTag : FalseObjectTag); + return true; + } + if (auto* stringObject = jsDynamicCast(obj)) { + if (!startObjectInternal(stringObject)) // handle duplicates + return true; + String str = asString(stringObject->internalValue())->value(m_lexicalGlobalObject); + dumpStringObject(str); + return true; + } + if (auto* numberObject = jsDynamicCast(obj)) { + if (!startObjectInternal(numberObject)) // handle duplicates + return true; + write(NumberObjectTag); + write(numberObject->internalValue().asNumber()); + return true; + } + if (auto* bigIntObject = jsDynamicCast(obj)) { + if (!startObjectInternal(bigIntObject)) // handle duplicates + return true; + JSValue bigIntValue = bigIntObject->internalValue(); + ASSERT(bigIntValue.isBigInt()); + write(BigIntObjectTag); + dumpBigIntData(bigIntValue); + return true; + } + if (auto* file = JSFile::toWrapped(vm, obj)) { + write(FileTag); + write(*file); + return true; + } + if (auto* list = JSFileList::toWrapped(vm, obj)) { + write(FileListTag); + write(list->length()); + for (auto& file : list->files()) + write(file.get()); + return true; + } + if (auto* blob = JSBlob::toWrapped(vm, obj)) { + write(BlobTag); + m_blobHandles.append(blob->handle().isolatedCopy()); + write(blob->url().string()); + write(blob->type()); + static_assert(sizeof(uint64_t) == sizeof(decltype(blob->size()))); + uint64_t size = blob->size(); + write(size); + uint64_t memoryCost = blob->memoryCost(); + write(memoryCost); + return true; + } + if (auto* data = JSImageData::toWrapped(vm, obj)) { + write(ImageDataTag); + write(data->width()); + write(data->height()); + CheckedUint32 dataLength = data->data().length(); + if (dataLength.hasOverflowed()) { + code = SerializationReturnCode::DataCloneError; + return true; + } + write(dataLength); + write(data->data().data(), dataLength); + write(data->colorSpace()); + return true; + } + if (auto* regExp = jsDynamicCast(obj)) { + write(RegExpTag); + write(regExp->regExp()->pattern()); + write(String::fromLatin1(JSC::Yarr::flagsString(regExp->regExp()->flags()).data())); + return true; + } + if (obj->inherits()) { + auto index = m_transferredMessagePorts.find(obj); + if (index != m_transferredMessagePorts.end()) { + write(MessagePortReferenceTag); + write(index->value); + return true; + } + // MessagePort object could not be found in transferred message ports + code = SerializationReturnCode::ValidationError; + return true; + } + if (auto* arrayBuffer = toPossiblySharedArrayBuffer(vm, obj)) { + if (arrayBuffer->isDetached()) { + code = SerializationReturnCode::ValidationError; + return true; + } + auto index = m_transferredArrayBuffers.find(obj); + if (index != m_transferredArrayBuffers.end()) { + write(ArrayBufferTransferTag); + write(index->value); + return true; + } + if (!startObjectInternal(obj)) // handle duplicates + return true; + + if (arrayBuffer->isShared() && m_context == SerializationContext::WorkerPostMessage) { + // https://html.spec.whatwg.org/multipage/structured-data.html#structuredserializeinternal + if (!JSC::Options::useSharedArrayBuffer()) { + code = SerializationReturnCode::DataCloneError; + return true; + } + uint32_t index = m_sharedBuffers.size(); + ArrayBufferContents contents; + if (arrayBuffer->shareWith(contents)) { + write(SharedArrayBufferTag); + m_sharedBuffers.append(WTFMove(contents)); + write(index); + return true; + } + } + + if (arrayBuffer->isResizableOrGrowableShared()) { + write(ResizableArrayBufferTag); + uint64_t byteLength = arrayBuffer->byteLength(); + write(byteLength); + uint64_t maxByteLength = arrayBuffer->maxByteLength().value_or(0); + write(maxByteLength); + write(static_cast(arrayBuffer->data()), byteLength); + return true; + } + + write(ArrayBufferTag); + uint64_t byteLength = arrayBuffer->byteLength(); + write(byteLength); + write(static_cast(arrayBuffer->data()), byteLength); + return true; + } + if (obj->inherits()) { + if (checkForDuplicate(obj)) + return true; + bool success = dumpArrayBufferView(obj, code); + recordObject(obj); + return success; + } +#if ENABLE(WEB_CRYPTO) + if (auto* key = JSCryptoKey::toWrapped(vm, obj)) { + write(CryptoKeyTag); + Vector serializedKey; + Vector dummyBlobHandles; + Vector> dummyMessagePorts; + Vector> dummyArrayBuffers; +#if ENABLE(WEB_CODECS) + Vector> dummyVideoChunks; + Vector> dummyVideoFrames; +#endif +#if ENABLE(WEBASSEMBLY) + WasmModuleArray dummyModules; + WasmMemoryHandleArray dummyMemoryHandles; +#endif + ArrayBufferContentsArray dummySharedBuffers; + CloneSerializer rawKeySerializer(m_lexicalGlobalObject, dummyMessagePorts, dummyArrayBuffers, { }, +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + { }, +#endif +#if ENABLE(WEB_RTC) + { }, +#endif +#if ENABLE(WEB_CODECS) + dummyVideoChunks, + dummyVideoFrames, +#endif +#if ENABLE(WEBASSEMBLY) + dummyModules, + dummyMemoryHandles, +#endif + dummyBlobHandles, serializedKey, SerializationContext::Default, dummySharedBuffers, m_forStorage); + rawKeySerializer.write(key); + Vector wrappedKey; + if (!wrapCryptoKey(m_lexicalGlobalObject, serializedKey, wrappedKey)) + return false; + write(wrappedKey); + return true; + } +#endif +#if ENABLE(WEB_RTC) + if (auto* rtcCertificate = JSRTCCertificate::toWrapped(vm, obj)) { + write(RTCCertificateTag); + write(rtcCertificate->expires()); + write(rtcCertificate->pemCertificate()); + write(rtcCertificate->origin().toString()); + write(rtcCertificate->pemPrivateKey()); + write(static_cast(rtcCertificate->getFingerprints().size())); + for (const auto& fingerprint : rtcCertificate->getFingerprints()) { + write(fingerprint.algorithm); + write(fingerprint.value); + } + return true; + } +#endif +#if ENABLE(WEBASSEMBLY) + if (JSWebAssemblyModule* module = jsDynamicCast(obj)) { + if (m_context != SerializationContext::WorkerPostMessage && m_context != SerializationContext::WindowPostMessage) + return false; + + uint32_t index = m_wasmModules.size(); + m_wasmModules.append(&module->module()); + write(WasmModuleTag); + write(agentClusterIDFromGlobalObject(*m_lexicalGlobalObject)); + write(index); + return true; + } + if (JSWebAssemblyMemory* memory = jsDynamicCast(obj)) { + if (!JSC::Options::useSharedArrayBuffer() || memory->memory().sharingMode() != JSC::MemorySharingMode::Shared) { + code = SerializationReturnCode::DataCloneError; + return true; + } + if (m_context != SerializationContext::WorkerPostMessage) { + code = SerializationReturnCode::DataCloneError; + return true; + } + uint32_t index = m_wasmMemoryHandles.size(); + m_wasmMemoryHandles.append(memory->memory().shared()); + write(WasmMemoryTag); + write(agentClusterIDFromGlobalObject(*m_lexicalGlobalObject)); + write(index); + return true; + } +#endif + if (obj->inherits()) { + dumpDOMPoint(obj); + return true; + } + if (obj->inherits()) { + dumpDOMRect(obj); + return true; + } + if (obj->inherits()) { + dumpDOMMatrix(obj); + return true; + } + if (obj->inherits()) { + dumpDOMQuad(obj); + return true; + } + if (obj->inherits()) { + dumpImageBitmap(obj, code); + return true; + } +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + if (obj->inherits()) { + dumpOffscreenCanvas(obj, code); + return true; + } +#endif +#if ENABLE(WEB_RTC) + if (obj->inherits()) { + dumpRTCDataChannel(obj, code); + return true; + } +#endif + if (obj->inherits()) { + dumpDOMException(obj, code); + return true; + } +#if ENABLE(WEB_CODECS) + if (obj->inherits()) { + if (m_forStorage == SerializationForStorage::Yes) + return false; + dumpWebCodecsEncodedVideoChunk(obj); + return true; + } + if (obj->inherits()) { + if (m_forStorage == SerializationForStorage::Yes) + return false; + return dumpWebCodecsVideoFrame(obj); + } +#endif + + return false; + } + // Any other types are expected to serialize as null. + write(NullTag); + return true; + } + + void write(SerializationTag tag) + { + writeLittleEndian(m_buffer, static_cast(tag)); + } + + void write(ArrayBufferViewSubtag tag) + { + writeLittleEndian(m_buffer, static_cast(tag)); + } + + void write(DestinationColorSpaceTag tag) + { + writeLittleEndian(m_buffer, static_cast(tag)); + } + +#if ENABLE(WEB_CRYPTO) + void write(CryptoKeyClassSubtag tag) + { + writeLittleEndian(m_buffer, static_cast(tag)); + } + + void write(CryptoKeyAsymmetricTypeSubtag tag) + { + writeLittleEndian(m_buffer, static_cast(tag)); + } + + void write(CryptoKeyUsageTag tag) + { + writeLittleEndian(m_buffer, static_cast(tag)); + } + + void write(CryptoAlgorithmIdentifierTag tag) + { + writeLittleEndian(m_buffer, static_cast(tag)); + } + + void write(CryptoKeyOKPOpNameTag tag) + { + writeLittleEndian(m_buffer, static_cast(tag)); + } +#endif + + void write(uint8_t c) + { + writeLittleEndian(m_buffer, c); + } + + void write(uint32_t i) + { + writeLittleEndian(m_buffer, i); + } + + void write(double d) + { + union { + double d; + int64_t i; + } u; + u.d = d; + writeLittleEndian(m_buffer, u.i); + } + + void write(int32_t i) + { + writeLittleEndian(m_buffer, i); + } + + void write(uint64_t i) + { + writeLittleEndian(m_buffer, i); + } + + void write(uint16_t ch) + { + writeLittleEndian(m_buffer, ch); + } + + void writeStringIndex(unsigned i) + { + writeConstantPoolIndex(m_constantPool, i); + } + + void writeObjectIndex(unsigned i) + { + writeConstantPoolIndex(m_objectPool, i); + } + + template void writeConstantPoolIndex(const T& constantPool, unsigned i) + { + ASSERT(i < constantPool.size()); + if (constantPool.size() <= 0xFF) + write(static_cast(i)); + else if (constantPool.size() <= 0xFFFF) + write(static_cast(i)); + else + write(static_cast(i)); + } + + void write(const Identifier& ident) + { + const String& str = ident.string(); + StringConstantPool::AddResult addResult = m_constantPool.add(ident.impl(), m_constantPool.size()); + if (!addResult.isNewEntry) { + write(StringPoolTag); + writeStringIndex(addResult.iterator->value); + return; + } + + unsigned length = str.length(); + + // Guard against overflow + if (length > (std::numeric_limits::max() - sizeof(uint32_t)) / sizeof(UChar)) { + fail(); + return; + } + + if (str.is8Bit()) + writeLittleEndian(m_buffer, length | StringDataIs8BitFlag); + else + writeLittleEndian(m_buffer, length); + + if (!length) + return; + if (str.is8Bit()) { + if (!writeLittleEndian(m_buffer, str.characters8(), length)) + fail(); + return; + } + if (!writeLittleEndian(m_buffer, str.characters16(), length)) + fail(); + } + + void write(const String& str) + { + if (str.isNull()) + write(m_emptyIdentifier); + else + write(Identifier::fromString(m_lexicalGlobalObject->vm(), str)); + } + + void write(const Vector& vector) + { + uint32_t size = vector.size(); + write(size); + writeLittleEndian(m_buffer, vector.data(), size); + } + + void write(const File& file) + { + m_blobHandles.append(file.handle().isolatedCopy()); + write(file.path()); + write(file.url().string()); + write(file.type()); + write(file.name()); + write(static_cast(file.lastModifiedOverride().value_or(-1))); + } + + void write(PredefinedColorSpace colorSpace) + { + switch (colorSpace) { + case PredefinedColorSpace::SRGB: + writeLittleEndian(m_buffer, static_cast(PredefinedColorSpaceTag::SRGB)); + break; +#if ENABLE(PREDEFINED_COLOR_SPACE_DISPLAY_P3) + case PredefinedColorSpace::DisplayP3: + writeLittleEndian(m_buffer, static_cast(PredefinedColorSpaceTag::DisplayP3)); + break; +#endif + } + } + +#if PLATFORM(COCOA) + void write(const RetainPtr& data) + { + uint32_t dataLength = CFDataGetLength(data.get()); + write(dataLength); + write(CFDataGetBytePtr(data.get()), dataLength); + } +#endif + + void write(DestinationColorSpace destinationColorSpace) + { + if (destinationColorSpace == DestinationColorSpace::SRGB()) { + write(DestinationColorSpaceSRGBTag); + return; + } + +#if ENABLE(DESTINATION_COLOR_SPACE_LINEAR_SRGB) + if (destinationColorSpace == DestinationColorSpace::LinearSRGB()) { + write(DestinationColorSpaceLinearSRGBTag); + return; + } +#endif + +#if ENABLE(DESTINATION_COLOR_SPACE_DISPLAY_P3) + if (destinationColorSpace == DestinationColorSpace::DisplayP3()) { + write(DestinationColorSpaceDisplayP3Tag); + return; + } +#endif + +#if PLATFORM(COCOA) + auto colorSpace = destinationColorSpace.platformColorSpace(); + + if (auto name = CGColorSpaceGetName(colorSpace)) { + auto data = adoptCF(CFStringCreateExternalRepresentation(nullptr, name, kCFStringEncodingUTF8, 0)); + if (!data) { + write(DestinationColorSpaceSRGBTag); + return; + } + + write(DestinationColorSpaceCGColorSpaceNameTag); + write(data); + return; + } + + if (auto propertyList = adoptCF(CGColorSpaceCopyPropertyList(colorSpace))) { + auto data = adoptCF(CFPropertyListCreateData(nullptr, propertyList.get(), kCFPropertyListBinaryFormat_v1_0, 0, nullptr)); + if (!data) { + write(DestinationColorSpaceSRGBTag); + return; + } + + write(DestinationColorSpaceCGColorSpacePropertyListTag); + write(data); + return; + } +#endif + + ASSERT_NOT_REACHED(); + write(DestinationColorSpaceSRGBTag); + } + +#if ENABLE(WEB_CRYPTO) + void write(CryptoKeyOKP::NamedCurve curve) + { + switch (curve) { + case CryptoKeyOKP::NamedCurve::X25519: + write(CryptoKeyOKPOpNameTag::X25519); + break; + case CryptoKeyOKP::NamedCurve::Ed25519: + write(CryptoKeyOKPOpNameTag::ED25519); + break; + } + } + + void write(CryptoAlgorithmIdentifier algorithm) + { + switch (algorithm) { + case CryptoAlgorithmIdentifier::RSAES_PKCS1_v1_5: + write(CryptoAlgorithmIdentifierTag::RSAES_PKCS1_v1_5); + break; + case CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5: + write(CryptoAlgorithmIdentifierTag::RSASSA_PKCS1_v1_5); + break; + case CryptoAlgorithmIdentifier::RSA_PSS: + write(CryptoAlgorithmIdentifierTag::RSA_PSS); + break; + case CryptoAlgorithmIdentifier::RSA_OAEP: + write(CryptoAlgorithmIdentifierTag::RSA_OAEP); + break; + case CryptoAlgorithmIdentifier::ECDSA: + write(CryptoAlgorithmIdentifierTag::ECDSA); + break; + case CryptoAlgorithmIdentifier::ECDH: + write(CryptoAlgorithmIdentifierTag::ECDH); + break; + case CryptoAlgorithmIdentifier::AES_CTR: + write(CryptoAlgorithmIdentifierTag::AES_CTR); + break; + case CryptoAlgorithmIdentifier::AES_CBC: + write(CryptoAlgorithmIdentifierTag::AES_CBC); + break; + case CryptoAlgorithmIdentifier::AES_GCM: + write(CryptoAlgorithmIdentifierTag::AES_GCM); + break; + case CryptoAlgorithmIdentifier::AES_CFB: + write(CryptoAlgorithmIdentifierTag::AES_CFB); + break; + case CryptoAlgorithmIdentifier::AES_KW: + write(CryptoAlgorithmIdentifierTag::AES_KW); + break; + case CryptoAlgorithmIdentifier::HMAC: + write(CryptoAlgorithmIdentifierTag::HMAC); + break; + case CryptoAlgorithmIdentifier::SHA_1: + write(CryptoAlgorithmIdentifierTag::SHA_1); + break; + case CryptoAlgorithmIdentifier::SHA_224: + write(CryptoAlgorithmIdentifierTag::SHA_224); + break; + case CryptoAlgorithmIdentifier::SHA_256: + write(CryptoAlgorithmIdentifierTag::SHA_256); + break; + case CryptoAlgorithmIdentifier::SHA_384: + write(CryptoAlgorithmIdentifierTag::SHA_384); + break; + case CryptoAlgorithmIdentifier::SHA_512: + write(CryptoAlgorithmIdentifierTag::SHA_512); + break; + case CryptoAlgorithmIdentifier::HKDF: + write(CryptoAlgorithmIdentifierTag::HKDF); + break; + case CryptoAlgorithmIdentifier::PBKDF2: + write(CryptoAlgorithmIdentifierTag::PBKDF2); + break; + case CryptoAlgorithmIdentifier::Ed25519: + write(CryptoAlgorithmIdentifierTag::ED25519); + break; + } + } + + void write(CryptoKeyRSAComponents::Type type) + { + switch (type) { + case CryptoKeyRSAComponents::Type::Public: + write(CryptoKeyAsymmetricTypeSubtag::Public); + return; + case CryptoKeyRSAComponents::Type::Private: + write(CryptoKeyAsymmetricTypeSubtag::Private); + return; + } + } + + void write(const CryptoKeyRSAComponents& key) + { + write(key.type()); + write(key.modulus()); + write(key.exponent()); + if (key.type() == CryptoKeyRSAComponents::Type::Public) + return; + + write(key.privateExponent()); + + unsigned primeCount = key.hasAdditionalPrivateKeyParameters() ? key.otherPrimeInfos().size() + 2 : 0; + write(primeCount); + if (!primeCount) + return; + + write(key.firstPrimeInfo().primeFactor); + write(key.firstPrimeInfo().factorCRTExponent); + write(key.secondPrimeInfo().primeFactor); + write(key.secondPrimeInfo().factorCRTExponent); + write(key.secondPrimeInfo().factorCRTCoefficient); + for (unsigned i = 2; i < primeCount; ++i) { + write(key.otherPrimeInfos()[i].primeFactor); + write(key.otherPrimeInfos()[i].factorCRTExponent); + write(key.otherPrimeInfos()[i].factorCRTCoefficient); + } + } + + void write(const CryptoKey* key) + { + write(currentKeyFormatVersion); + + write(key->extractable()); + + CryptoKeyUsageBitmap usages = key->usagesBitmap(); + write(countUsages(usages)); + if (usages & CryptoKeyUsageEncrypt) + write(CryptoKeyUsageTag::Encrypt); + if (usages & CryptoKeyUsageDecrypt) + write(CryptoKeyUsageTag::Decrypt); + if (usages & CryptoKeyUsageSign) + write(CryptoKeyUsageTag::Sign); + if (usages & CryptoKeyUsageVerify) + write(CryptoKeyUsageTag::Verify); + if (usages & CryptoKeyUsageDeriveKey) + write(CryptoKeyUsageTag::DeriveKey); + if (usages & CryptoKeyUsageDeriveBits) + write(CryptoKeyUsageTag::DeriveBits); + if (usages & CryptoKeyUsageWrapKey) + write(CryptoKeyUsageTag::WrapKey); + if (usages & CryptoKeyUsageUnwrapKey) + write(CryptoKeyUsageTag::UnwrapKey); + + switch (key->keyClass()) { + case CryptoKeyClass::HMAC: + write(CryptoKeyClassSubtag::HMAC); + write(downcast(*key).key()); + write(downcast(*key).hashAlgorithmIdentifier()); + break; + case CryptoKeyClass::AES: + write(CryptoKeyClassSubtag::AES); + write(key->algorithmIdentifier()); + write(downcast(*key).key()); + break; + case CryptoKeyClass::EC: + write(CryptoKeyClassSubtag::EC); + write(key->algorithmIdentifier()); + write(downcast(*key).namedCurveString()); + switch (key->type()) { + case CryptoKey::Type::Public: { + write(CryptoKeyAsymmetricTypeSubtag::Public); + auto result = downcast(*key).exportRaw(); + ASSERT(!result.hasException()); + write(result.releaseReturnValue()); + break; + } + case CryptoKey::Type::Private: { + write(CryptoKeyAsymmetricTypeSubtag::Private); + // Use the standard complied method is not very efficient, but simple/reliable. + auto result = downcast(*key).exportPkcs8(); + ASSERT(!result.hasException()); + write(result.releaseReturnValue()); + break; + } + default: + ASSERT_NOT_REACHED(); + } + break; + case CryptoKeyClass::Raw: + write(CryptoKeyClassSubtag::Raw); + write(key->algorithmIdentifier()); + write(downcast(*key).key()); + break; + case CryptoKeyClass::RSA: { + write(CryptoKeyClassSubtag::RSA); + write(key->algorithmIdentifier()); + CryptoAlgorithmIdentifier hash; + bool isRestrictedToHash = downcast(*key).isRestrictedToHash(hash); + write(isRestrictedToHash); + if (isRestrictedToHash) + write(hash); + write(*downcast(*key).exportData()); + break; + } + case CryptoKeyClass::OKP: + write(CryptoKeyClassSubtag::OKP); + write(key->algorithmIdentifier()); + write(downcast(*key).namedCurve()); + write(downcast(*key).platformKey()); + break; + } + } +#endif + + void write(const uint8_t* data, unsigned length) + { + m_buffer.append(data, length); + } + + Vector& m_buffer; + Vector& m_blobHandles; + ObjectPool m_objectPool; + ObjectPool m_transferredMessagePorts; + ObjectPool m_transferredArrayBuffers; + ObjectPool m_transferredImageBitmaps; +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + ObjectPool m_transferredOffscreenCanvases; +#endif +#if ENABLE(WEB_RTC) + ObjectPool m_transferredRTCDataChannels; +#endif + typedef HashMap, uint32_t, IdentifierRepHash> StringConstantPool; + StringConstantPool m_constantPool; + Identifier m_emptyIdentifier; + SerializationContext m_context; + ArrayBufferContentsArray& m_sharedBuffers; +#if ENABLE(WEBASSEMBLY) + WasmModuleArray& m_wasmModules; + WasmMemoryHandleArray& m_wasmMemoryHandles; +#endif +#if ENABLE(WEB_CODECS) + Vector>& m_serializedVideoChunks; + Vector>& m_serializedVideoFrames; +#endif + SerializationForStorage m_forStorage; +}; + +SerializationReturnCode CloneSerializer::serialize(JSValue in) +{ + VM& vm = m_lexicalGlobalObject->vm(); + Vector indexStack; + Vector lengthStack; + Vector propertyStack; + Vector inputObjectStack; + Vector mapIteratorStack; + Vector setIteratorStack; + Vector mapIteratorValueStack; + Vector stateStack; + WalkerState state = StateUnknown; + JSValue inValue = in; + auto scope = DECLARE_THROW_SCOPE(vm); + while (1) { + switch (state) { + arrayStartState: + case ArrayStartState: { + ASSERT(isArray(inValue)); + if (inputObjectStack.size() > maximumFilterRecursion) + return SerializationReturnCode::StackOverflowError; + + JSArray* inArray = asArray(inValue); + unsigned length = inArray->length(); + if (!startArray(inArray)) + break; + inputObjectStack.append(inArray); + indexStack.append(0); + lengthStack.append(length); + } + arrayStartVisitMember: + FALLTHROUGH; + case ArrayStartVisitMember: { + JSObject* array = inputObjectStack.last(); + uint32_t index = indexStack.last(); + if (index == lengthStack.last()) { + indexStack.removeLast(); + lengthStack.removeLast(); + + propertyStack.append(PropertyNameArray(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude)); + array->getOwnNonIndexPropertyNames(m_lexicalGlobalObject, propertyStack.last(), DontEnumPropertiesMode::Exclude); + if (UNLIKELY(scope.exception())) + return SerializationReturnCode::ExistingExceptionError; + if (propertyStack.last().size()) { + write(NonIndexPropertiesTag); + indexStack.append(0); + goto objectStartVisitMember; + } + propertyStack.removeLast(); + + endObject(); + inputObjectStack.removeLast(); + break; + } + inValue = array->getDirectIndex(m_lexicalGlobalObject, index); + if (UNLIKELY(scope.exception())) + return SerializationReturnCode::ExistingExceptionError; + if (!inValue) { + indexStack.last()++; + goto arrayStartVisitMember; + } + + write(index); + auto terminalCode = SerializationReturnCode::SuccessfullyCompleted; + if (dumpIfTerminal(inValue, terminalCode)) { + if (terminalCode != SerializationReturnCode::SuccessfullyCompleted) + return terminalCode; + indexStack.last()++; + goto arrayStartVisitMember; + } + stateStack.append(ArrayEndVisitMember); + goto stateUnknown; + } + case ArrayEndVisitMember: { + indexStack.last()++; + goto arrayStartVisitMember; + } + objectStartState: + case ObjectStartState: { + ASSERT(inValue.isObject()); + if (inputObjectStack.size() > maximumFilterRecursion) + return SerializationReturnCode::StackOverflowError; + JSObject* inObject = asObject(inValue); + if (!startObject(inObject)) + break; + // At this point, all supported objects other than Object + // objects have been handled. If we reach this point and + // the input is not an Object object then we should throw + // a DataCloneError. + if (inObject->classInfo() != JSFinalObject::info()) + return SerializationReturnCode::DataCloneError; + inputObjectStack.append(inObject); + indexStack.append(0); + propertyStack.append(PropertyNameArray(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude)); + inObject->methodTable()->getOwnPropertyNames(inObject, m_lexicalGlobalObject, propertyStack.last(), DontEnumPropertiesMode::Exclude); + if (UNLIKELY(scope.exception())) + return SerializationReturnCode::ExistingExceptionError; + } + objectStartVisitMember: + FALLTHROUGH; + case ObjectStartVisitMember: { + JSObject* object = inputObjectStack.last(); + uint32_t index = indexStack.last(); + PropertyNameArray& properties = propertyStack.last(); + if (index == properties.size()) { + endObject(); + inputObjectStack.removeLast(); + indexStack.removeLast(); + propertyStack.removeLast(); + break; + } + inValue = getProperty(object, properties[index]); + if (UNLIKELY(scope.exception())) + return SerializationReturnCode::ExistingExceptionError; + + if (!inValue) { + // Property was removed during serialisation + indexStack.last()++; + goto objectStartVisitMember; + } + write(properties[index]); + + if (UNLIKELY(scope.exception())) + return SerializationReturnCode::ExistingExceptionError; + + auto terminalCode = SerializationReturnCode::SuccessfullyCompleted; + if (!dumpIfTerminal(inValue, terminalCode)) { + stateStack.append(ObjectEndVisitMember); + goto stateUnknown; + } + if (terminalCode != SerializationReturnCode::SuccessfullyCompleted) + return terminalCode; + FALLTHROUGH; + } + case ObjectEndVisitMember: { + if (UNLIKELY(scope.exception())) + return SerializationReturnCode::ExistingExceptionError; + + indexStack.last()++; + goto objectStartVisitMember; + } + mapStartState: { + ASSERT(inValue.isObject()); + if (inputObjectStack.size() > maximumFilterRecursion) + return SerializationReturnCode::StackOverflowError; + JSMap* inMap = jsCast(inValue); + if (!startMap(inMap)) + break; + JSMapIterator* iterator = JSMapIterator::create(vm, m_lexicalGlobalObject->mapIteratorStructure(), inMap, IterationKind::Entries); + m_gcBuffer.appendWithCrashOnOverflow(inMap); + m_gcBuffer.appendWithCrashOnOverflow(iterator); + mapIteratorStack.append(iterator); + inputObjectStack.append(inMap); + goto mapDataStartVisitEntry; + } + mapDataStartVisitEntry: + case MapDataStartVisitEntry: { + JSMapIterator* iterator = mapIteratorStack.last(); + JSValue key, value; + if (!iterator->nextKeyValue(m_lexicalGlobalObject, key, value)) { + mapIteratorStack.removeLast(); + JSObject* object = inputObjectStack.last(); + ASSERT(jsDynamicCast(object)); + propertyStack.append(PropertyNameArray(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude)); + object->methodTable()->getOwnPropertyNames(object, m_lexicalGlobalObject, propertyStack.last(), DontEnumPropertiesMode::Exclude); + if (UNLIKELY(scope.exception())) + return SerializationReturnCode::ExistingExceptionError; + write(NonMapPropertiesTag); + indexStack.append(0); + goto objectStartVisitMember; + } + inValue = key; + m_gcBuffer.appendWithCrashOnOverflow(value); + mapIteratorValueStack.append(value); + stateStack.append(MapDataEndVisitKey); + goto stateUnknown; + } + case MapDataEndVisitKey: { + inValue = mapIteratorValueStack.last(); + mapIteratorValueStack.removeLast(); + stateStack.append(MapDataEndVisitValue); + goto stateUnknown; + } + case MapDataEndVisitValue: { + goto mapDataStartVisitEntry; + } + + setStartState: { + ASSERT(inValue.isObject()); + if (inputObjectStack.size() > maximumFilterRecursion) + return SerializationReturnCode::StackOverflowError; + JSSet* inSet = jsCast(inValue); + if (!startSet(inSet)) + break; + JSSetIterator* iterator = JSSetIterator::create(vm, m_lexicalGlobalObject->setIteratorStructure(), inSet, IterationKind::Keys); + m_gcBuffer.appendWithCrashOnOverflow(inSet); + m_gcBuffer.appendWithCrashOnOverflow(iterator); + setIteratorStack.append(iterator); + inputObjectStack.append(inSet); + goto setDataStartVisitEntry; + } + setDataStartVisitEntry: + case SetDataStartVisitEntry: { + JSSetIterator* iterator = setIteratorStack.last(); + JSValue key; + if (!iterator->next(m_lexicalGlobalObject, key)) { + setIteratorStack.removeLast(); + JSObject* object = inputObjectStack.last(); + ASSERT(jsDynamicCast(object)); + propertyStack.append(PropertyNameArray(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude)); + object->methodTable()->getOwnPropertyNames(object, m_lexicalGlobalObject, propertyStack.last(), DontEnumPropertiesMode::Exclude); + if (UNLIKELY(scope.exception())) + return SerializationReturnCode::ExistingExceptionError; + write(NonSetPropertiesTag); + indexStack.append(0); + goto objectStartVisitMember; + } + inValue = key; + stateStack.append(SetDataEndVisitKey); + goto stateUnknown; + } + case SetDataEndVisitKey: { + goto setDataStartVisitEntry; + } + + stateUnknown: + case StateUnknown: { + auto terminalCode = SerializationReturnCode::SuccessfullyCompleted; + if (dumpIfTerminal(inValue, terminalCode)) { + if (terminalCode != SerializationReturnCode::SuccessfullyCompleted) + return terminalCode; + break; + } + + if (isArray(inValue)) + goto arrayStartState; + if (isMap(inValue)) + goto mapStartState; + if (isSet(inValue)) + goto setStartState; + goto objectStartState; + } + } + if (stateStack.isEmpty()) + break; + + state = stateStack.last(); + stateStack.removeLast(); + } + if (m_failed) + return SerializationReturnCode::UnspecifiedError; + + return SerializationReturnCode::SuccessfullyCompleted; +} + +class CloneDeserializer : CloneBase { + WTF_FORBID_HEAP_ALLOCATION; +public: + static String deserializeString(const Vector& buffer) + { + if (buffer.isEmpty()) + return String(); + const uint8_t* ptr = buffer.begin(); + const uint8_t* end = buffer.end(); + uint32_t version; + if (!readLittleEndian(ptr, end, version) || version > CurrentVersion) + return String(); + uint8_t tag; + if (!readLittleEndian(ptr, end, tag) || tag != StringTag) + return String(); + uint32_t length; + if (!readLittleEndian(ptr, end, length)) + return String(); + bool is8Bit = length & StringDataIs8BitFlag; + length &= ~StringDataIs8BitFlag; + String str; + if (!readString(ptr, end, str, length, is8Bit)) + return String(); + return str; + } + + static DeserializationResult deserialize(JSGlobalObject* lexicalGlobalObject, JSGlobalObject* globalObject, const Vector>& messagePorts, Vector>&& backingStores +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + , Vector>&& detachedOffscreenCanvases +#endif +#if ENABLE(WEB_RTC) + , Vector>&& detachedRTCDataChannels +#endif + , ArrayBufferContentsArray* arrayBufferContentsArray, const Vector& buffer, const Vector& blobURLs, const Vector blobFilePaths, ArrayBufferContentsArray* sharedBuffers +#if ENABLE(WEBASSEMBLY) + , WasmModuleArray* wasmModules + , WasmMemoryHandleArray* wasmMemoryHandles +#endif +#if ENABLE(WEB_CODECS) + , Vector>&& serializedVideoChunks + , Vector&& serializedVideoFrames +#endif + ) + { + if (!buffer.size()) + return std::make_pair(jsNull(), SerializationReturnCode::UnspecifiedError); + CloneDeserializer deserializer(lexicalGlobalObject, globalObject, messagePorts, arrayBufferContentsArray, buffer, blobURLs, blobFilePaths, sharedBuffers, WTFMove(backingStores) +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + , WTFMove(detachedOffscreenCanvases) +#endif +#if ENABLE(WEB_RTC) + , WTFMove(detachedRTCDataChannels) +#endif +#if ENABLE(WEBASSEMBLY) + , wasmModules + , wasmMemoryHandles +#endif +#if ENABLE(WEB_CODECS) + , WTFMove(serializedVideoChunks) + , WTFMove(serializedVideoFrames) +#endif + ); + if (!deserializer.isValid()) + return std::make_pair(JSValue(), SerializationReturnCode::ValidationError); + return deserializer.deserialize(); + } + +private: + struct CachedString { + CachedString(const String& string) + : m_string(string) + { + } + + JSValue jsString(JSGlobalObject* lexicalGlobalObject) + { + if (!m_jsString) + m_jsString = JSC::jsString(lexicalGlobalObject->vm(), m_string); + return m_jsString; + } + const String& string() { return m_string; } + String takeString() { return WTFMove(m_string); } + + private: + String m_string; + JSValue m_jsString; + }; + + struct CachedStringRef { + CachedStringRef() + : m_base(0) + , m_index(0) + { + } + CachedStringRef(Vector* base, size_t index) + : m_base(base) + , m_index(index) + { + } + + CachedString* operator->() { ASSERT(m_base); return &m_base->at(m_index); } + + private: + Vector* m_base; + size_t m_index; + }; + + CloneDeserializer(JSGlobalObject* lexicalGlobalObject, JSGlobalObject* globalObject, const Vector>& messagePorts, ArrayBufferContentsArray* arrayBufferContents, Vector>&& backingStores, const Vector& buffer +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + , Vector>&& detachedOffscreenCanvases = { } +#endif +#if ENABLE(WEB_RTC) + , Vector>&& detachedRTCDataChannels = { } +#endif +#if ENABLE(WEBASSEMBLY) + , WasmModuleArray* wasmModules = nullptr + , WasmMemoryHandleArray* wasmMemoryHandles = nullptr +#endif +#if ENABLE(WEB_CODECS) + , Vector>&& serializedVideoChunks = { } + , Vector&& serializedVideoFrames = { } +#endif + ) + : CloneBase(lexicalGlobalObject) + , m_globalObject(globalObject) + , m_isDOMGlobalObject(globalObject->inherits()) + , m_canCreateDOMObject(m_isDOMGlobalObject && !globalObject->inherits()) + , m_ptr(buffer.data()) + , m_end(buffer.data() + buffer.size()) + , m_version(0xFFFFFFFF) + , m_messagePorts(messagePorts) + , m_arrayBufferContents(arrayBufferContents) + , m_arrayBuffers(arrayBufferContents ? arrayBufferContents->size() : 0) + , m_backingStores(WTFMove(backingStores)) + , m_imageBitmaps(m_backingStores.size()) +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + , m_detachedOffscreenCanvases(WTFMove(detachedOffscreenCanvases)) + , m_offscreenCanvases(m_detachedOffscreenCanvases.size()) +#endif +#if ENABLE(WEB_RTC) + , m_detachedRTCDataChannels(WTFMove(detachedRTCDataChannels)) + , m_rtcDataChannels(m_detachedRTCDataChannels.size()) +#endif +#if ENABLE(WEBASSEMBLY) + , m_wasmModules(wasmModules) + , m_wasmMemoryHandles(wasmMemoryHandles) +#endif +#if ENABLE(WEB_CODECS) + , m_serializedVideoChunks(WTFMove(serializedVideoChunks)) + , m_videoChunks(m_serializedVideoChunks.size()) + , m_serializedVideoFrames(WTFMove(serializedVideoFrames)) + , m_videoFrames(m_serializedVideoFrames.size()) +#endif + { + if (!read(m_version)) + m_version = 0xFFFFFFFF; + } + + CloneDeserializer(JSGlobalObject* lexicalGlobalObject, JSGlobalObject* globalObject, const Vector>& messagePorts, ArrayBufferContentsArray* arrayBufferContents, const Vector& buffer, const Vector& blobURLs, const Vector blobFilePaths, ArrayBufferContentsArray* sharedBuffers, Vector>&& backingStores +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + , Vector>&& detachedOffscreenCanvases +#endif +#if ENABLE(WEB_RTC) + , Vector>&& detachedRTCDataChannels +#endif +#if ENABLE(WEBASSEMBLY) + , WasmModuleArray* wasmModules + , WasmMemoryHandleArray* wasmMemoryHandles +#endif +#if ENABLE(WEB_CODECS) + , Vector>&& serializedVideoChunks = { } + , Vector&& serializedVideoFrames = { } +#endif + ) + : CloneBase(lexicalGlobalObject) + , m_globalObject(globalObject) + , m_isDOMGlobalObject(globalObject->inherits()) + , m_canCreateDOMObject(m_isDOMGlobalObject && !globalObject->inherits()) + , m_ptr(buffer.data()) + , m_end(buffer.data() + buffer.size()) + , m_version(0xFFFFFFFF) + , m_messagePorts(messagePorts) + , m_arrayBufferContents(arrayBufferContents) + , m_arrayBuffers(arrayBufferContents ? arrayBufferContents->size() : 0) + , m_blobURLs(blobURLs) + , m_blobFilePaths(blobFilePaths) + , m_sharedBuffers(sharedBuffers) + , m_backingStores(WTFMove(backingStores)) + , m_imageBitmaps(m_backingStores.size()) +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + , m_detachedOffscreenCanvases(WTFMove(detachedOffscreenCanvases)) + , m_offscreenCanvases(m_detachedOffscreenCanvases.size()) +#endif +#if ENABLE(WEB_RTC) + , m_detachedRTCDataChannels(WTFMove(detachedRTCDataChannels)) + , m_rtcDataChannels(m_detachedRTCDataChannels.size()) +#endif +#if ENABLE(WEBASSEMBLY) + , m_wasmModules(wasmModules) + , m_wasmMemoryHandles(wasmMemoryHandles) +#endif +#if ENABLE(WEB_CODECS) + , m_serializedVideoChunks(WTFMove(serializedVideoChunks)) + , m_videoChunks(m_serializedVideoChunks.size()) + , m_serializedVideoFrames(WTFMove(serializedVideoFrames)) + , m_videoFrames(m_serializedVideoFrames.size()) +#endif + { + if (!read(m_version)) + m_version = 0xFFFFFFFF; + } + + DeserializationResult deserialize(); + + bool isValid() const { return m_version <= CurrentVersion; } + + template bool readLittleEndian(T& value) + { + if (m_failed || !readLittleEndian(m_ptr, m_end, value)) { + fail(); + return false; + } + return true; + } +#if ASSUME_LITTLE_ENDIAN + template static bool readLittleEndian(const uint8_t*& ptr, const uint8_t* end, T& value) + { + if (ptr > end - sizeof(value)) + return false; + + if (sizeof(T) == 1) + value = *ptr++; + else { + value = *reinterpret_cast(ptr); + ptr += sizeof(T); + } + return true; + } +#else + template static bool readLittleEndian(const uint8_t*& ptr, const uint8_t* end, T& value) + { + if (ptr > end - sizeof(value)) + return false; + + if (sizeof(T) == 1) + value = *ptr++; + else { + value = 0; + for (unsigned i = 0; i < sizeof(T); i++) + value += ((T)*ptr++) << (i * 8); + } + return true; + } +#endif + + bool read(uint32_t& i) + { + return readLittleEndian(i); + } + + bool read(int32_t& i) + { + return readLittleEndian(*reinterpret_cast(&i)); + } + + bool read(uint16_t& i) + { + return readLittleEndian(i); + } + + bool read(uint8_t& i) + { + return readLittleEndian(i); + } + + bool read(double& d) + { + union { + double d; + uint64_t i64; + } u; + if (!readLittleEndian(u.i64)) + return false; + d = u.d; + return true; + } + + bool read(uint64_t& i) + { + return readLittleEndian(i); + } + + bool readStringIndex(uint32_t& i) + { + return readConstantPoolIndex(m_constantPool, i); + } + + template bool readConstantPoolIndex(const T& constantPool, uint32_t& i) + { + if (constantPool.size() <= 0xFF) { + uint8_t i8; + if (!read(i8)) + return false; + i = i8; + return true; + } + if (constantPool.size() <= 0xFFFF) { + uint16_t i16; + if (!read(i16)) + return false; + i = i16; + return true; + } + return read(i); + } + + static bool readString(const uint8_t*& ptr, const uint8_t* end, String& str, unsigned length, bool is8Bit) + { + if (length >= std::numeric_limits::max() / sizeof(UChar)) + return false; + + if (is8Bit) { + if ((end - ptr) < static_cast(length)) + return false; + str = String { ptr, length }; + ptr += length; + return true; + } + + unsigned size = length * sizeof(UChar); + if ((end - ptr) < static_cast(size)) + return false; + +#if ASSUME_LITTLE_ENDIAN + str = String(reinterpret_cast(ptr), length); + ptr += length * sizeof(UChar); +#else + UChar* characters; + str = String::createUninitialized(length, characters); + for (unsigned i = 0; i < length; ++i) { + uint16_t c; + readLittleEndian(ptr, end, c); + characters[i] = c; + } +#endif + return true; + } + + bool readStringData(CachedStringRef& cachedString) + { + bool scratch; + return readStringData(cachedString, scratch); + } + + bool readStringData(CachedStringRef& cachedString, bool& wasTerminator) + { + if (m_failed) + return false; + uint32_t length = 0; + if (!read(length)) + return false; + if (length == TerminatorTag) { + wasTerminator = true; + return false; + } + if (length == StringPoolTag) { + unsigned index = 0; + if (!readStringIndex(index)) { + fail(); + return false; + } + if (index >= m_constantPool.size()) { + fail(); + return false; + } + cachedString = CachedStringRef(&m_constantPool, index); + return true; + } + bool is8Bit = length & StringDataIs8BitFlag; + length &= ~StringDataIs8BitFlag; + String str; + if (!readString(m_ptr, m_end, str, length, is8Bit)) { + fail(); + return false; + } + m_constantPool.append(str); + cachedString = CachedStringRef(&m_constantPool, m_constantPool.size() - 1); + return true; + } + + SerializationTag readTag() + { + if (m_ptr >= m_end) + return ErrorTag; + return static_cast(*m_ptr++); + } + + bool readArrayBufferViewSubtag(ArrayBufferViewSubtag& tag) + { + if (m_ptr >= m_end) + return false; + tag = static_cast(*m_ptr++); + return true; + } + + void putProperty(JSObject* object, unsigned index, JSValue value) + { + object->putDirectIndex(m_lexicalGlobalObject, index, value); + } + + void putProperty(JSObject* object, const Identifier& property, JSValue value) + { + object->putDirectMayBeIndex(m_lexicalGlobalObject, property, value); + } + + bool readFile(RefPtr& file) + { + CachedStringRef path; + if (!readStringData(path)) + return false; + CachedStringRef url; + if (!readStringData(url)) + return false; + CachedStringRef type; + if (!readStringData(type)) + return false; + CachedStringRef name; + if (!readStringData(name)) + return false; + std::optional optionalLastModified; + if (m_version > 6) { + double lastModified; + if (!read(lastModified)) + return false; + if (lastModified >= 0) + optionalLastModified = lastModified; + } + + // If the blob URL for this file has an associated blob file path, prefer that one over the "built-in" path. + String filePath = blobFilePathForBlobURL(url->string()); + if (filePath.isEmpty()) + filePath = path->string(); + + if (!m_canCreateDOMObject) + return true; + + file = File::deserialize(executionContext(m_lexicalGlobalObject), filePath, URL { url->string() }, type->string(), name->string(), optionalLastModified); + return true; + } + + template + bool readArrayBufferImpl(RefPtr& arrayBuffer) + { + LengthType length; + if (!read(length)) + return false; + if (m_ptr + length > m_end) + return false; + arrayBuffer = ArrayBuffer::tryCreate(m_ptr, length); + if (!arrayBuffer) + return false; + m_ptr += length; + return true; + } + + bool readArrayBuffer(RefPtr& arrayBuffer) + { + if (m_version < 10) + return readArrayBufferImpl(arrayBuffer); + return readArrayBufferImpl(arrayBuffer); + } + + bool readResizableNonSharedArrayBuffer(RefPtr& arrayBuffer) + { + uint64_t byteLength; + if (!read(byteLength)) + return false; + uint64_t maxByteLength; + if (!read(maxByteLength)) + return false; + if (m_ptr + byteLength > m_end) + return false; + arrayBuffer = ArrayBuffer::tryCreate(byteLength, 1, maxByteLength); + if (!arrayBuffer) + return false; + ASSERT(arrayBuffer->isResizableNonShared()); + memcpy(arrayBuffer->data(), m_ptr, byteLength); + m_ptr += byteLength; + return true; + } + + template + bool readArrayBufferViewImpl(VM& vm, JSValue& arrayBufferView) + { + ArrayBufferViewSubtag arrayBufferViewSubtag; + if (!readArrayBufferViewSubtag(arrayBufferViewSubtag)) + return false; + LengthType byteOffset; + if (!read(byteOffset)) + return false; + LengthType byteLength; + if (!read(byteLength)) + return false; + JSValue arrayBufferValue = readTerminal(); + if (!arrayBufferValue || !arrayBufferValue.inherits()) + return false; + JSObject* arrayBufferObj = asObject(arrayBufferValue); + + unsigned elementSize = typedArrayElementSize(arrayBufferViewSubtag); + if (!elementSize) + return false; + + RefPtr arrayBuffer = toPossiblySharedArrayBuffer(vm, arrayBufferObj); + if (!arrayBuffer) { + arrayBufferView = jsNull(); + return true; + } + + std::optional length; + if (byteLength != autoLengthMarker) { + LengthType computedLength = byteLength / elementSize; + if (computedLength * elementSize != byteLength) + return false; + length = computedLength; + } else { + if (!arrayBuffer->isResizableOrGrowableShared()) + return false; + } + + switch (arrayBufferViewSubtag) { + case DataViewTag: + arrayBufferView = toJS(m_lexicalGlobalObject, m_globalObject, DataView::wrappedAs(arrayBuffer.releaseNonNull(), byteOffset, length).get()); + return true; + case Int8ArrayTag: + arrayBufferView = toJS(m_lexicalGlobalObject, m_globalObject, Int8Array::wrappedAs(arrayBuffer.releaseNonNull(), byteOffset, length).get()); + return true; + case Uint8ArrayTag: + arrayBufferView = toJS(m_lexicalGlobalObject, m_globalObject, Uint8Array::wrappedAs(arrayBuffer.releaseNonNull(), byteOffset, length).get()); + return true; + case Uint8ClampedArrayTag: + arrayBufferView = toJS(m_lexicalGlobalObject, m_globalObject, Uint8ClampedArray::wrappedAs(arrayBuffer.releaseNonNull(), byteOffset, length).get()); + return true; + case Int16ArrayTag: + arrayBufferView = toJS(m_lexicalGlobalObject, m_globalObject, Int16Array::wrappedAs(arrayBuffer.releaseNonNull(), byteOffset, length).get()); + return true; + case Uint16ArrayTag: + arrayBufferView = toJS(m_lexicalGlobalObject, m_globalObject, Uint16Array::wrappedAs(arrayBuffer.releaseNonNull(), byteOffset, length).get()); + return true; + case Int32ArrayTag: + arrayBufferView = toJS(m_lexicalGlobalObject, m_globalObject, Int32Array::wrappedAs(arrayBuffer.releaseNonNull(), byteOffset, length).get()); + return true; + case Uint32ArrayTag: + arrayBufferView = toJS(m_lexicalGlobalObject, m_globalObject, Uint32Array::wrappedAs(arrayBuffer.releaseNonNull(), byteOffset, length).get()); + return true; + case Float32ArrayTag: + arrayBufferView = toJS(m_lexicalGlobalObject, m_globalObject, Float32Array::wrappedAs(arrayBuffer.releaseNonNull(), byteOffset, length).get()); + return true; + case Float64ArrayTag: + arrayBufferView = toJS(m_lexicalGlobalObject, m_globalObject, Float64Array::wrappedAs(arrayBuffer.releaseNonNull(), byteOffset, length).get()); + return true; + case BigInt64ArrayTag: + arrayBufferView = toJS(m_lexicalGlobalObject, m_globalObject, BigInt64Array::wrappedAs(arrayBuffer.releaseNonNull(), byteOffset, length).get()); + return true; + case BigUint64ArrayTag: + arrayBufferView = toJS(m_lexicalGlobalObject, m_globalObject, BigUint64Array::wrappedAs(arrayBuffer.releaseNonNull(), byteOffset, length).get()); + return true; + default: + return false; + } + } + + bool readArrayBufferView(VM& vm, JSValue& arrayBufferView) + { + if (m_version < 10) + return readArrayBufferViewImpl(vm, arrayBufferView); + return readArrayBufferViewImpl(vm, arrayBufferView); + } + + bool read(Vector& result) + { + ASSERT(result.isEmpty()); + uint32_t size; + if (!read(size)) + return false; + if (m_ptr + size > m_end) + return false; + result.append(m_ptr, size); + m_ptr += size; + return true; + } + + bool read(PredefinedColorSpace& result) + { + uint8_t tag; + if (!read(tag)) + return false; + + switch (static_cast(tag)) { + case PredefinedColorSpaceTag::SRGB: + result = PredefinedColorSpace::SRGB; + return true; +#if ENABLE(PREDEFINED_COLOR_SPACE_DISPLAY_P3) + case PredefinedColorSpaceTag::DisplayP3: + result = PredefinedColorSpace::DisplayP3; + return true; +#endif + default: + return false; + } + } + + bool read(DestinationColorSpaceTag& tag) + { + if (m_ptr >= m_end) + return false; + tag = static_cast(*m_ptr++); + return true; + } + +#if PLATFORM(COCOA) + bool read(RetainPtr& data) + { + uint32_t dataLength; + if (!read(dataLength) || static_cast(m_end - m_ptr) < dataLength) + return false; + + data = adoptCF(CFDataCreateWithBytesNoCopy(nullptr, m_ptr, dataLength, kCFAllocatorNull)); + if (!data) + return false; + + m_ptr += dataLength; + return true; + } +#endif + + bool read(DestinationColorSpace& destinationColorSpace) + { + DestinationColorSpaceTag tag; + if (!read(tag)) + return false; + + switch (tag) { + case DestinationColorSpaceSRGBTag: + destinationColorSpace = DestinationColorSpace::SRGB(); + return true; +#if ENABLE(DESTINATION_COLOR_SPACE_LINEAR_SRGB) + case DestinationColorSpaceLinearSRGBTag: + destinationColorSpace = DestinationColorSpace::LinearSRGB(); + return true; +#endif +#if ENABLE(DESTINATION_COLOR_SPACE_DISPLAY_P3) + case DestinationColorSpaceDisplayP3Tag: + destinationColorSpace = DestinationColorSpace::DisplayP3(); + return true; +#endif +#if PLATFORM(COCOA) + case DestinationColorSpaceCGColorSpaceNameTag: { + RetainPtr data; + if (!read(data)) + return false; + + auto name = adoptCF(CFStringCreateFromExternalRepresentation(nullptr, data.get(), kCFStringEncodingUTF8)); + if (!name) + return false; + + auto colorSpace = adoptCF(CGColorSpaceCreateWithName(name.get())); + if (!colorSpace) + return false; + + destinationColorSpace = DestinationColorSpace(colorSpace.get()); + return true; + } + case DestinationColorSpaceCGColorSpacePropertyListTag: { + RetainPtr data; + if (!read(data)) + return false; + + auto propertyList = adoptCF(CFPropertyListCreateWithData(nullptr, data.get(), kCFPropertyListImmutable, nullptr, nullptr)); + if (!propertyList) + return false; + + auto colorSpace = adoptCF(CGColorSpaceCreateWithPropertyList(propertyList.get())); + if (!colorSpace) + return false; + + destinationColorSpace = DestinationColorSpace(colorSpace.get()); + return true; + } +#endif + } + + ASSERT_NOT_REACHED(); + return false; + } + +#if ENABLE(WEB_CRYPTO) + bool read(CryptoKeyOKP::NamedCurve& result) + { + uint8_t nameTag; + if (!read(nameTag)) + return false; + if (nameTag > cryptoKeyOKPOpNameTagMaximumValue) + return false; + + switch (static_cast(nameTag)) { + case CryptoKeyOKPOpNameTag::X25519: + result = CryptoKeyOKP::NamedCurve::X25519; + break; + case CryptoKeyOKPOpNameTag::ED25519: + result = CryptoKeyOKP::NamedCurve::Ed25519; + break; + } + + return true; + } + + bool read(CryptoAlgorithmIdentifier& result) + { + uint8_t algorithmTag; + if (!read(algorithmTag)) + return false; + if (algorithmTag > cryptoAlgorithmIdentifierTagMaximumValue) + return false; + switch (static_cast(algorithmTag)) { + case CryptoAlgorithmIdentifierTag::RSAES_PKCS1_v1_5: + result = CryptoAlgorithmIdentifier::RSAES_PKCS1_v1_5; + break; + case CryptoAlgorithmIdentifierTag::RSASSA_PKCS1_v1_5: + result = CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5; + break; + case CryptoAlgorithmIdentifierTag::RSA_PSS: + result = CryptoAlgorithmIdentifier::RSA_PSS; + break; + case CryptoAlgorithmIdentifierTag::RSA_OAEP: + result = CryptoAlgorithmIdentifier::RSA_OAEP; + break; + case CryptoAlgorithmIdentifierTag::ECDSA: + result = CryptoAlgorithmIdentifier::ECDSA; + break; + case CryptoAlgorithmIdentifierTag::ECDH: + result = CryptoAlgorithmIdentifier::ECDH; + break; + case CryptoAlgorithmIdentifierTag::AES_CTR: + result = CryptoAlgorithmIdentifier::AES_CTR; + break; + case CryptoAlgorithmIdentifierTag::AES_CBC: + result = CryptoAlgorithmIdentifier::AES_CBC; + break; + case CryptoAlgorithmIdentifierTag::AES_GCM: + result = CryptoAlgorithmIdentifier::AES_GCM; + break; + case CryptoAlgorithmIdentifierTag::AES_CFB: + result = CryptoAlgorithmIdentifier::AES_CFB; + break; + case CryptoAlgorithmIdentifierTag::AES_KW: + result = CryptoAlgorithmIdentifier::AES_KW; + break; + case CryptoAlgorithmIdentifierTag::HMAC: + result = CryptoAlgorithmIdentifier::HMAC; + break; + case CryptoAlgorithmIdentifierTag::SHA_1: + result = CryptoAlgorithmIdentifier::SHA_1; + break; + case CryptoAlgorithmIdentifierTag::SHA_224: + result = CryptoAlgorithmIdentifier::SHA_224; + break; + case CryptoAlgorithmIdentifierTag::SHA_256: + result = CryptoAlgorithmIdentifier::SHA_256; + break; + case CryptoAlgorithmIdentifierTag::SHA_384: + result = CryptoAlgorithmIdentifier::SHA_384; + break; + case CryptoAlgorithmIdentifierTag::SHA_512: + result = CryptoAlgorithmIdentifier::SHA_512; + break; + case CryptoAlgorithmIdentifierTag::HKDF: + result = CryptoAlgorithmIdentifier::HKDF; + break; + case CryptoAlgorithmIdentifierTag::PBKDF2: + result = CryptoAlgorithmIdentifier::PBKDF2; + break; + case CryptoAlgorithmIdentifierTag::ED25519: + result = CryptoAlgorithmIdentifier::Ed25519; + break; + } + return true; + } + + bool read(CryptoKeyClassSubtag& result) + { + uint8_t tag; + if (!read(tag)) + return false; + if (tag > cryptoKeyClassSubtagMaximumValue) + return false; + result = static_cast(tag); + return true; + } + + bool read(CryptoKeyUsageTag& result) + { + uint8_t tag; + if (!read(tag)) + return false; + if (tag > cryptoKeyUsageTagMaximumValue) + return false; + result = static_cast(tag); + return true; + } + + bool read(CryptoKeyAsymmetricTypeSubtag& result) + { + uint8_t tag; + if (!read(tag)) + return false; + if (tag > cryptoKeyAsymmetricTypeSubtagMaximumValue) + return false; + result = static_cast(tag); + return true; + } + + bool readHMACKey(bool extractable, CryptoKeyUsageBitmap usages, RefPtr& result) + { + Vector keyData; + if (!read(keyData)) + return false; + CryptoAlgorithmIdentifier hash; + if (!read(hash)) + return false; + result = CryptoKeyHMAC::importRaw(0, hash, WTFMove(keyData), extractable, usages); + return true; + } + + bool readAESKey(bool extractable, CryptoKeyUsageBitmap usages, RefPtr& result) + { + CryptoAlgorithmIdentifier algorithm; + if (!read(algorithm)) + return false; + if (!CryptoKeyAES::isValidAESAlgorithm(algorithm)) + return false; + Vector keyData; + if (!read(keyData)) + return false; + result = CryptoKeyAES::importRaw(algorithm, WTFMove(keyData), extractable, usages); + return true; + } + + bool readRSAKey(bool extractable, CryptoKeyUsageBitmap usages, RefPtr& result) + { + CryptoAlgorithmIdentifier algorithm; + if (!read(algorithm)) + return false; + + int32_t isRestrictedToHash; + CryptoAlgorithmIdentifier hash = CryptoAlgorithmIdentifier::SHA_1; + if (!read(isRestrictedToHash)) + return false; + if (isRestrictedToHash && !read(hash)) + return false; + + CryptoKeyAsymmetricTypeSubtag type; + if (!read(type)) + return false; + + Vector modulus; + if (!read(modulus)) + return false; + Vector exponent; + if (!read(exponent)) + return false; + + if (type == CryptoKeyAsymmetricTypeSubtag::Public) { + auto keyData = CryptoKeyRSAComponents::createPublic(modulus, exponent); + auto key = CryptoKeyRSA::create(algorithm, hash, isRestrictedToHash, *keyData, extractable, usages); + result = WTFMove(key); + return true; + } + + Vector privateExponent; + if (!read(privateExponent)) + return false; + + uint32_t primeCount; + if (!read(primeCount)) + return false; + + if (!primeCount) { + auto keyData = CryptoKeyRSAComponents::createPrivate(modulus, exponent, privateExponent); + auto key = CryptoKeyRSA::create(algorithm, hash, isRestrictedToHash, *keyData, extractable, usages); + result = WTFMove(key); + return true; + } + + if (primeCount < 2) + return false; + + CryptoKeyRSAComponents::PrimeInfo firstPrimeInfo; + CryptoKeyRSAComponents::PrimeInfo secondPrimeInfo; + Vector otherPrimeInfos(primeCount - 2); + + if (!read(firstPrimeInfo.primeFactor)) + return false; + if (!read(firstPrimeInfo.factorCRTExponent)) + return false; + if (!read(secondPrimeInfo.primeFactor)) + return false; + if (!read(secondPrimeInfo.factorCRTExponent)) + return false; + if (!read(secondPrimeInfo.factorCRTCoefficient)) + return false; + for (unsigned i = 2; i < primeCount; ++i) { + if (!read(otherPrimeInfos[i].primeFactor)) + return false; + if (!read(otherPrimeInfos[i].factorCRTExponent)) + return false; + if (!read(otherPrimeInfos[i].factorCRTCoefficient)) + return false; + } + + auto keyData = CryptoKeyRSAComponents::createPrivateWithAdditionalData(modulus, exponent, privateExponent, firstPrimeInfo, secondPrimeInfo, otherPrimeInfos); + auto key = CryptoKeyRSA::create(algorithm, hash, isRestrictedToHash, *keyData, extractable, usages); + result = WTFMove(key); + return true; + } + + bool readECKey(bool extractable, CryptoKeyUsageBitmap usages, RefPtr& result) + { + CryptoAlgorithmIdentifier algorithm; + if (!read(algorithm)) + return false; + if (!CryptoKeyEC::isValidECAlgorithm(algorithm)) + return false; + CachedStringRef curve; + if (!readStringData(curve)) + return false; + CryptoKeyAsymmetricTypeSubtag type; + if (!read(type)) + return false; + Vector keyData; + if (!read(keyData)) + return false; + + switch (type) { + case CryptoKeyAsymmetricTypeSubtag::Public: + result = CryptoKeyEC::importRaw(algorithm, curve->string(), WTFMove(keyData), extractable, usages); + break; + case CryptoKeyAsymmetricTypeSubtag::Private: + result = CryptoKeyEC::importPkcs8(algorithm, curve->string(), WTFMove(keyData), extractable, usages); + break; + } + + return true; + } + + bool readOKPKey(bool extractable, CryptoKeyUsageBitmap usages, RefPtr& result) + { + CryptoAlgorithmIdentifier algorithm; + if (!read(algorithm)) + return false; + if (!CryptoKeyOKP::isValidOKPAlgorithm(algorithm)) + return false; + CryptoKeyOKP::NamedCurve namedCurve; + if (!read(namedCurve)) + return false; + Vector keyData; + if (!read(keyData)) + return false; + + result = CryptoKeyOKP::importRaw(algorithm, namedCurve, WTFMove(keyData), extractable, usages); + return true; + } + + bool readRawKey(CryptoKeyUsageBitmap usages, RefPtr& result) + { + CryptoAlgorithmIdentifier algorithm; + if (!read(algorithm)) + return false; + Vector keyData; + if (!read(keyData)) + return false; + result = CryptoKeyRaw::create(algorithm, WTFMove(keyData), usages); + return true; + } + + bool readCryptoKey(JSValue& cryptoKey) + { + uint32_t keyFormatVersion; + if (!read(keyFormatVersion) || keyFormatVersion > currentKeyFormatVersion) + return false; + + int32_t extractable; + if (!read(extractable)) + return false; + + uint32_t usagesCount; + if (!read(usagesCount)) + return false; + + CryptoKeyUsageBitmap usages = 0; + for (uint32_t i = 0; i < usagesCount; ++i) { + CryptoKeyUsageTag usage; + if (!read(usage)) + return false; + switch (usage) { + case CryptoKeyUsageTag::Encrypt: + usages |= CryptoKeyUsageEncrypt; + break; + case CryptoKeyUsageTag::Decrypt: + usages |= CryptoKeyUsageDecrypt; + break; + case CryptoKeyUsageTag::Sign: + usages |= CryptoKeyUsageSign; + break; + case CryptoKeyUsageTag::Verify: + usages |= CryptoKeyUsageVerify; + break; + case CryptoKeyUsageTag::DeriveKey: + usages |= CryptoKeyUsageDeriveKey; + break; + case CryptoKeyUsageTag::DeriveBits: + usages |= CryptoKeyUsageDeriveBits; + break; + case CryptoKeyUsageTag::WrapKey: + usages |= CryptoKeyUsageWrapKey; + break; + case CryptoKeyUsageTag::UnwrapKey: + usages |= CryptoKeyUsageUnwrapKey; + break; + } + } + + CryptoKeyClassSubtag cryptoKeyClass; + if (!read(cryptoKeyClass)) + return false; + RefPtr result; + switch (cryptoKeyClass) { + case CryptoKeyClassSubtag::HMAC: + if (!readHMACKey(extractable, usages, result)) + return false; + break; + case CryptoKeyClassSubtag::AES: + if (!readAESKey(extractable, usages, result)) + return false; + break; + case CryptoKeyClassSubtag::RSA: + if (!readRSAKey(extractable, usages, result)) + return false; + break; + case CryptoKeyClassSubtag::EC: + if (!readECKey(extractable, usages, result)) + return false; + break; + case CryptoKeyClassSubtag::Raw: + if (!readRawKey(usages, result)) + return false; + break; + case CryptoKeyClassSubtag::OKP: + if (!readOKPKey(extractable, usages, result)) + return false; + break; + } + cryptoKey = getJSValue(result.get()); + return true; + } +#endif + + template + JSValue getJSValue(T&& nativeObj) + { + return toJS(m_lexicalGlobalObject, jsCast(m_globalObject), std::forward(nativeObj)); + } + + template + JSValue readDOMPoint() + { + double x; + if (!read(x)) + return { }; + double y; + if (!read(y)) + return { }; + double z; + if (!read(z)) + return { }; + double w; + if (!read(w)) + return { }; + + return toJSNewlyCreated(m_lexicalGlobalObject, jsCast(m_globalObject), T::create(x, y, z, w)); + } + + template + JSValue readDOMMatrix() + { + uint8_t is2D; + if (!read(is2D)) + return { }; + + if (is2D) { + double m11; + if (!read(m11)) + return { }; + double m12; + if (!read(m12)) + return { }; + double m21; + if (!read(m21)) + return { }; + double m22; + if (!read(m22)) + return { }; + double m41; + if (!read(m41)) + return { }; + double m42; + if (!read(m42)) + return { }; + + TransformationMatrix matrix(m11, m12, m21, m22, m41, m42); + return toJSNewlyCreated(m_lexicalGlobalObject, jsCast(m_globalObject), T::create(WTFMove(matrix), DOMMatrixReadOnly::Is2D::Yes)); + } else { + double m11; + if (!read(m11)) + return { }; + double m12; + if (!read(m12)) + return { }; + double m13; + if (!read(m13)) + return { }; + double m14; + if (!read(m14)) + return { }; + double m21; + if (!read(m21)) + return { }; + double m22; + if (!read(m22)) + return { }; + double m23; + if (!read(m23)) + return { }; + double m24; + if (!read(m24)) + return { }; + double m31; + if (!read(m31)) + return { }; + double m32; + if (!read(m32)) + return { }; + double m33; + if (!read(m33)) + return { }; + double m34; + if (!read(m34)) + return { }; + double m41; + if (!read(m41)) + return { }; + double m42; + if (!read(m42)) + return { }; + double m43; + if (!read(m43)) + return { }; + double m44; + if (!read(m44)) + return { }; + + TransformationMatrix matrix(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44); + return toJSNewlyCreated(m_lexicalGlobalObject, jsCast(m_globalObject), T::create(WTFMove(matrix), DOMMatrixReadOnly::Is2D::No)); + } + } + + template + JSValue readDOMRect() + { + double x; + if (!read(x)) + return { }; + double y; + if (!read(y)) + return { }; + double width; + if (!read(width)) + return { }; + double height; + if (!read(height)) + return { }; + + return toJSNewlyCreated(m_lexicalGlobalObject, jsCast(m_globalObject), T::create(x, y, width, height)); + } + + std::optional readDOMPointInit() + { + DOMPointInit point; + if (!read(point.x)) + return std::nullopt; + if (!read(point.y)) + return std::nullopt; + if (!read(point.z)) + return std::nullopt; + if (!read(point.w)) + return std::nullopt; + + return point; + } + + JSValue readDOMQuad() + { + auto p1 = readDOMPointInit(); + if (!p1) + return JSValue(); + auto p2 = readDOMPointInit(); + if (!p2) + return JSValue(); + auto p3 = readDOMPointInit(); + if (!p3) + return JSValue(); + auto p4 = readDOMPointInit(); + if (!p4) + return JSValue(); + + return toJSNewlyCreated(m_lexicalGlobalObject, jsCast(m_globalObject), DOMQuad::create(p1.value(), p2.value(), p3.value(), p4.value())); + } + + JSValue readTransferredImageBitmap() + { + uint32_t index; + bool indexSuccessfullyRead = read(index); + if (!indexSuccessfullyRead || index >= m_backingStores.size()) { + fail(); + return JSValue(); + } + + if (!m_imageBitmaps[index]) { + m_backingStores.at(index)->connect(*executionContext(m_lexicalGlobalObject)); + m_imageBitmaps[index] = ImageBitmap::create(WTFMove(m_backingStores.at(index))); + } + + auto bitmap = m_imageBitmaps[index].get(); + return getJSValue(bitmap); + } + +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + JSValue readOffscreenCanvas() + { + uint32_t index; + bool indexSuccessfullyRead = read(index); + if (!indexSuccessfullyRead || index >= m_detachedOffscreenCanvases.size()) { + fail(); + return JSValue(); + } + + if (!m_offscreenCanvases[index]) + m_offscreenCanvases[index] = OffscreenCanvas::create(*executionContext(m_lexicalGlobalObject), WTFMove(m_detachedOffscreenCanvases.at(index))); + + auto offscreenCanvas = m_offscreenCanvases[index].get(); + return getJSValue(offscreenCanvas); + } +#endif + +#if ENABLE(WEB_RTC) + JSValue readRTCCertificate() + { + double expires; + if (!read(expires)) { + fail(); + return JSValue(); + } + CachedStringRef certificate; + if (!readStringData(certificate)) { + fail(); + return JSValue(); + } + CachedStringRef origin; + if (!readStringData(origin)) { + fail(); + return JSValue(); + } + CachedStringRef keyedMaterial; + if (!readStringData(keyedMaterial)) { + fail(); + return JSValue(); + } + unsigned size = 0; + if (!read(size)) + return JSValue(); + + Vector fingerprints; + fingerprints.reserveInitialCapacity(size); + for (unsigned i = 0; i < size; i++) { + CachedStringRef algorithm; + if (!readStringData(algorithm)) + return JSValue(); + CachedStringRef value; + if (!readStringData(value)) + return JSValue(); + fingerprints.uncheckedAppend(RTCCertificate::DtlsFingerprint { algorithm->string(), value->string() }); + } + + if (!m_canCreateDOMObject) + return constructEmptyObject(m_lexicalGlobalObject, m_globalObject->objectPrototype()); + + auto rtcCertificate = RTCCertificate::create(SecurityOrigin::createFromString(origin->string()), expires, WTFMove(fingerprints), certificate->takeString(), keyedMaterial->takeString()); + return toJSNewlyCreated(m_lexicalGlobalObject, jsCast(m_globalObject), WTFMove(rtcCertificate)); + } + + JSValue readRTCDataChannel() + { + uint32_t index; + bool indexSuccessfullyRead = read(index); + if (!indexSuccessfullyRead || index >= m_detachedRTCDataChannels.size()) { + fail(); + return JSValue(); + } + + if (!m_rtcDataChannels[index]) { + auto detachedChannel = WTFMove(m_detachedRTCDataChannels.at(index)); + m_rtcDataChannels[index] = RTCDataChannel::create(*executionContext(m_lexicalGlobalObject), detachedChannel->identifier, WTFMove(detachedChannel->label), WTFMove(detachedChannel->options), detachedChannel->state); + } + + return getJSValue(m_rtcDataChannels[index].get()); + } +#endif + +#if ENABLE(WEB_CODECS) + JSValue readWebCodecsEncodedVideoChunk() + { + uint32_t index; + bool indexSuccessfullyRead = read(index); + if (!indexSuccessfullyRead || index >= m_serializedVideoChunks.size()) { + fail(); + return JSValue(); + } + + if (!m_videoChunks[index]) + m_videoChunks[index] = WebCodecsEncodedVideoChunk::create(m_serializedVideoChunks.at(index).releaseNonNull()); + + return getJSValue(m_videoChunks[index].get()); + } + JSValue readWebCodecsVideoFrame() + { + uint32_t index; + bool indexSuccessfullyRead = read(index); + if (!indexSuccessfullyRead || index >= m_serializedVideoFrames.size()) { + fail(); + return JSValue(); + } + + if (!m_videoFrames[index]) + m_videoFrames[index] = WebCodecsVideoFrame::create(*executionContext(m_lexicalGlobalObject), WTFMove(m_serializedVideoFrames.at(index))); + + return getJSValue(m_videoFrames[index].get()); + } +#endif + + JSValue readImageBitmap() + { + uint8_t serializationState; + int32_t logicalWidth; + int32_t logicalHeight; + double resolutionScale; + auto colorSpace = DestinationColorSpace::SRGB(); + RefPtr arrayBuffer; + + if (!read(serializationState) || !read(logicalWidth) || !read(logicalHeight) || !read(resolutionScale) || (m_version > 8 && !read(colorSpace)) || !readArrayBufferImpl(arrayBuffer)) { + fail(); + return JSValue(); + } + + auto logicalSize = IntSize(logicalWidth, logicalHeight); + auto imageDataSize = logicalSize; + imageDataSize.scale(resolutionScale); + + auto buffer = ImageBitmap::createImageBuffer(*executionContext(m_lexicalGlobalObject), logicalSize, RenderingMode::Unaccelerated, colorSpace, resolutionScale); + if (!buffer) { + fail(); + return JSValue(); + } + + PixelBufferFormat format { AlphaPremultiplication::Premultiplied, PixelFormat::RGBA8, colorSpace }; + auto pixelBuffer = ByteArrayPixelBuffer::tryCreate(format, imageDataSize, arrayBuffer.releaseNonNull()); + if (!pixelBuffer) { + fail(); + return JSValue(); + } + + buffer->putPixelBuffer(*pixelBuffer, { IntPoint::zero(), logicalSize }); + + auto bitmap = ImageBitmap::create(ImageBitmapBacking(WTFMove(buffer), OptionSet::fromRaw(serializationState))); + return getJSValue(bitmap); + } + + JSValue readDOMException() + { + CachedStringRef message; + if (!readStringData(message)) + return JSValue(); + CachedStringRef name; + if (!readStringData(name)) + return JSValue(); + auto exception = DOMException::create(message->string(), name->string()); + return getJSValue(exception); + } + + JSValue readBigInt() + { + uint8_t sign = 0; + if (!read(sign)) + return JSValue(); + uint32_t lengthInUint64 = 0; + if (!read(lengthInUint64)) + return JSValue(); + + if (!lengthInUint64) { +#if USE(BIGINT32) + return jsBigInt32(0); +#else + JSBigInt* bigInt = JSBigInt::tryCreateZero(m_lexicalGlobalObject->vm()); + if (UNLIKELY(!bigInt)) { + fail(); + return JSValue(); + } + m_gcBuffer.appendWithCrashOnOverflow(bigInt); + return bigInt; +#endif + } + +#if USE(BIGINT32) + static_assert(sizeof(JSBigInt::Digit) == sizeof(uint64_t)); + if (lengthInUint64 == 1) { + uint64_t digit64 = 0; + if (!read(digit64)) + return JSValue(); + if (sign) { + if (digit64 <= static_cast(-static_cast(INT32_MIN))) + return jsBigInt32(static_cast(-static_cast(digit64))); + } else { + if (digit64 <= INT32_MAX) + return jsBigInt32(static_cast(digit64)); + } + ASSERT(digit64 != 0); + JSBigInt* bigInt = JSBigInt::tryCreateWithLength(m_lexicalGlobalObject->vm(), 1); + if (!bigInt) { + fail(); + return JSValue(); + } + bigInt->setDigit(0, digit64); + bigInt->setSign(sign); + bigInt = bigInt->tryRightTrim(m_lexicalGlobalObject->vm()); + if (!bigInt) { + fail(); + return JSValue(); + } + m_gcBuffer.appendWithCrashOnOverflow(bigInt); + return tryConvertToBigInt32(bigInt); + } +#endif + JSBigInt* bigInt = nullptr; + if constexpr (sizeof(JSBigInt::Digit) == sizeof(uint64_t)) { + bigInt = JSBigInt::tryCreateWithLength(m_lexicalGlobalObject->vm(), lengthInUint64); + if (!bigInt) { + fail(); + return JSValue(); + } + for (uint32_t index = 0; index < lengthInUint64; ++index) { + uint64_t digit64 = 0; + if (!read(digit64)) + return JSValue(); + bigInt->setDigit(index, digit64); + } + } else { + ASSERT(sizeof(JSBigInt::Digit) == sizeof(uint32_t)); + bigInt = JSBigInt::tryCreateWithLength(m_lexicalGlobalObject->vm(), lengthInUint64 * 2); + if (!bigInt) { + fail(); + return JSValue(); + } + for (uint32_t index = 0; index < lengthInUint64; ++index) { + uint64_t digit64 = 0; + if (!read(digit64)) + return JSValue(); + bigInt->setDigit(index * 2, static_cast(digit64)); + bigInt->setDigit(index * 2 + 1, static_cast(digit64 >> 32)); + } + } + bigInt->setSign(sign); + bigInt = bigInt->tryRightTrim(m_lexicalGlobalObject->vm()); + if (!bigInt) { + fail(); + return JSValue(); + } + m_gcBuffer.appendWithCrashOnOverflow(bigInt); + return tryConvertToBigInt32(bigInt); + } + + JSValue readTerminal() + { + SerializationTag tag = readTag(); + switch (tag) { + case UndefinedTag: + return jsUndefined(); + case NullTag: + return jsNull(); + case IntTag: { + int32_t i; + if (!read(i)) + return JSValue(); + return jsNumber(i); + } + case ZeroTag: + return jsNumber(0); + case OneTag: + return jsNumber(1); + case FalseTag: + return jsBoolean(false); + case TrueTag: + return jsBoolean(true); + case FalseObjectTag: { + BooleanObject* obj = BooleanObject::create(m_lexicalGlobalObject->vm(), m_globalObject->booleanObjectStructure()); + obj->setInternalValue(m_lexicalGlobalObject->vm(), jsBoolean(false)); + m_gcBuffer.appendWithCrashOnOverflow(obj); + return obj; + } + case TrueObjectTag: { + BooleanObject* obj = BooleanObject::create(m_lexicalGlobalObject->vm(), m_globalObject->booleanObjectStructure()); + obj->setInternalValue(m_lexicalGlobalObject->vm(), jsBoolean(true)); + m_gcBuffer.appendWithCrashOnOverflow(obj); + return obj; + } + case DoubleTag: { + double d; + if (!read(d)) + return JSValue(); + return jsNumber(purifyNaN(d)); + } + case BigIntTag: + return readBigInt(); + case NumberObjectTag: { + double d; + if (!read(d)) + return JSValue(); + NumberObject* obj = constructNumber(m_globalObject, jsNumber(purifyNaN(d))); + m_gcBuffer.appendWithCrashOnOverflow(obj); + return obj; + } + case BigIntObjectTag: { + JSValue bigInt = readBigInt(); + if (!bigInt) + return JSValue(); + ASSERT(bigInt.isBigInt()); + BigIntObject* obj = BigIntObject::create(m_lexicalGlobalObject->vm(), m_globalObject, bigInt); + m_gcBuffer.appendWithCrashOnOverflow(obj); + return obj; + } + case DateTag: { + double d; + if (!read(d)) + return JSValue(); + return DateInstance::create(m_lexicalGlobalObject->vm(), m_globalObject->dateStructure(), d); + } + case FileTag: { + RefPtr file; + if (!readFile(file)) + return JSValue(); + if (!m_canCreateDOMObject) + return jsNull(); + return toJS(m_lexicalGlobalObject, jsCast(m_globalObject), file.get()); + } + case FileListTag: { + unsigned length = 0; + if (!read(length)) + return JSValue(); + ASSERT(m_globalObject->inherits()); + Vector> files; + for (unsigned i = 0; i < length; i++) { + RefPtr file; + if (!readFile(file)) + return JSValue(); + if (m_canCreateDOMObject) + files.append(file.releaseNonNull()); + } + if (!m_canCreateDOMObject) + return jsNull(); + return getJSValue(FileList::create(WTFMove(files)).get()); + } + case ImageDataTag: { + uint32_t width; + if (!read(width)) + return JSValue(); + uint32_t height; + if (!read(height)) + return JSValue(); + uint32_t length; + if (!read(length)) + return JSValue(); + if (static_cast(m_end - m_ptr) < length) { + fail(); + return JSValue(); + } + auto bufferStart = m_ptr; + m_ptr += length; + + auto resultColorSpace = PredefinedColorSpace::SRGB; + if (m_version > 7) { + if (!read(resultColorSpace)) + return JSValue(); + } + + if (length && (IntSize(width, height).area() * 4) != length) { + fail(); + return JSValue(); + } + + if (!m_isDOMGlobalObject) + return jsNull(); + + auto result = ImageData::createUninitialized(width, height, resultColorSpace); + if (result.hasException()) { + fail(); + return JSValue(); + } + if (length) + memcpy(result.returnValue()->data().data(), bufferStart, length); + else + result.returnValue()->data().zeroFill(); + return getJSValue(result.releaseReturnValue()); + } + case BlobTag: { + CachedStringRef url; + if (!readStringData(url)) + return JSValue(); + CachedStringRef type; + if (!readStringData(type)) + return JSValue(); + uint64_t size = 0; + if (!read(size)) + return JSValue(); + uint64_t memoryCost = 0; + if (m_version >= 11 && !read(memoryCost)) + return JSValue(); + if (!m_canCreateDOMObject) + return jsNull(); + return getJSValue(Blob::deserialize(executionContext(m_lexicalGlobalObject), URL { url->string() }, type->string(), size, memoryCost, blobFilePathForBlobURL(url->string())).get()); + } + case StringTag: { + CachedStringRef cachedString; + if (!readStringData(cachedString)) + return JSValue(); + return cachedString->jsString(m_lexicalGlobalObject); + } + case EmptyStringTag: + return jsEmptyString(m_lexicalGlobalObject->vm()); + case StringObjectTag: { + CachedStringRef cachedString; + if (!readStringData(cachedString)) + return JSValue(); + StringObject* obj = constructString(m_lexicalGlobalObject->vm(), m_globalObject, cachedString->jsString(m_lexicalGlobalObject)); + m_gcBuffer.appendWithCrashOnOverflow(obj); + return obj; + } + case EmptyStringObjectTag: { + VM& vm = m_lexicalGlobalObject->vm(); + StringObject* obj = constructString(vm, m_globalObject, jsEmptyString(vm)); + m_gcBuffer.appendWithCrashOnOverflow(obj); + return obj; + } + case RegExpTag: { + CachedStringRef pattern; + if (!readStringData(pattern)) + return JSValue(); + CachedStringRef flags; + if (!readStringData(flags)) + return JSValue(); + auto reFlags = Yarr::parseFlags(flags->string()); + ASSERT(reFlags.has_value()); + VM& vm = m_lexicalGlobalObject->vm(); + RegExp* regExp = RegExp::create(vm, pattern->string(), reFlags.value()); + return RegExpObject::create(vm, m_globalObject->regExpStructure(), regExp); + } + case ObjectReferenceTag: { + unsigned index = 0; + if (!readConstantPoolIndex(m_gcBuffer, index)) { + fail(); + return JSValue(); + } + return m_gcBuffer.at(index); + } + case MessagePortReferenceTag: { + uint32_t index; + bool indexSuccessfullyRead = read(index); + if (!indexSuccessfullyRead || index >= m_messagePorts.size()) { + fail(); + return JSValue(); + } + return getJSValue(m_messagePorts[index].get()); + } +#if ENABLE(WEBASSEMBLY) + case WasmModuleTag: { + if (m_version >= 12) { + // https://webassembly.github.io/spec/web-api/index.html#serialization + CachedStringRef agentClusterID; + bool agentClusterIDSuccessfullyRead = readStringData(agentClusterID); + if (!agentClusterIDSuccessfullyRead || agentClusterID->string() != agentClusterIDFromGlobalObject(*m_globalObject)) { + fail(); + return JSValue(); + } + } + uint32_t index; + bool indexSuccessfullyRead = read(index); + if (!indexSuccessfullyRead || !m_wasmModules || index >= m_wasmModules->size()) { + fail(); + return JSValue(); + } + auto scope = DECLARE_THROW_SCOPE(m_lexicalGlobalObject->vm()); + JSValue result = JSC::JSWebAssemblyModule::createStub(m_lexicalGlobalObject->vm(), m_lexicalGlobalObject, m_globalObject->webAssemblyModuleStructure(), m_wasmModules->at(index)); + // Since we are cloning a JSWebAssemblyModule, it's impossible for that + // module to not have been a valid module. Therefore, createStub should + // not throw. + scope.releaseAssertNoException(); + m_gcBuffer.appendWithCrashOnOverflow(result); + return result; + } + case WasmMemoryTag: { + if (m_version >= 12) { + CachedStringRef agentClusterID; + bool agentClusterIDSuccessfullyRead = readStringData(agentClusterID); + if (!agentClusterIDSuccessfullyRead || agentClusterID->string() != agentClusterIDFromGlobalObject(*m_globalObject)) { + fail(); + return JSValue(); + } + } + uint32_t index; + bool indexSuccessfullyRead = read(index); + if (!indexSuccessfullyRead || !m_wasmMemoryHandles || index >= m_wasmMemoryHandles->size() || !JSC::Options::useSharedArrayBuffer()) { + fail(); + return JSValue(); + } + + auto& vm = m_lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + JSWebAssemblyMemory* result = JSC::JSWebAssemblyMemory::tryCreate(m_lexicalGlobalObject, vm, m_globalObject->webAssemblyMemoryStructure()); + // Since we are cloning a JSWebAssemblyMemory, it's impossible for that + // module to not have been a valid module. Therefore, createStub should + // not throw. + scope.releaseAssertNoException(); + + RefPtr memory; + auto handler = [&vm, result] (Wasm::Memory::GrowSuccess, PageCount oldPageCount, PageCount newPageCount) { result->growSuccessCallback(vm, oldPageCount, newPageCount); }; + if (RefPtr contents = m_wasmMemoryHandles->at(index)) { + if (!contents->memoryHandle()) { + fail(); + return JSValue(); + } + memory = Wasm::Memory::create(contents.releaseNonNull(), WTFMove(handler)); + } else { + // zero size & max-size. + memory = Wasm::Memory::createZeroSized(JSC::MemorySharingMode::Shared, WTFMove(handler)); + } + + result->adopt(memory.releaseNonNull()); + m_gcBuffer.appendWithCrashOnOverflow(result); + return result; + } +#endif + case ArrayBufferTag: { + RefPtr arrayBuffer; + if (!readArrayBuffer(arrayBuffer)) { + fail(); + return JSValue(); + } + Structure* structure = m_globalObject->arrayBufferStructure(arrayBuffer->sharingMode()); + // A crazy RuntimeFlags mismatch could mean that we are not equipped to handle shared + // array buffers while the sender is. In that case, we would see a null structure here. + if (UNLIKELY(!structure)) { + fail(); + return JSValue(); + } + JSValue result = JSArrayBuffer::create(m_lexicalGlobalObject->vm(), structure, WTFMove(arrayBuffer)); + m_gcBuffer.appendWithCrashOnOverflow(result); + return result; + } + case ResizableArrayBufferTag: { + RefPtr arrayBuffer; + if (!readResizableNonSharedArrayBuffer(arrayBuffer)) { + fail(); + return JSValue(); + } + Structure* structure = m_globalObject->arrayBufferStructure(arrayBuffer->sharingMode()); + // A crazy RuntimeFlags mismatch could mean that we are not equipped to handle shared + // array buffers while the sender is. In that case, we would see a null structure here. + if (UNLIKELY(!structure)) { + fail(); + return JSValue(); + } + JSValue result = JSArrayBuffer::create(m_lexicalGlobalObject->vm(), structure, WTFMove(arrayBuffer)); + m_gcBuffer.appendWithCrashOnOverflow(result); + return result; + } + case ArrayBufferTransferTag: { + uint32_t index; + bool indexSuccessfullyRead = read(index); + if (!indexSuccessfullyRead || index >= m_arrayBuffers.size()) { + fail(); + return JSValue(); + } + + if (!m_arrayBuffers[index]) + m_arrayBuffers[index] = ArrayBuffer::create(WTFMove(m_arrayBufferContents->at(index))); + + return getJSValue(m_arrayBuffers[index].get()); + } + case SharedArrayBufferTag: { + // https://html.spec.whatwg.org/multipage/structured-data.html#structureddeserialize + uint32_t index = UINT_MAX; + bool indexSuccessfullyRead = read(index); + if (!indexSuccessfullyRead || !m_sharedBuffers || index >= m_sharedBuffers->size() || !JSC::Options::useSharedArrayBuffer()) { + fail(); + return JSValue(); + } + + RELEASE_ASSERT(m_sharedBuffers->at(index)); + auto buffer = ArrayBuffer::create(WTFMove(m_sharedBuffers->at(index))); + JSValue result = getJSValue(buffer.get()); + m_gcBuffer.appendWithCrashOnOverflow(result); + return result; + } + case ArrayBufferViewTag: { + JSValue arrayBufferView; + if (!readArrayBufferView(m_lexicalGlobalObject->vm(), arrayBufferView)) { + fail(); + return JSValue(); + } + m_gcBuffer.appendWithCrashOnOverflow(arrayBufferView); + return arrayBufferView; + } +#if ENABLE(WEB_CRYPTO) + case CryptoKeyTag: { + Vector wrappedKey; + if (!read(wrappedKey)) { + fail(); + return JSValue(); + } + Vector serializedKey; + if (!unwrapCryptoKey(m_lexicalGlobalObject, wrappedKey, serializedKey)) { + fail(); + return JSValue(); + } + JSValue cryptoKey; + Vector> dummyMessagePorts; + CloneDeserializer rawKeyDeserializer(m_lexicalGlobalObject, m_globalObject, dummyMessagePorts, nullptr, { }, serializedKey); + if (!rawKeyDeserializer.readCryptoKey(cryptoKey)) { + fail(); + return JSValue(); + } + m_gcBuffer.appendWithCrashOnOverflow(cryptoKey); + return cryptoKey; + } +#endif + case DOMPointReadOnlyTag: + return readDOMPoint(); + case DOMPointTag: + return readDOMPoint(); + case DOMRectReadOnlyTag: + return readDOMRect(); + case DOMRectTag: + return readDOMRect(); + case DOMMatrixReadOnlyTag: + return readDOMMatrix(); + case DOMMatrixTag: + return readDOMMatrix(); + case DOMQuadTag: + return readDOMQuad(); + case ImageBitmapTransferTag: + return readTransferredImageBitmap(); +#if ENABLE(WEB_RTC) + case RTCCertificateTag: + return readRTCCertificate(); + +#endif + case ImageBitmapTag: + return readImageBitmap(); +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + case OffscreenCanvasTransferTag: + return readOffscreenCanvas(); +#endif +#if ENABLE(WEB_RTC) + case RTCDataChannelTransferTag: + return readRTCDataChannel(); +#endif +#if ENABLE(WEB_CODECS) + case WebCodecsEncodedVideoChunkTag: + return readWebCodecsEncodedVideoChunk(); + case WebCodecsVideoFrameTag: + return readWebCodecsVideoFrame(); +#endif + case DOMExceptionTag: + return readDOMException(); + + default: + m_ptr--; // Push the tag back + return JSValue(); + } + } + + template + bool consumeCollectionDataTerminationIfPossible() + { + if (readTag() == Tag) + return true; + m_ptr--; + return false; + } + + JSGlobalObject* const m_globalObject; + const bool m_isDOMGlobalObject; + const bool m_canCreateDOMObject; + const uint8_t* m_ptr; + const uint8_t* const m_end; + unsigned m_version; + Vector m_constantPool; + const Vector>& m_messagePorts; + ArrayBufferContentsArray* m_arrayBufferContents; + Vector> m_arrayBuffers; + Vector m_blobURLs; + Vector m_blobFilePaths; + ArrayBufferContentsArray* m_sharedBuffers; + Vector> m_backingStores; + Vector> m_imageBitmaps; +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + Vector> m_detachedOffscreenCanvases; + Vector> m_offscreenCanvases; +#endif +#if ENABLE(WEB_RTC) + Vector> m_detachedRTCDataChannels; + Vector> m_rtcDataChannels; +#endif +#if ENABLE(WEBASSEMBLY) + WasmModuleArray* const m_wasmModules; + WasmMemoryHandleArray* const m_wasmMemoryHandles; +#endif +#if ENABLE(WEB_CODECS) + Vector> m_serializedVideoChunks; + Vector> m_videoChunks; + Vector m_serializedVideoFrames; + Vector> m_videoFrames; +#endif + + String blobFilePathForBlobURL(const String& blobURL) + { + size_t i = 0; + for (; i < m_blobURLs.size(); ++i) { + if (m_blobURLs[i] == blobURL) + break; + } + + return i < m_blobURLs.size() ? m_blobFilePaths[i] : String(); + } +}; + +DeserializationResult CloneDeserializer::deserialize() +{ + VM& vm = m_lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + Vector indexStack; + Vector propertyNameStack; + MarkedVector outputObjectStack; + MarkedVector mapKeyStack; + MarkedVector mapStack; + MarkedVector setStack; + Vector stateStack; + WalkerState state = StateUnknown; + JSValue outValue; + + while (1) { + switch (state) { + arrayStartState: + case ArrayStartState: { + uint32_t length; + if (!read(length)) { + fail(); + goto error; + } + JSArray* outArray = constructEmptyArray(m_globalObject, static_cast(nullptr), length); + if (UNLIKELY(scope.exception())) + goto error; + m_gcBuffer.appendWithCrashOnOverflow(outArray); + outputObjectStack.append(outArray); + } + arrayStartVisitMember: + FALLTHROUGH; + case ArrayStartVisitMember: { + uint32_t index; + if (!read(index)) { + fail(); + goto error; + } + if (index == TerminatorTag) { + JSObject* outArray = outputObjectStack.last(); + outValue = outArray; + outputObjectStack.removeLast(); + break; + } else if (index == NonIndexPropertiesTag) { + goto objectStartVisitMember; + } + + if (JSValue terminal = readTerminal()) { + putProperty(outputObjectStack.last(), index, terminal); + goto arrayStartVisitMember; + } + if (m_failed) + goto error; + indexStack.append(index); + stateStack.append(ArrayEndVisitMember); + goto stateUnknown; + } + case ArrayEndVisitMember: { + JSObject* outArray = outputObjectStack.last(); + putProperty(outArray, indexStack.last(), outValue); + indexStack.removeLast(); + goto arrayStartVisitMember; + } + objectStartState: + case ObjectStartState: { + if (outputObjectStack.size() > maximumFilterRecursion) + return std::make_pair(JSValue(), SerializationReturnCode::StackOverflowError); + JSObject* outObject = constructEmptyObject(m_lexicalGlobalObject, m_globalObject->objectPrototype()); + m_gcBuffer.appendWithCrashOnOverflow(outObject); + outputObjectStack.append(outObject); + } + objectStartVisitMember: + FALLTHROUGH; + case ObjectStartVisitMember: { + CachedStringRef cachedString; + bool wasTerminator = false; + if (!readStringData(cachedString, wasTerminator)) { + if (!wasTerminator) + goto error; + + JSObject* outObject = outputObjectStack.last(); + outValue = outObject; + outputObjectStack.removeLast(); + break; + } + + if (JSValue terminal = readTerminal()) { + putProperty(outputObjectStack.last(), Identifier::fromString(vm, cachedString->string()), terminal); + goto objectStartVisitMember; + } + stateStack.append(ObjectEndVisitMember); + propertyNameStack.append(Identifier::fromString(vm, cachedString->string())); + goto stateUnknown; + } + case ObjectEndVisitMember: { + putProperty(outputObjectStack.last(), propertyNameStack.last(), outValue); + propertyNameStack.removeLast(); + goto objectStartVisitMember; + } + mapObjectStartState: { + if (outputObjectStack.size() > maximumFilterRecursion) + return std::make_pair(JSValue(), SerializationReturnCode::StackOverflowError); + JSMap* map = JSMap::create(m_lexicalGlobalObject->vm(), m_globalObject->mapStructure()); + m_gcBuffer.appendWithCrashOnOverflow(map); + outputObjectStack.append(map); + mapStack.append(map); + goto mapDataStartVisitEntry; + } + mapDataStartVisitEntry: + case MapDataStartVisitEntry: { + if (consumeCollectionDataTerminationIfPossible()) { + mapStack.removeLast(); + goto objectStartVisitMember; + } + stateStack.append(MapDataEndVisitKey); + goto stateUnknown; + } + case MapDataEndVisitKey: { + mapKeyStack.append(outValue); + stateStack.append(MapDataEndVisitValue); + goto stateUnknown; + } + case MapDataEndVisitValue: { + mapStack.last()->set(m_lexicalGlobalObject, mapKeyStack.last(), outValue); + mapKeyStack.removeLast(); + goto mapDataStartVisitEntry; + } + + setObjectStartState: { + if (outputObjectStack.size() > maximumFilterRecursion) + return std::make_pair(JSValue(), SerializationReturnCode::StackOverflowError); + JSSet* set = JSSet::create(m_lexicalGlobalObject->vm(), m_globalObject->setStructure()); + m_gcBuffer.appendWithCrashOnOverflow(set); + outputObjectStack.append(set); + setStack.append(set); + goto setDataStartVisitEntry; + } + setDataStartVisitEntry: + case SetDataStartVisitEntry: { + if (consumeCollectionDataTerminationIfPossible()) { + setStack.removeLast(); + goto objectStartVisitMember; + } + stateStack.append(SetDataEndVisitKey); + goto stateUnknown; + } + case SetDataEndVisitKey: { + JSSet* set = setStack.last(); + set->add(m_lexicalGlobalObject, outValue); + goto setDataStartVisitEntry; + } + + stateUnknown: + case StateUnknown: + if (JSValue terminal = readTerminal()) { + outValue = terminal; + break; + } + SerializationTag tag = readTag(); + if (tag == ArrayTag) + goto arrayStartState; + if (tag == ObjectTag) + goto objectStartState; + if (tag == MapObjectTag) + goto mapObjectStartState; + if (tag == SetObjectTag) + goto setObjectStartState; + goto error; + } + if (stateStack.isEmpty()) + break; + + state = stateStack.last(); + stateStack.removeLast(); + } + ASSERT(outValue); + ASSERT(!m_failed); + return std::make_pair(outValue, SerializationReturnCode::SuccessfullyCompleted); +error: + fail(); + return std::make_pair(JSValue(), SerializationReturnCode::ValidationError); +} + +SerializedScriptValue::~SerializedScriptValue() = default; + +SerializedScriptValue::SerializedScriptValue(Vector&& buffer, std::unique_ptr&& arrayBufferContentsArray +#if ENABLE(WEB_RTC) + , Vector>&& detachedRTCDataChannels +#endif +#if ENABLE(WEB_CODECS) + , Vector>&& serializedVideoChunks + , Vector&& serializedVideoFrames +#endif + ) + : m_data(WTFMove(buffer)) + , m_arrayBufferContentsArray(WTFMove(arrayBufferContentsArray)) +#if ENABLE(WEB_RTC) + , m_detachedRTCDataChannels(WTFMove(detachedRTCDataChannels)) +#endif +#if ENABLE(WEB_CODECS) + , m_serializedVideoChunks(WTFMove(serializedVideoChunks)) + , m_serializedVideoFrames(WTFMove(serializedVideoFrames)) +#endif +{ + m_memoryCost = computeMemoryCost(); +} + +SerializedScriptValue::SerializedScriptValue(Vector&& buffer, Vector&& blobHandles, std::unique_ptr arrayBufferContentsArray, std::unique_ptr sharedBufferContentsArray, Vector>&& backingStores +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + , Vector>&& detachedOffscreenCanvases +#endif +#if ENABLE(WEB_RTC) + , Vector>&& detachedRTCDataChannels +#endif +#if ENABLE(WEBASSEMBLY) + , std::unique_ptr wasmModulesArray + , std::unique_ptr wasmMemoryHandlesArray +#endif +#if ENABLE(WEB_CODECS) + , Vector>&& serializedVideoChunks + , Vector&& serializedVideoFrames +#endif + ) + : m_data(WTFMove(buffer)) + , m_arrayBufferContentsArray(WTFMove(arrayBufferContentsArray)) + , m_sharedBufferContentsArray(WTFMove(sharedBufferContentsArray)) + , m_backingStores(WTFMove(backingStores)) +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + , m_detachedOffscreenCanvases(WTFMove(detachedOffscreenCanvases)) +#endif +#if ENABLE(WEB_RTC) + , m_detachedRTCDataChannels(WTFMove(detachedRTCDataChannels)) +#endif +#if ENABLE(WEBASSEMBLY) + , m_wasmModulesArray(WTFMove(wasmModulesArray)) + , m_wasmMemoryHandlesArray(WTFMove(wasmMemoryHandlesArray)) +#endif +#if ENABLE(WEB_CODECS) + , m_serializedVideoChunks(WTFMove(serializedVideoChunks)) + , m_serializedVideoFrames(WTFMove(serializedVideoFrames)) +#endif + , m_blobHandles(crossThreadCopy(WTFMove(blobHandles))) +{ + m_memoryCost = computeMemoryCost(); +} + +size_t SerializedScriptValue::computeMemoryCost() const +{ + size_t cost = m_data.size(); + + if (m_arrayBufferContentsArray) { + for (auto& content : *m_arrayBufferContentsArray) + cost += content.sizeInBytes(); + } + + if (m_sharedBufferContentsArray) { + for (auto& content : *m_sharedBufferContentsArray) + cost += content.sizeInBytes(); + } + + for (auto& backingStore : m_backingStores) { + if (auto buffer = backingStore ? backingStore->buffer() : nullptr) + cost += buffer->memoryCost(); + } + +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + for (auto& canvas : m_detachedOffscreenCanvases) { + if (canvas) + cost += canvas->memoryCost(); + } +#endif +#if ENABLE(WEB_RTC) + for (auto& channel : m_detachedRTCDataChannels) { + if (channel) + cost += channel->memoryCost(); + } +#endif +#if ENABLE(WEBASSEMBLY) + // We are not supporting WebAssembly Module memory estimation yet. + if (m_wasmMemoryHandlesArray) { + for (auto& content : *m_wasmMemoryHandlesArray) + cost += content->sizeInBytes(std::memory_order_relaxed); + } +#endif +#if ENABLE(WEB_CODECS) + for (auto& chunk : m_serializedVideoChunks) { + if (chunk) + cost += chunk->memoryCost(); + } + for (auto& frame : m_serializedVideoFrames) + cost += frame.memoryCost(); +#endif + + for (auto& handle : m_blobHandles) + cost += handle.url().string().sizeInBytes(); + + return cost; +} + +static ExceptionOr> transferArrayBuffers(VM& vm, const Vector>& arrayBuffers) +{ + if (arrayBuffers.isEmpty()) + return nullptr; + + auto contents = makeUnique(arrayBuffers.size()); + + HashSet visited; + for (size_t arrayBufferIndex = 0; arrayBufferIndex < arrayBuffers.size(); arrayBufferIndex++) { + if (visited.contains(arrayBuffers[arrayBufferIndex].get())) + continue; + visited.add(arrayBuffers[arrayBufferIndex].get()); + + bool result = arrayBuffers[arrayBufferIndex]->transferTo(vm, contents->at(arrayBufferIndex)); + if (!result) + return Exception { TypeError }; + } + + return contents; +} + +static void maybeThrowExceptionIfSerializationFailed(JSGlobalObject& lexicalGlobalObject, SerializationReturnCode code) +{ + auto& vm = lexicalGlobalObject.vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + switch (code) { + case SerializationReturnCode::SuccessfullyCompleted: + break; + case SerializationReturnCode::StackOverflowError: + throwException(&lexicalGlobalObject, scope, createStackOverflowError(&lexicalGlobalObject)); + break; + case SerializationReturnCode::ValidationError: + throwTypeError(&lexicalGlobalObject, scope, "Unable to deserialize data."_s); + break; + case SerializationReturnCode::DataCloneError: + throwDataCloneError(lexicalGlobalObject, scope); + break; + case SerializationReturnCode::ExistingExceptionError: + case SerializationReturnCode::UnspecifiedError: + break; + case SerializationReturnCode::InterruptedExecutionError: + ASSERT_NOT_REACHED(); + } +} + +static Exception exceptionForSerializationFailure(SerializationReturnCode code) +{ + ASSERT(code != SerializationReturnCode::SuccessfullyCompleted); + + switch (code) { + case SerializationReturnCode::StackOverflowError: + return Exception { StackOverflowError }; + case SerializationReturnCode::ValidationError: + return Exception { TypeError }; + case SerializationReturnCode::DataCloneError: + return Exception { DataCloneError }; + case SerializationReturnCode::ExistingExceptionError: + return Exception { ExistingExceptionError }; + case SerializationReturnCode::UnspecifiedError: + return Exception { TypeError }; + case SerializationReturnCode::SuccessfullyCompleted: + case SerializationReturnCode::InterruptedExecutionError: + ASSERT_NOT_REACHED(); + return Exception { TypeError }; + } + ASSERT_NOT_REACHED(); + return Exception { TypeError }; +} + +static bool containsDuplicates(const Vector>& imageBitmaps) +{ + HashSet visited; + for (auto& imageBitmap : imageBitmaps) { + if (!visited.add(imageBitmap.get())) + return true; + } + return false; +} + +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) +static bool canOffscreenCanvasesDetach(const Vector>& offscreenCanvases) +{ + HashSet visited; + for (auto& offscreenCanvas : offscreenCanvases) { + if (!offscreenCanvas->canDetach()) + return false; + // Check the return value of add, we should not encounter duplicates. + if (!visited.add(offscreenCanvas.get())) + return false; + } + return true; +} +#endif + +#if ENABLE(WEB_RTC) +static bool canDetachRTCDataChannels(const Vector>& channels) +{ + HashSet visited; + for (auto& channel : channels) { + if (!channel->canDetach()) + return false; + // Check the return value of add, we should not encounter duplicates. + if (!visited.add(channel.ptr())) + return false; + } + return true; +} +#endif + +RefPtr SerializedScriptValue::create(JSC::JSGlobalObject& globalObject, JSC::JSValue value, SerializationForStorage forStorage, SerializationErrorMode throwExceptions, SerializationContext serializationContext) +{ + Vector> dummyPorts; + auto result = create(globalObject, value, { }, dummyPorts, forStorage, throwExceptions, serializationContext); + if (result.hasException()) + return nullptr; + return result.releaseReturnValue(); +} + +ExceptionOr> SerializedScriptValue::create(JSGlobalObject& globalObject, JSValue value, Vector>&& transferList, Vector>& messagePorts, SerializationForStorage forStorage, SerializationContext serializationContext) +{ + return create(globalObject, value, WTFMove(transferList), messagePorts, forStorage, SerializationErrorMode::NonThrowing, serializationContext); +} + +ExceptionOr> SerializedScriptValue::create(JSGlobalObject& lexicalGlobalObject, JSValue value, Vector>&& transferList, Vector>& messagePorts, SerializationForStorage forStorage, SerializationErrorMode throwExceptions, SerializationContext context) +{ + VM& vm = lexicalGlobalObject.vm(); + Vector> arrayBuffers; + Vector> imageBitmaps; +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + Vector> offscreenCanvases; +#endif +#if ENABLE(WEB_RTC) + Vector> dataChannels; +#endif +#if ENABLE(WEB_CODECS) + Vector> transferredVideoFrames; +#endif + HashSet uniqueTransferables; + for (auto& transferable : transferList) { + if (!uniqueTransferables.add(transferable.get()).isNewEntry) + return Exception { DataCloneError, "Duplicate transferable for structured clone"_s }; + + if (auto arrayBuffer = toPossiblySharedArrayBuffer(vm, transferable.get())) { + if (arrayBuffer->isDetached() || arrayBuffer->isShared()) + return Exception { DataCloneError }; + if (arrayBuffer->isLocked()) { + auto scope = DECLARE_THROW_SCOPE(vm); + throwVMTypeError(&lexicalGlobalObject, scope, errorMessageForTransfer(arrayBuffer)); + return Exception { ExistingExceptionError }; + } + arrayBuffers.append(WTFMove(arrayBuffer)); + continue; + } + if (auto port = JSMessagePort::toWrapped(vm, transferable.get())) { + if (port->isDetached()) + return Exception { DataCloneError, "MessagePort is detached"_s }; + messagePorts.append(WTFMove(port)); + continue; + } + + if (auto imageBitmap = JSImageBitmap::toWrapped(vm, transferable.get())) { + if (imageBitmap->isDetached()) + return Exception { DataCloneError }; + if (!imageBitmap->originClean()) + return Exception { DataCloneError }; + + imageBitmaps.append(WTFMove(imageBitmap)); + continue; + } + +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + if (auto offscreenCanvas = JSOffscreenCanvas::toWrapped(vm, transferable.get())) { + offscreenCanvases.append(WTFMove(offscreenCanvas)); + continue; + } +#endif + +#if ENABLE(WEB_RTC) + if (auto channel = JSRTCDataChannel::toWrapped(vm, transferable.get())) { + dataChannels.append(*channel); + continue; + } +#endif + +#if ENABLE(WEB_CODECS) + if (auto videoFrame = JSWebCodecsVideoFrame::toWrapped(vm, transferable.get())) { + if (videoFrame->isDetached()) + return Exception { DataCloneError }; + transferredVideoFrames.append(*videoFrame); + continue; + } +#endif + return Exception { DataCloneError }; + } + + if (containsDuplicates(imageBitmaps)) + return Exception { DataCloneError }; +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + if (!canOffscreenCanvasesDetach(offscreenCanvases)) + return Exception { InvalidStateError }; +#endif +#if ENABLE(WEB_RTC) + if (!canDetachRTCDataChannels(dataChannels)) + return Exception { DataCloneError }; +#endif + + Vector buffer; + Vector blobHandles; +#if ENABLE(WEBASSEMBLY) + WasmModuleArray wasmModules; + WasmMemoryHandleArray wasmMemoryHandles; +#endif + std::unique_ptr sharedBuffers = makeUnique(); +#if ENABLE(WEB_CODECS) + Vector> serializedVideoChunks; + Vector> serializedVideoFrames; +#endif + auto code = CloneSerializer::serialize(&lexicalGlobalObject, value, messagePorts, arrayBuffers, imageBitmaps, +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + offscreenCanvases, +#endif +#if ENABLE(WEB_RTC) + dataChannels, +#endif +#if ENABLE(WEB_CODECS) + serializedVideoChunks, + serializedVideoFrames, +#endif +#if ENABLE(WEBASSEMBLY) + wasmModules, + wasmMemoryHandles, +#endif + blobHandles, buffer, context, *sharedBuffers, forStorage); + + if (throwExceptions == SerializationErrorMode::Throwing) + maybeThrowExceptionIfSerializationFailed(lexicalGlobalObject, code); + + if (code != SerializationReturnCode::SuccessfullyCompleted) + return exceptionForSerializationFailure(code); + + auto arrayBufferContentsArray = transferArrayBuffers(vm, arrayBuffers); + if (arrayBufferContentsArray.hasException()) + return arrayBufferContentsArray.releaseException(); + + auto backingStores = ImageBitmap::detachBitmaps(WTFMove(imageBitmaps)); + +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + Vector> detachedCanvases; + for (auto offscreenCanvas : offscreenCanvases) + detachedCanvases.append(offscreenCanvas->detach()); +#endif +#if ENABLE(WEB_RTC) + Vector> detachedRTCDataChannels; + for (auto& channel : dataChannels) + detachedRTCDataChannels.append(channel->detach()); +#endif + +#if ENABLE(WEB_CODECS) + auto serializedVideoFrameData = map(serializedVideoFrames, [](auto& frame) -> WebCodecsVideoFrameData { return frame->data(); }); +#endif +#if ENABLE(WEB_CODECS) + for (auto& videoFrame : transferredVideoFrames) + videoFrame->close(); +#endif + + return adoptRef(*new SerializedScriptValue(WTFMove(buffer), WTFMove(blobHandles), arrayBufferContentsArray.releaseReturnValue(), context == SerializationContext::WorkerPostMessage ? WTFMove(sharedBuffers) : nullptr, WTFMove(backingStores) +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + , WTFMove(detachedCanvases) +#endif +#if ENABLE(WEB_RTC) + , WTFMove(detachedRTCDataChannels) +#endif +#if ENABLE(WEBASSEMBLY) + , makeUnique(wasmModules) + , context == SerializationContext::WorkerPostMessage ? makeUnique(wasmMemoryHandles) : nullptr +#endif +#if ENABLE(WEB_CODECS) + , WTFMove(serializedVideoChunks) + , WTFMove(serializedVideoFrameData) +#endif + )); +} + +RefPtr SerializedScriptValue::create(StringView string) +{ + Vector buffer; + if (!CloneSerializer::serialize(string, buffer)) + return nullptr; + return adoptRef(*new SerializedScriptValue(WTFMove(buffer))); +} + +RefPtr SerializedScriptValue::create(JSContextRef originContext, JSValueRef apiValue, JSValueRef* exception) +{ + JSGlobalObject* lexicalGlobalObject = toJS(originContext); + VM& vm = lexicalGlobalObject->vm(); + JSLockHolder locker(vm); + auto scope = DECLARE_CATCH_SCOPE(vm); + + JSValue value = toJS(lexicalGlobalObject, apiValue); + auto serializedValue = SerializedScriptValue::create(*lexicalGlobalObject, value); + if (UNLIKELY(scope.exception())) { + if (exception) + *exception = toRef(lexicalGlobalObject, scope.exception()->value()); + scope.clearException(); + return nullptr; + } + ASSERT(serializedValue); + return serializedValue; +} + +String SerializedScriptValue::toString() const +{ + return CloneDeserializer::deserializeString(m_data); +} + +JSValue SerializedScriptValue::deserialize(JSGlobalObject& lexicalGlobalObject, JSGlobalObject* globalObject, SerializationErrorMode throwExceptions, bool* didFail) +{ + return deserialize(lexicalGlobalObject, globalObject, { }, throwExceptions, didFail); +} + +JSValue SerializedScriptValue::deserialize(JSGlobalObject& lexicalGlobalObject, JSGlobalObject* globalObject, const Vector>& messagePorts, SerializationErrorMode throwExceptions, bool* didFail) +{ + Vector dummyBlobs; + Vector dummyPaths; + return deserialize(lexicalGlobalObject, globalObject, messagePorts, dummyBlobs, dummyPaths, throwExceptions, didFail); +} + +JSValue SerializedScriptValue::deserialize(JSGlobalObject& lexicalGlobalObject, JSGlobalObject* globalObject, const Vector>& messagePorts, const Vector& blobURLs, const Vector& blobFilePaths, SerializationErrorMode throwExceptions, bool* didFail) +{ + DeserializationResult result = CloneDeserializer::deserialize(&lexicalGlobalObject, globalObject, messagePorts, WTFMove(m_backingStores) +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + , WTFMove(m_detachedOffscreenCanvases) +#endif +#if ENABLE(WEB_RTC) + , WTFMove(m_detachedRTCDataChannels) +#endif + , m_arrayBufferContentsArray.get(), m_data, blobURLs, blobFilePaths, m_sharedBufferContentsArray.get() +#if ENABLE(WEBASSEMBLY) + , m_wasmModulesArray.get() + , m_wasmMemoryHandlesArray.get() +#endif +#if ENABLE(WEB_CODECS) + , WTFMove(m_serializedVideoChunks) + , WTFMove(m_serializedVideoFrames) +#endif + ); + if (didFail) + *didFail = result.second != SerializationReturnCode::SuccessfullyCompleted; + if (throwExceptions == SerializationErrorMode::Throwing) + maybeThrowExceptionIfSerializationFailed(lexicalGlobalObject, result.second); + return result.first ? result.first : jsNull(); +} + +JSValueRef SerializedScriptValue::deserialize(JSContextRef destinationContext, JSValueRef* exception) +{ + JSGlobalObject* lexicalGlobalObject = toJS(destinationContext); + VM& vm = lexicalGlobalObject->vm(); + JSLockHolder locker(vm); + auto scope = DECLARE_CATCH_SCOPE(vm); + + JSValue value = deserialize(*lexicalGlobalObject, lexicalGlobalObject); + if (UNLIKELY(scope.exception())) { + if (exception) + *exception = toRef(lexicalGlobalObject, scope.exception()->value()); + scope.clearException(); + return nullptr; + } + ASSERT(value); + return toRef(lexicalGlobalObject, value); +} + +Ref SerializedScriptValue::nullValue() +{ + return adoptRef(*new SerializedScriptValue(Vector())); +} + +uint32_t SerializedScriptValue::wireFormatVersion() +{ + return CurrentVersion; +} + +Vector SerializedScriptValue::blobURLs() const +{ + return m_blobHandles.map([](auto& handle) { + return handle.url().string().isolatedCopy(); + }); +} + +void SerializedScriptValue::writeBlobsToDiskForIndexedDB(CompletionHandler&& completionHandler) +{ + ASSERT(isMainThread()); + ASSERT(hasBlobURLs()); + + blobRegistry().writeBlobsToTemporaryFilesForIndexedDB(blobURLs(), [completionHandler = WTFMove(completionHandler), this, protectedThis = Ref { *this }] (auto&& blobFilePaths) mutable { + ASSERT(isMainThread()); + + if (blobFilePaths.isEmpty()) { + // We should have successfully written blobs to temporary files. + // If we failed, then we can't successfully store this record. + completionHandler({ }); + return; + } + + ASSERT(m_blobHandles.size() == blobFilePaths.size()); + + completionHandler({ *this, blobURLs(), blobFilePaths }); + }); +} + +IDBValue SerializedScriptValue::writeBlobsToDiskForIndexedDBSynchronously() +{ + ASSERT(!isMainThread()); + + BinarySemaphore semaphore; + IDBValue value; + callOnMainThread([this, &semaphore, &value] { + writeBlobsToDiskForIndexedDB([&semaphore, &value](IDBValue&& result) { + ASSERT(isMainThread()); + value.setAsIsolatedCopy(result); + + semaphore.signal(); + }); + }); + semaphore.wait(); + + return value; +} + +} // namespace WebCore diff --git a/src/bun.js/bindings/webcore/SerializedScriptValue.h b/src/bun.js/bindings/webcore/SerializedScriptValue.h new file mode 100644 index 000000000000..3f86ae81dc0a --- /dev/null +++ b/src/bun.js/bindings/webcore/SerializedScriptValue.h @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2009, 2013, 2016 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#pragma once + +#include "Blob.h" +#include "DetachedRTCDataChannel.h" +#include "ExceptionOr.h" +#include +#include +#include +#include +#include +#include +#include + +#if ENABLE(WEB_CODECS) +#include "WebCodecsEncodedVideoChunk.h" +#include "WebCodecsVideoFrame.h" +#endif + +typedef const struct OpaqueJSContext* JSContextRef; +typedef const struct OpaqueJSValue* JSValueRef; + +#if ENABLE(WEBASSEMBLY) +namespace JSC { namespace Wasm { +class Module; +class MemoryHandle; +} } +#endif + +namespace WebCore { + +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) +class DetachedOffscreenCanvas; +#endif +class IDBValue; +class MessagePort; +class ImageBitmapBacking; +class FragmentedSharedBuffer; +enum class SerializationReturnCode; + +enum class SerializationErrorMode { NonThrowing, Throwing }; +enum class SerializationContext { Default, WorkerPostMessage, WindowPostMessage }; +enum class SerializationForStorage : bool { No, Yes }; + +using ArrayBufferContentsArray = Vector; +#if ENABLE(WEBASSEMBLY) +using WasmModuleArray = Vector>; +using WasmMemoryHandleArray = Vector>; +#endif + +DECLARE_ALLOCATOR_WITH_HEAP_IDENTIFIER(SerializedScriptValue); +class SerializedScriptValue : public ThreadSafeRefCounted { + WTF_MAKE_FAST_ALLOCATED_WITH_HEAP_IDENTIFIER(SerializedScriptValue); +public: + WEBCORE_EXPORT static ExceptionOr> create(JSC::JSGlobalObject&, JSC::JSValue, Vector>&& transfer, Vector>&, SerializationForStorage = SerializationForStorage::No, SerializationContext = SerializationContext::Default); + WEBCORE_EXPORT static RefPtr create(JSC::JSGlobalObject&, JSC::JSValue, SerializationForStorage = SerializationForStorage::No, SerializationErrorMode = SerializationErrorMode::Throwing, SerializationContext = SerializationContext::Default); + static RefPtr convert(JSC::JSGlobalObject& globalObject, JSC::JSValue value) { return create(globalObject, value, SerializationForStorage::Yes); } + + WEBCORE_EXPORT static RefPtr create(StringView); + + static Ref nullValue(); + + WEBCORE_EXPORT JSC::JSValue deserialize(JSC::JSGlobalObject&, JSC::JSGlobalObject*, SerializationErrorMode = SerializationErrorMode::Throwing, bool* didFail = nullptr); + WEBCORE_EXPORT JSC::JSValue deserialize(JSC::JSGlobalObject&, JSC::JSGlobalObject*, const Vector>&, SerializationErrorMode = SerializationErrorMode::Throwing, bool* didFail = nullptr); + JSC::JSValue deserialize(JSC::JSGlobalObject&, JSC::JSGlobalObject*, const Vector>&, const Vector& blobURLs, const Vector& blobFilePaths, SerializationErrorMode = SerializationErrorMode::Throwing, bool* didFail = nullptr); + + static uint32_t wireFormatVersion(); + + WEBCORE_EXPORT String toString() const; + + // API implementation helpers. These don't expose special behavior for ArrayBuffers or MessagePorts. + WEBCORE_EXPORT static RefPtr create(JSContextRef, JSValueRef, JSValueRef* exception); + WEBCORE_EXPORT JSValueRef deserialize(JSContextRef, JSValueRef* exception); + + bool hasBlobURLs() const { return !m_blobHandles.isEmpty(); } + + Vector blobURLs() const; + Vector blobHandles() const { return crossThreadCopy(m_blobHandles); } + void writeBlobsToDiskForIndexedDB(CompletionHandler&&); + IDBValue writeBlobsToDiskForIndexedDBSynchronously(); + static Ref createFromWireBytes(Vector&& data) + { + return adoptRef(*new SerializedScriptValue(WTFMove(data))); + } + const Vector& wireBytes() const { return m_data; } + + template void encode(Encoder&) const; + template static RefPtr decode(Decoder&); + + size_t memoryCost() const { return m_memoryCost; } + + WEBCORE_EXPORT ~SerializedScriptValue(); + +private: + static ExceptionOr> create(JSC::JSGlobalObject&, JSC::JSValue, Vector>&& transfer, Vector>&, SerializationForStorage, SerializationErrorMode, SerializationContext); + WEBCORE_EXPORT SerializedScriptValue(Vector&&, std::unique_ptr&& = nullptr +#if ENABLE(WEB_RTC) + , Vector>&& = { } +#endif +#if ENABLE(WEB_CODECS) + , Vector>&& = { } + , Vector&& = { } +#endif + ); + + SerializedScriptValue(Vector&&, Vector&& blobHandles, std::unique_ptr, std::unique_ptr sharedBuffers, Vector>&& backingStores +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + , Vector>&& = { } +#endif +#if ENABLE(WEB_RTC) + , Vector>&& = { } +#endif +#if ENABLE(WEBASSEMBLY) + , std::unique_ptr = nullptr + , std::unique_ptr = nullptr +#endif +#if ENABLE(WEB_CODECS) + , Vector>&& = { } + , Vector&& = { } +#endif + ); + + size_t computeMemoryCost() const; + + Vector m_data; + std::unique_ptr m_arrayBufferContentsArray; + std::unique_ptr m_sharedBufferContentsArray; + Vector> m_backingStores; +#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + Vector> m_detachedOffscreenCanvases; +#endif +#if ENABLE(WEB_RTC) + Vector> m_detachedRTCDataChannels; +#endif +#if ENABLE(WEBASSEMBLY) + std::unique_ptr m_wasmModulesArray; + std::unique_ptr m_wasmMemoryHandlesArray; +#endif +#if ENABLE(WEB_CODECS) + Vector> m_serializedVideoChunks; + Vector m_serializedVideoFrames; +#endif + Vector m_blobHandles; + size_t m_memoryCost { 0 }; +}; + +template +void SerializedScriptValue::encode(Encoder& encoder) const +{ + encoder << m_data; + + auto hasArray = m_arrayBufferContentsArray && m_arrayBufferContentsArray->size(); + encoder << hasArray; + + if (hasArray) { + encoder << static_cast(m_arrayBufferContentsArray->size()); + for (const auto& arrayBufferContents : *m_arrayBufferContentsArray) + encoder << std::span(reinterpret_cast(arrayBufferContents.data()), arrayBufferContents.sizeInBytes()); + } + +#if ENABLE(WEB_RTC) + encoder << static_cast(m_detachedRTCDataChannels.size()); + for (const auto &channel : m_detachedRTCDataChannels) + encoder << *channel; +#endif + +#if ENABLE(WEB_CODECS) + encoder << static_cast(m_serializedVideoChunks.size()); + for (const auto &videoChunk : m_serializedVideoChunks) + encoder << videoChunk->data(); + + // FIXME: encode video frames +#endif +} + +template +RefPtr SerializedScriptValue::decode(Decoder& decoder) +{ + Vector data; + if (!decoder.decode(data)) + return nullptr; + + bool hasArray; + if (!decoder.decode(hasArray)) + return nullptr; + + std::unique_ptr arrayBufferContentsArray; + if (hasArray) { + uint64_t arrayLength; + if (!decoder.decode(arrayLength)) + return nullptr; + ASSERT(arrayLength); + + arrayBufferContentsArray = makeUnique(); + while (arrayLength--) { + std::span data; + if (!decoder.decode(data)) + return nullptr; + + auto buffer = Gigacage::tryMalloc(Gigacage::Primitive, data.size_bytes()); + if (!buffer) + return nullptr; + + static_assert(sizeof(std::span::element_type) == 1); + memcpy(buffer, data.data(), data.size_bytes()); + JSC::ArrayBufferDestructorFunction destructor = ArrayBuffer::primitiveGigacageDestructor(); + arrayBufferContentsArray->append({ buffer, data.size_bytes(), std::nullopt, WTFMove(destructor) }); + } + } + +#if ENABLE(WEB_RTC) + uint64_t detachedRTCDataChannelsSize; + if (!decoder.decode(detachedRTCDataChannelsSize)) + return nullptr; + + Vector> detachedRTCDataChannels; + while (detachedRTCDataChannelsSize--) { + std::optional detachedRTCDataChannel; + decoder >> detachedRTCDataChannel; + if (!detachedRTCDataChannel) + return nullptr; + detachedRTCDataChannels.append(makeUnique(WTFMove(*detachedRTCDataChannel))); + } +#endif +#if ENABLE(WEB_CODECS) + uint64_t serializedVideoChunksSize; + if (!decoder.decode(serializedVideoChunksSize)) + return nullptr; + + Vector> serializedVideoChunks; + while (serializedVideoChunksSize--) { + std::optional videoChunkData; + decoder >> videoChunkData; + if (!videoChunkData) + return nullptr; + serializedVideoChunks.append(WebCodecsEncodedVideoChunkStorage::create(WTFMove(*videoChunkData))); + } + // FIXME: decode video frames + Vector serializedVideoFrames; +#endif + + return adoptRef(*new SerializedScriptValue(WTFMove(data), WTFMove(arrayBufferContentsArray) +#if ENABLE(WEB_RTC) + , WTFMove(detachedRTCDataChannels) +#endif +#if ENABLE(WEB_CODECS) + , WTFMove(serializedVideoChunks) +#endif +#if ENABLE(WEB_CODECS) + , WTFMove(serializedVideoFrames) +#endif + )); +} + + +} From e5c2cfa4373d26433b136a4a44dff858edec56f5 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Mon, 10 Jul 2023 13:32:00 -0700 Subject: [PATCH 02/14] format --- .../webcore/SerializedScriptValue.cpp | 710 +++++++++--------- .../bindings/webcore/SerializedScriptValue.h | 60 +- 2 files changed, 407 insertions(+), 363 deletions(-) diff --git a/src/bun.js/bindings/webcore/SerializedScriptValue.cpp b/src/bun.js/bindings/webcore/SerializedScriptValue.cpp index beadb3d0c339..d52b50326faf 100644 --- a/src/bun.js/bindings/webcore/SerializedScriptValue.cpp +++ b/src/bun.js/bindings/webcore/SerializedScriptValue.cpp @@ -139,10 +139,18 @@ enum class SerializationReturnCode { UnspecifiedError }; -enum WalkerState { StateUnknown, ArrayStartState, ArrayStartVisitMember, ArrayEndVisitMember, - ObjectStartState, ObjectStartVisitMember, ObjectEndVisitMember, - MapDataStartVisitEntry, MapDataEndVisitKey, MapDataEndVisitValue, - SetDataStartVisitEntry, SetDataEndVisitKey }; +enum WalkerState { StateUnknown, + ArrayStartState, + ArrayStartVisitMember, + ArrayEndVisitMember, + ObjectStartState, + ObjectStartVisitMember, + ObjectEndVisitMember, + MapDataStartVisitEntry, + MapDataEndVisitKey, + MapDataEndVisitValue, + SetDataStartVisitEntry, + SetDataEndVisitKey }; // These can't be reordered, and any new types must be added to the end of the list // When making changes to these lists please cover your new type(s) in the API test "IndexedDB.StructuredCloneBackwardCompatibility" @@ -260,7 +268,8 @@ static unsigned typedArrayElementSize(ArrayBufferViewSubtag tag) enum class PredefinedColorSpaceTag : uint8_t { SRGB = 0 #if ENABLE(PREDEFINED_COLOR_SPACE_DISPLAY_P3) - , DisplayP3 = 1 + , + DisplayP3 = 1 #endif }; @@ -361,7 +370,6 @@ enum class CryptoKeyOKPOpNameTag { }; const uint8_t cryptoKeyOKPOpNameTagMaximumValue = 1; - #endif /* CurrentVersion tracks the serialization version so that persistent stores @@ -575,6 +583,7 @@ using DeserializationResult = std::pair; class CloneBase { WTF_FORBID_HEAP_ALLOCATION; + protected: CloneBase(JSGlobalObject* lexicalGlobalObject) : m_lexicalGlobalObject(lexicalGlobalObject) @@ -607,12 +616,12 @@ static bool unwrapCryptoKey(JSGlobalObject* lexicalGlobalObject, const Vector static void writeLittleEndian(Vector& buffer, T value) +template static void writeLittleEndian(Vector& buffer, T value) { buffer.append(reinterpret_cast(&value), sizeof(value)); } #else -template static void writeLittleEndian(Vector& buffer, T value) +template static void writeLittleEndian(Vector& buffer, T value) { for (unsigned i = 0; i < sizeof(T); i++) { buffer.append(value & 0xFF); @@ -621,12 +630,12 @@ template static void writeLittleEndian(Vector& buffer, T v } #endif -template <> void writeLittleEndian(Vector& buffer, uint8_t value) +template<> void writeLittleEndian(Vector& buffer, uint8_t value) { buffer.append(value); } -template static bool writeLittleEndian(Vector& buffer, const T* values, uint32_t length) +template static bool writeLittleEndian(Vector& buffer, const T* values, uint32_t length) { if (length > std::numeric_limits::max() / sizeof(T)) return false; @@ -645,7 +654,7 @@ template static bool writeLittleEndian(Vector& buffer, con return true; } -template <> bool writeLittleEndian(Vector& buffer, const uint8_t* values, uint32_t length) +template<> bool writeLittleEndian(Vector& buffer, const uint8_t* values, uint32_t length) { buffer.append(values, length); return true; @@ -653,21 +662,22 @@ template <> bool writeLittleEndian(Vector& buffer, const uint8 class CloneSerializer : CloneBase { WTF_FORBID_HEAP_ALLOCATION; + public: static SerializationReturnCode serialize(JSGlobalObject* lexicalGlobalObject, JSValue value, Vector>& messagePorts, Vector>& arrayBuffers, const Vector>& imageBitmaps, #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) - const Vector>& offscreenCanvases, + const Vector>& offscreenCanvases, #endif #if ENABLE(WEB_RTC) - const Vector>& rtcDataChannels, + const Vector>& rtcDataChannels, #endif #if ENABLE(WEB_CODECS) - Vector>& serializedVideoChunks, - Vector>& serializedVideoFrames, + Vector>& serializedVideoChunks, + Vector>& serializedVideoFrames, #endif #if ENABLE(WEBASSEMBLY) - WasmModuleArray& wasmModules, - WasmMemoryHandleArray& wasmMemoryHandles, + WasmModuleArray& wasmModules, + WasmMemoryHandleArray& wasmMemoryHandles, #endif Vector& blobHandles, Vector& out, SerializationContext context, ArrayBufferContentsArray& sharedBuffers, SerializationForStorage forStorage) @@ -712,18 +722,18 @@ class CloneSerializer : CloneBase { CloneSerializer(JSGlobalObject* lexicalGlobalObject, Vector>& messagePorts, Vector>& arrayBuffers, const Vector>& imageBitmaps, #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) - const Vector>& offscreenCanvases, + const Vector>& offscreenCanvases, #endif #if ENABLE(WEB_RTC) - const Vector>& rtcDataChannels, + const Vector>& rtcDataChannels, #endif #if ENABLE(WEB_CODECS) - Vector>& serializedVideoChunks, - Vector>& serializedVideoFrames, + Vector>& serializedVideoChunks, + Vector>& serializedVideoFrames, #endif #if ENABLE(WEBASSEMBLY) - WasmModuleArray& wasmModules, - WasmMemoryHandleArray& wasmMemoryHandles, + WasmModuleArray& wasmModules, + WasmMemoryHandleArray& wasmMemoryHandles, #endif Vector& blobHandles, Vector& out, SerializationContext context, ArrayBufferContentsArray& sharedBuffers, SerializationForStorage forStorage) : CloneBase(lexicalGlobalObject) @@ -754,7 +764,7 @@ class CloneSerializer : CloneBase { #endif } - template + template void fillTransferMap(const Vector>& input, ObjectPool& result) { if (input.isEmpty()) @@ -767,7 +777,7 @@ class CloneSerializer : CloneBase { result.add(obj, i); } } - template + template void fillTransferMap(const Vector>& input, ObjectPool& result) { if (input.isEmpty()) @@ -1414,7 +1424,7 @@ class CloneSerializer : CloneBase { } if (!startObjectInternal(obj)) // handle duplicates return true; - + if (arrayBuffer->isShared() && m_context == SerializationContext::WorkerPostMessage) { // https://html.spec.whatwg.org/multipage/structured-data.html#structuredserializeinternal if (!JSC::Options::useSharedArrayBuffer()) { @@ -1430,7 +1440,7 @@ class CloneSerializer : CloneBase { return true; } } - + if (arrayBuffer->isResizableOrGrowableShared()) { write(ResizableArrayBufferTag); uint64_t byteLength = arrayBuffer->byteLength(); @@ -1470,12 +1480,12 @@ class CloneSerializer : CloneBase { WasmMemoryHandleArray dummyMemoryHandles; #endif ArrayBufferContentsArray dummySharedBuffers; - CloneSerializer rawKeySerializer(m_lexicalGlobalObject, dummyMessagePorts, dummyArrayBuffers, { }, + CloneSerializer rawKeySerializer(m_lexicalGlobalObject, dummyMessagePorts, dummyArrayBuffers, {}, #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) - { }, + {}, #endif #if ENABLE(WEB_RTC) - { }, + {}, #endif #if ENABLE(WEB_CODECS) dummyVideoChunks, @@ -1514,7 +1524,7 @@ class CloneSerializer : CloneBase { if (m_context != SerializationContext::WorkerPostMessage && m_context != SerializationContext::WindowPostMessage) return false; - uint32_t index = m_wasmModules.size(); + uint32_t index = m_wasmModules.size(); m_wasmModules.append(&module->module()); write(WasmModuleTag); write(agentClusterIDFromGlobalObject(*m_lexicalGlobalObject)); @@ -1666,7 +1676,7 @@ class CloneSerializer : CloneBase { { writeLittleEndian(m_buffer, i); } - + void write(uint16_t ch) { writeLittleEndian(m_buffer, ch); @@ -1676,13 +1686,13 @@ class CloneSerializer : CloneBase { { writeConstantPoolIndex(m_constantPool, i); } - + void writeObjectIndex(unsigned i) { writeConstantPoolIndex(m_objectPool, i); } - template void writeConstantPoolIndex(const T& constantPool, unsigned i) + template void writeConstantPoolIndex(const T& constantPool, unsigned i) { ASSERT(i < constantPool.size()); if (constantPool.size() <= 0xFF) @@ -2081,232 +2091,232 @@ SerializationReturnCode CloneSerializer::serialize(JSValue in) auto scope = DECLARE_THROW_SCOPE(vm); while (1) { switch (state) { - arrayStartState: - case ArrayStartState: { - ASSERT(isArray(inValue)); - if (inputObjectStack.size() > maximumFilterRecursion) - return SerializationReturnCode::StackOverflowError; - - JSArray* inArray = asArray(inValue); - unsigned length = inArray->length(); - if (!startArray(inArray)) - break; - inputObjectStack.append(inArray); - indexStack.append(0); - lengthStack.append(length); - } - arrayStartVisitMember: + arrayStartState: + case ArrayStartState: { + ASSERT(isArray(inValue)); + if (inputObjectStack.size() > maximumFilterRecursion) + return SerializationReturnCode::StackOverflowError; + + JSArray* inArray = asArray(inValue); + unsigned length = inArray->length(); + if (!startArray(inArray)) + break; + inputObjectStack.append(inArray); + indexStack.append(0); + lengthStack.append(length); + } + arrayStartVisitMember: FALLTHROUGH; - case ArrayStartVisitMember: { - JSObject* array = inputObjectStack.last(); - uint32_t index = indexStack.last(); - if (index == lengthStack.last()) { - indexStack.removeLast(); - lengthStack.removeLast(); - - propertyStack.append(PropertyNameArray(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude)); - array->getOwnNonIndexPropertyNames(m_lexicalGlobalObject, propertyStack.last(), DontEnumPropertiesMode::Exclude); - if (UNLIKELY(scope.exception())) - return SerializationReturnCode::ExistingExceptionError; - if (propertyStack.last().size()) { - write(NonIndexPropertiesTag); - indexStack.append(0); - goto objectStartVisitMember; - } - propertyStack.removeLast(); + case ArrayStartVisitMember: { + JSObject* array = inputObjectStack.last(); + uint32_t index = indexStack.last(); + if (index == lengthStack.last()) { + indexStack.removeLast(); + lengthStack.removeLast(); - endObject(); - inputObjectStack.removeLast(); - break; - } - inValue = array->getDirectIndex(m_lexicalGlobalObject, index); + propertyStack.append(PropertyNameArray(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude)); + array->getOwnNonIndexPropertyNames(m_lexicalGlobalObject, propertyStack.last(), DontEnumPropertiesMode::Exclude); if (UNLIKELY(scope.exception())) return SerializationReturnCode::ExistingExceptionError; - if (!inValue) { - indexStack.last()++; - goto arrayStartVisitMember; + if (propertyStack.last().size()) { + write(NonIndexPropertiesTag); + indexStack.append(0); + goto objectStartVisitMember; } + propertyStack.removeLast(); - write(index); - auto terminalCode = SerializationReturnCode::SuccessfullyCompleted; - if (dumpIfTerminal(inValue, terminalCode)) { - if (terminalCode != SerializationReturnCode::SuccessfullyCompleted) - return terminalCode; - indexStack.last()++; - goto arrayStartVisitMember; - } - stateStack.append(ArrayEndVisitMember); - goto stateUnknown; + endObject(); + inputObjectStack.removeLast(); + break; } - case ArrayEndVisitMember: { + inValue = array->getDirectIndex(m_lexicalGlobalObject, index); + if (UNLIKELY(scope.exception())) + return SerializationReturnCode::ExistingExceptionError; + if (!inValue) { indexStack.last()++; goto arrayStartVisitMember; } - objectStartState: - case ObjectStartState: { - ASSERT(inValue.isObject()); - if (inputObjectStack.size() > maximumFilterRecursion) - return SerializationReturnCode::StackOverflowError; - JSObject* inObject = asObject(inValue); - if (!startObject(inObject)) - break; - // At this point, all supported objects other than Object - // objects have been handled. If we reach this point and - // the input is not an Object object then we should throw - // a DataCloneError. - if (inObject->classInfo() != JSFinalObject::info()) - return SerializationReturnCode::DataCloneError; - inputObjectStack.append(inObject); - indexStack.append(0); - propertyStack.append(PropertyNameArray(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude)); - inObject->methodTable()->getOwnPropertyNames(inObject, m_lexicalGlobalObject, propertyStack.last(), DontEnumPropertiesMode::Exclude); - if (UNLIKELY(scope.exception())) - return SerializationReturnCode::ExistingExceptionError; - } - objectStartVisitMember: - FALLTHROUGH; - case ObjectStartVisitMember: { - JSObject* object = inputObjectStack.last(); - uint32_t index = indexStack.last(); - PropertyNameArray& properties = propertyStack.last(); - if (index == properties.size()) { - endObject(); - inputObjectStack.removeLast(); - indexStack.removeLast(); - propertyStack.removeLast(); - break; - } - inValue = getProperty(object, properties[index]); - if (UNLIKELY(scope.exception())) - return SerializationReturnCode::ExistingExceptionError; - if (!inValue) { - // Property was removed during serialisation - indexStack.last()++; - goto objectStartVisitMember; - } - write(properties[index]); - - if (UNLIKELY(scope.exception())) - return SerializationReturnCode::ExistingExceptionError; - - auto terminalCode = SerializationReturnCode::SuccessfullyCompleted; - if (!dumpIfTerminal(inValue, terminalCode)) { - stateStack.append(ObjectEndVisitMember); - goto stateUnknown; - } + write(index); + auto terminalCode = SerializationReturnCode::SuccessfullyCompleted; + if (dumpIfTerminal(inValue, terminalCode)) { if (terminalCode != SerializationReturnCode::SuccessfullyCompleted) return terminalCode; - FALLTHROUGH; + indexStack.last()++; + goto arrayStartVisitMember; } - case ObjectEndVisitMember: { - if (UNLIKELY(scope.exception())) - return SerializationReturnCode::ExistingExceptionError; + stateStack.append(ArrayEndVisitMember); + goto stateUnknown; + } + case ArrayEndVisitMember: { + indexStack.last()++; + goto arrayStartVisitMember; + } + objectStartState: + case ObjectStartState: { + ASSERT(inValue.isObject()); + if (inputObjectStack.size() > maximumFilterRecursion) + return SerializationReturnCode::StackOverflowError; + JSObject* inObject = asObject(inValue); + if (!startObject(inObject)) + break; + // At this point, all supported objects other than Object + // objects have been handled. If we reach this point and + // the input is not an Object object then we should throw + // a DataCloneError. + if (inObject->classInfo() != JSFinalObject::info()) + return SerializationReturnCode::DataCloneError; + inputObjectStack.append(inObject); + indexStack.append(0); + propertyStack.append(PropertyNameArray(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude)); + inObject->methodTable()->getOwnPropertyNames(inObject, m_lexicalGlobalObject, propertyStack.last(), DontEnumPropertiesMode::Exclude); + if (UNLIKELY(scope.exception())) + return SerializationReturnCode::ExistingExceptionError; + } + objectStartVisitMember: + FALLTHROUGH; + case ObjectStartVisitMember: { + JSObject* object = inputObjectStack.last(); + uint32_t index = indexStack.last(); + PropertyNameArray& properties = propertyStack.last(); + if (index == properties.size()) { + endObject(); + inputObjectStack.removeLast(); + indexStack.removeLast(); + propertyStack.removeLast(); + break; + } + inValue = getProperty(object, properties[index]); + if (UNLIKELY(scope.exception())) + return SerializationReturnCode::ExistingExceptionError; + if (!inValue) { + // Property was removed during serialisation indexStack.last()++; goto objectStartVisitMember; } - mapStartState: { - ASSERT(inValue.isObject()); - if (inputObjectStack.size() > maximumFilterRecursion) - return SerializationReturnCode::StackOverflowError; - JSMap* inMap = jsCast(inValue); - if (!startMap(inMap)) - break; - JSMapIterator* iterator = JSMapIterator::create(vm, m_lexicalGlobalObject->mapIteratorStructure(), inMap, IterationKind::Entries); - m_gcBuffer.appendWithCrashOnOverflow(inMap); - m_gcBuffer.appendWithCrashOnOverflow(iterator); - mapIteratorStack.append(iterator); - inputObjectStack.append(inMap); - goto mapDataStartVisitEntry; - } - mapDataStartVisitEntry: - case MapDataStartVisitEntry: { - JSMapIterator* iterator = mapIteratorStack.last(); - JSValue key, value; - if (!iterator->nextKeyValue(m_lexicalGlobalObject, key, value)) { - mapIteratorStack.removeLast(); - JSObject* object = inputObjectStack.last(); - ASSERT(jsDynamicCast(object)); - propertyStack.append(PropertyNameArray(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude)); - object->methodTable()->getOwnPropertyNames(object, m_lexicalGlobalObject, propertyStack.last(), DontEnumPropertiesMode::Exclude); - if (UNLIKELY(scope.exception())) - return SerializationReturnCode::ExistingExceptionError; - write(NonMapPropertiesTag); - indexStack.append(0); - goto objectStartVisitMember; - } - inValue = key; - m_gcBuffer.appendWithCrashOnOverflow(value); - mapIteratorValueStack.append(value); - stateStack.append(MapDataEndVisitKey); - goto stateUnknown; - } - case MapDataEndVisitKey: { - inValue = mapIteratorValueStack.last(); - mapIteratorValueStack.removeLast(); - stateStack.append(MapDataEndVisitValue); - goto stateUnknown; - } - case MapDataEndVisitValue: { - goto mapDataStartVisitEntry; - } + write(properties[index]); - setStartState: { - ASSERT(inValue.isObject()); - if (inputObjectStack.size() > maximumFilterRecursion) - return SerializationReturnCode::StackOverflowError; - JSSet* inSet = jsCast(inValue); - if (!startSet(inSet)) - break; - JSSetIterator* iterator = JSSetIterator::create(vm, m_lexicalGlobalObject->setIteratorStructure(), inSet, IterationKind::Keys); - m_gcBuffer.appendWithCrashOnOverflow(inSet); - m_gcBuffer.appendWithCrashOnOverflow(iterator); - setIteratorStack.append(iterator); - inputObjectStack.append(inSet); - goto setDataStartVisitEntry; - } - setDataStartVisitEntry: - case SetDataStartVisitEntry: { - JSSetIterator* iterator = setIteratorStack.last(); - JSValue key; - if (!iterator->next(m_lexicalGlobalObject, key)) { - setIteratorStack.removeLast(); - JSObject* object = inputObjectStack.last(); - ASSERT(jsDynamicCast(object)); - propertyStack.append(PropertyNameArray(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude)); - object->methodTable()->getOwnPropertyNames(object, m_lexicalGlobalObject, propertyStack.last(), DontEnumPropertiesMode::Exclude); - if (UNLIKELY(scope.exception())) - return SerializationReturnCode::ExistingExceptionError; - write(NonSetPropertiesTag); - indexStack.append(0); - goto objectStartVisitMember; - } - inValue = key; - stateStack.append(SetDataEndVisitKey); + if (UNLIKELY(scope.exception())) + return SerializationReturnCode::ExistingExceptionError; + + auto terminalCode = SerializationReturnCode::SuccessfullyCompleted; + if (!dumpIfTerminal(inValue, terminalCode)) { + stateStack.append(ObjectEndVisitMember); goto stateUnknown; } - case SetDataEndVisitKey: { - goto setDataStartVisitEntry; + if (terminalCode != SerializationReturnCode::SuccessfullyCompleted) + return terminalCode; + FALLTHROUGH; + } + case ObjectEndVisitMember: { + if (UNLIKELY(scope.exception())) + return SerializationReturnCode::ExistingExceptionError; + + indexStack.last()++; + goto objectStartVisitMember; + } + mapStartState : { + ASSERT(inValue.isObject()); + if (inputObjectStack.size() > maximumFilterRecursion) + return SerializationReturnCode::StackOverflowError; + JSMap* inMap = jsCast(inValue); + if (!startMap(inMap)) + break; + JSMapIterator* iterator = JSMapIterator::create(vm, m_lexicalGlobalObject->mapIteratorStructure(), inMap, IterationKind::Entries); + m_gcBuffer.appendWithCrashOnOverflow(inMap); + m_gcBuffer.appendWithCrashOnOverflow(iterator); + mapIteratorStack.append(iterator); + inputObjectStack.append(inMap); + goto mapDataStartVisitEntry; + } + mapDataStartVisitEntry: + case MapDataStartVisitEntry: { + JSMapIterator* iterator = mapIteratorStack.last(); + JSValue key, value; + if (!iterator->nextKeyValue(m_lexicalGlobalObject, key, value)) { + mapIteratorStack.removeLast(); + JSObject* object = inputObjectStack.last(); + ASSERT(jsDynamicCast(object)); + propertyStack.append(PropertyNameArray(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude)); + object->methodTable()->getOwnPropertyNames(object, m_lexicalGlobalObject, propertyStack.last(), DontEnumPropertiesMode::Exclude); + if (UNLIKELY(scope.exception())) + return SerializationReturnCode::ExistingExceptionError; + write(NonMapPropertiesTag); + indexStack.append(0); + goto objectStartVisitMember; } + inValue = key; + m_gcBuffer.appendWithCrashOnOverflow(value); + mapIteratorValueStack.append(value); + stateStack.append(MapDataEndVisitKey); + goto stateUnknown; + } + case MapDataEndVisitKey: { + inValue = mapIteratorValueStack.last(); + mapIteratorValueStack.removeLast(); + stateStack.append(MapDataEndVisitValue); + goto stateUnknown; + } + case MapDataEndVisitValue: { + goto mapDataStartVisitEntry; + } - stateUnknown: - case StateUnknown: { - auto terminalCode = SerializationReturnCode::SuccessfullyCompleted; - if (dumpIfTerminal(inValue, terminalCode)) { - if (terminalCode != SerializationReturnCode::SuccessfullyCompleted) - return terminalCode; - break; - } + setStartState : { + ASSERT(inValue.isObject()); + if (inputObjectStack.size() > maximumFilterRecursion) + return SerializationReturnCode::StackOverflowError; + JSSet* inSet = jsCast(inValue); + if (!startSet(inSet)) + break; + JSSetIterator* iterator = JSSetIterator::create(vm, m_lexicalGlobalObject->setIteratorStructure(), inSet, IterationKind::Keys); + m_gcBuffer.appendWithCrashOnOverflow(inSet); + m_gcBuffer.appendWithCrashOnOverflow(iterator); + setIteratorStack.append(iterator); + inputObjectStack.append(inSet); + goto setDataStartVisitEntry; + } + setDataStartVisitEntry: + case SetDataStartVisitEntry: { + JSSetIterator* iterator = setIteratorStack.last(); + JSValue key; + if (!iterator->next(m_lexicalGlobalObject, key)) { + setIteratorStack.removeLast(); + JSObject* object = inputObjectStack.last(); + ASSERT(jsDynamicCast(object)); + propertyStack.append(PropertyNameArray(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude)); + object->methodTable()->getOwnPropertyNames(object, m_lexicalGlobalObject, propertyStack.last(), DontEnumPropertiesMode::Exclude); + if (UNLIKELY(scope.exception())) + return SerializationReturnCode::ExistingExceptionError; + write(NonSetPropertiesTag); + indexStack.append(0); + goto objectStartVisitMember; + } + inValue = key; + stateStack.append(SetDataEndVisitKey); + goto stateUnknown; + } + case SetDataEndVisitKey: { + goto setDataStartVisitEntry; + } - if (isArray(inValue)) - goto arrayStartState; - if (isMap(inValue)) - goto mapStartState; - if (isSet(inValue)) - goto setStartState; - goto objectStartState; + stateUnknown: + case StateUnknown: { + auto terminalCode = SerializationReturnCode::SuccessfullyCompleted; + if (dumpIfTerminal(inValue, terminalCode)) { + if (terminalCode != SerializationReturnCode::SuccessfullyCompleted) + return terminalCode; + break; } + + if (isArray(inValue)) + goto arrayStartState; + if (isMap(inValue)) + goto mapStartState; + if (isSet(inValue)) + goto setStartState; + goto objectStartState; + } } if (stateStack.isEmpty()) break; @@ -2322,6 +2332,7 @@ SerializationReturnCode CloneSerializer::serialize(JSValue in) class CloneDeserializer : CloneBase { WTF_FORBID_HEAP_ALLOCATION; + public: static String deserializeString(const Vector& buffer) { @@ -2348,40 +2359,45 @@ class CloneDeserializer : CloneBase { static DeserializationResult deserialize(JSGlobalObject* lexicalGlobalObject, JSGlobalObject* globalObject, const Vector>& messagePorts, Vector>&& backingStores #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) - , Vector>&& detachedOffscreenCanvases + , + Vector>&& detachedOffscreenCanvases #endif #if ENABLE(WEB_RTC) - , Vector>&& detachedRTCDataChannels + , + Vector>&& detachedRTCDataChannels #endif - , ArrayBufferContentsArray* arrayBufferContentsArray, const Vector& buffer, const Vector& blobURLs, const Vector blobFilePaths, ArrayBufferContentsArray* sharedBuffers + , + ArrayBufferContentsArray* arrayBufferContentsArray, const Vector& buffer, const Vector& blobURLs, const Vector blobFilePaths, ArrayBufferContentsArray* sharedBuffers #if ENABLE(WEBASSEMBLY) - , WasmModuleArray* wasmModules - , WasmMemoryHandleArray* wasmMemoryHandles + , + WasmModuleArray* wasmModules, WasmMemoryHandleArray* wasmMemoryHandles #endif #if ENABLE(WEB_CODECS) - , Vector>&& serializedVideoChunks - , Vector&& serializedVideoFrames + , + Vector>&& serializedVideoChunks, Vector&& serializedVideoFrames #endif - ) + ) { if (!buffer.size()) return std::make_pair(jsNull(), SerializationReturnCode::UnspecifiedError); CloneDeserializer deserializer(lexicalGlobalObject, globalObject, messagePorts, arrayBufferContentsArray, buffer, blobURLs, blobFilePaths, sharedBuffers, WTFMove(backingStores) #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) - , WTFMove(detachedOffscreenCanvases) + , + WTFMove(detachedOffscreenCanvases) #endif #if ENABLE(WEB_RTC) - , WTFMove(detachedRTCDataChannels) + , + WTFMove(detachedRTCDataChannels) #endif #if ENABLE(WEBASSEMBLY) - , wasmModules - , wasmMemoryHandles + , + wasmModules, wasmMemoryHandles #endif #if ENABLE(WEB_CODECS) - , WTFMove(serializedVideoChunks) - , WTFMove(serializedVideoFrames) + , + WTFMove(serializedVideoChunks), WTFMove(serializedVideoFrames) #endif - ); + ); if (!deserializer.isValid()) return std::make_pair(JSValue(), SerializationReturnCode::ValidationError); return deserializer.deserialize(); @@ -2419,9 +2435,13 @@ class CloneDeserializer : CloneBase { , m_index(index) { } - - CachedString* operator->() { ASSERT(m_base); return &m_base->at(m_index); } - + + CachedString* operator->() + { + ASSERT(m_base); + return &m_base->at(m_index); + } + private: Vector* m_base; size_t m_index; @@ -2429,18 +2449,20 @@ class CloneDeserializer : CloneBase { CloneDeserializer(JSGlobalObject* lexicalGlobalObject, JSGlobalObject* globalObject, const Vector>& messagePorts, ArrayBufferContentsArray* arrayBufferContents, Vector>&& backingStores, const Vector& buffer #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) - , Vector>&& detachedOffscreenCanvases = { } + , + Vector>&& detachedOffscreenCanvases = {} #endif #if ENABLE(WEB_RTC) - , Vector>&& detachedRTCDataChannels = { } + , + Vector>&& detachedRTCDataChannels = {} #endif #if ENABLE(WEBASSEMBLY) - , WasmModuleArray* wasmModules = nullptr - , WasmMemoryHandleArray* wasmMemoryHandles = nullptr + , + WasmModuleArray* wasmModules = nullptr, WasmMemoryHandleArray* wasmMemoryHandles = nullptr #endif #if ENABLE(WEB_CODECS) - , Vector>&& serializedVideoChunks = { } - , Vector&& serializedVideoFrames = { } + , + Vector>&& serializedVideoChunks = {}, Vector&& serializedVideoFrames = {} #endif ) : CloneBase(lexicalGlobalObject) @@ -2480,18 +2502,20 @@ class CloneDeserializer : CloneBase { CloneDeserializer(JSGlobalObject* lexicalGlobalObject, JSGlobalObject* globalObject, const Vector>& messagePorts, ArrayBufferContentsArray* arrayBufferContents, const Vector& buffer, const Vector& blobURLs, const Vector blobFilePaths, ArrayBufferContentsArray* sharedBuffers, Vector>&& backingStores #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) - , Vector>&& detachedOffscreenCanvases + , + Vector>&& detachedOffscreenCanvases #endif #if ENABLE(WEB_RTC) - , Vector>&& detachedRTCDataChannels + , + Vector>&& detachedRTCDataChannels #endif #if ENABLE(WEBASSEMBLY) - , WasmModuleArray* wasmModules - , WasmMemoryHandleArray* wasmMemoryHandles + , + WasmModuleArray* wasmModules, WasmMemoryHandleArray* wasmMemoryHandles #endif #if ENABLE(WEB_CODECS) - , Vector>&& serializedVideoChunks = { } - , Vector&& serializedVideoFrames = { } + , + Vector>&& serializedVideoChunks = {}, Vector&& serializedVideoFrames = {} #endif ) : CloneBase(lexicalGlobalObject) @@ -2536,7 +2560,7 @@ class CloneDeserializer : CloneBase { bool isValid() const { return m_version <= CurrentVersion; } - template bool readLittleEndian(T& value) + template bool readLittleEndian(T& value) { if (m_failed || !readLittleEndian(m_ptr, m_end, value)) { fail(); @@ -2545,7 +2569,7 @@ class CloneDeserializer : CloneBase { return true; } #if ASSUME_LITTLE_ENDIAN - template static bool readLittleEndian(const uint8_t*& ptr, const uint8_t* end, T& value) + template static bool readLittleEndian(const uint8_t*& ptr, const uint8_t* end, T& value) { if (ptr > end - sizeof(value)) return false; @@ -2559,7 +2583,7 @@ class CloneDeserializer : CloneBase { return true; } #else - template static bool readLittleEndian(const uint8_t*& ptr, const uint8_t* end, T& value) + template static bool readLittleEndian(const uint8_t*& ptr, const uint8_t* end, T& value) { if (ptr > end - sizeof(value)) return false; @@ -2617,7 +2641,7 @@ class CloneDeserializer : CloneBase { return readConstantPoolIndex(m_constantPool, i); } - template bool readConstantPoolIndex(const T& constantPool, uint32_t& i) + template bool readConstantPoolIndex(const T& constantPool, uint32_t& i) { if (constantPool.size() <= 0xFF) { uint8_t i8; @@ -2811,7 +2835,7 @@ class CloneDeserializer : CloneBase { return true; } - template + template bool readArrayBufferViewImpl(VM& vm, JSValue& arrayBufferView) { ArrayBufferViewSubtag arrayBufferViewSubtag; @@ -3397,16 +3421,16 @@ class CloneDeserializer : CloneBase { { double x; if (!read(x)) - return { }; + return {}; double y; if (!read(y)) - return { }; + return {}; double z; if (!read(z)) - return { }; + return {}; double w; if (!read(w)) - return { }; + return {}; return toJSNewlyCreated(m_lexicalGlobalObject, jsCast(m_globalObject), T::create(x, y, z, w)); } @@ -3416,79 +3440,79 @@ class CloneDeserializer : CloneBase { { uint8_t is2D; if (!read(is2D)) - return { }; + return {}; if (is2D) { double m11; if (!read(m11)) - return { }; + return {}; double m12; if (!read(m12)) - return { }; + return {}; double m21; if (!read(m21)) - return { }; + return {}; double m22; if (!read(m22)) - return { }; + return {}; double m41; if (!read(m41)) - return { }; + return {}; double m42; if (!read(m42)) - return { }; + return {}; TransformationMatrix matrix(m11, m12, m21, m22, m41, m42); return toJSNewlyCreated(m_lexicalGlobalObject, jsCast(m_globalObject), T::create(WTFMove(matrix), DOMMatrixReadOnly::Is2D::Yes)); } else { double m11; if (!read(m11)) - return { }; + return {}; double m12; if (!read(m12)) - return { }; + return {}; double m13; if (!read(m13)) - return { }; + return {}; double m14; if (!read(m14)) - return { }; + return {}; double m21; if (!read(m21)) - return { }; + return {}; double m22; if (!read(m22)) - return { }; + return {}; double m23; if (!read(m23)) - return { }; + return {}; double m24; if (!read(m24)) - return { }; + return {}; double m31; if (!read(m31)) - return { }; + return {}; double m32; if (!read(m32)) - return { }; + return {}; double m33; if (!read(m33)) - return { }; + return {}; double m34; if (!read(m34)) - return { }; + return {}; double m41; if (!read(m41)) - return { }; + return {}; double m42; if (!read(m42)) - return { }; + return {}; double m43; if (!read(m43)) - return { }; + return {}; double m44; if (!read(m44)) - return { }; + return {}; TransformationMatrix matrix(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44); return toJSNewlyCreated(m_lexicalGlobalObject, jsCast(m_globalObject), T::create(WTFMove(matrix), DOMMatrixReadOnly::Is2D::No)); @@ -3500,16 +3524,16 @@ class CloneDeserializer : CloneBase { { double x; if (!read(x)) - return { }; + return {}; double y; if (!read(y)) - return { }; + return {}; double width; if (!read(width)) - return { }; + return {}; double height; if (!read(height)) - return { }; + return {}; return toJSNewlyCreated(m_lexicalGlobalObject, jsCast(m_globalObject), T::create(x, y, width, height)); } @@ -4070,7 +4094,7 @@ class CloneDeserializer : CloneBase { scope.releaseAssertNoException(); RefPtr memory; - auto handler = [&vm, result] (Wasm::Memory::GrowSuccess, PageCount oldPageCount, PageCount newPageCount) { result->growSuccessCallback(vm, oldPageCount, newPageCount); }; + auto handler = [&vm, result](Wasm::Memory::GrowSuccess, PageCount oldPageCount, PageCount newPageCount) { result->growSuccessCallback(vm, oldPageCount, newPageCount); }; if (RefPtr contents = m_wasmMemoryHandles->at(index)) { if (!contents->memoryHandle()) { fail(); @@ -4142,7 +4166,7 @@ class CloneDeserializer : CloneBase { fail(); return JSValue(); } - + RELEASE_ASSERT(m_sharedBuffers->at(index)); auto buffer = ArrayBuffer::create(WTFMove(m_sharedBuffers->at(index))); JSValue result = getJSValue(buffer.get()); @@ -4172,7 +4196,7 @@ class CloneDeserializer : CloneBase { } JSValue cryptoKey; Vector> dummyMessagePorts; - CloneDeserializer rawKeyDeserializer(m_lexicalGlobalObject, m_globalObject, dummyMessagePorts, nullptr, { }, serializedKey); + CloneDeserializer rawKeyDeserializer(m_lexicalGlobalObject, m_globalObject, dummyMessagePorts, nullptr, {}, serializedKey); if (!rawKeyDeserializer.readCryptoKey(cryptoKey)) { fail(); return JSValue(); @@ -4313,7 +4337,7 @@ DeserializationResult CloneDeserializer::deserialize() outputObjectStack.append(outArray); } arrayStartVisitMember: - FALLTHROUGH; + FALLTHROUGH; case ArrayStartVisitMember: { uint32_t index; if (!read(index)) { @@ -4354,7 +4378,7 @@ DeserializationResult CloneDeserializer::deserialize() outputObjectStack.append(outObject); } objectStartVisitMember: - FALLTHROUGH; + FALLTHROUGH; case ObjectStartVisitMember: { CachedStringRef cachedString; bool wasTerminator = false; @@ -4381,7 +4405,7 @@ DeserializationResult CloneDeserializer::deserialize() propertyNameStack.removeLast(); goto objectStartVisitMember; } - mapObjectStartState: { + mapObjectStartState : { if (outputObjectStack.size() > maximumFilterRecursion) return std::make_pair(JSValue(), SerializationReturnCode::StackOverflowError); JSMap* map = JSMap::create(m_lexicalGlobalObject->vm(), m_globalObject->mapStructure()); @@ -4410,7 +4434,7 @@ DeserializationResult CloneDeserializer::deserialize() goto mapDataStartVisitEntry; } - setObjectStartState: { + setObjectStartState : { if (outputObjectStack.size() > maximumFilterRecursion) return std::make_pair(JSValue(), SerializationReturnCode::StackOverflowError); JSSet* set = JSSet::create(m_lexicalGlobalObject->vm(), m_globalObject->setStructure()); @@ -4469,13 +4493,14 @@ SerializedScriptValue::~SerializedScriptValue() = default; SerializedScriptValue::SerializedScriptValue(Vector&& buffer, std::unique_ptr&& arrayBufferContentsArray #if ENABLE(WEB_RTC) - , Vector>&& detachedRTCDataChannels + , + Vector>&& detachedRTCDataChannels #endif #if ENABLE(WEB_CODECS) - , Vector>&& serializedVideoChunks - , Vector&& serializedVideoFrames + , + Vector>&& serializedVideoChunks, Vector&& serializedVideoFrames #endif - ) + ) : m_data(WTFMove(buffer)) , m_arrayBufferContentsArray(WTFMove(arrayBufferContentsArray)) #if ENABLE(WEB_RTC) @@ -4491,20 +4516,22 @@ SerializedScriptValue::SerializedScriptValue(Vector&& buffer, std::uniq SerializedScriptValue::SerializedScriptValue(Vector&& buffer, Vector&& blobHandles, std::unique_ptr arrayBufferContentsArray, std::unique_ptr sharedBufferContentsArray, Vector>&& backingStores #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) - , Vector>&& detachedOffscreenCanvases + , + Vector>&& detachedOffscreenCanvases #endif #if ENABLE(WEB_RTC) - , Vector>&& detachedRTCDataChannels + , + Vector>&& detachedRTCDataChannels #endif #if ENABLE(WEBASSEMBLY) - , std::unique_ptr wasmModulesArray - , std::unique_ptr wasmMemoryHandlesArray + , + std::unique_ptr wasmModulesArray, std::unique_ptr wasmMemoryHandlesArray #endif #if ENABLE(WEB_CODECS) - , Vector>&& serializedVideoChunks - , Vector&& serializedVideoFrames + , + Vector>&& serializedVideoChunks, Vector&& serializedVideoFrames #endif - ) + ) : m_data(WTFMove(buffer)) , m_arrayBufferContentsArray(WTFMove(arrayBufferContentsArray)) , m_sharedBufferContentsArray(WTFMove(sharedBufferContentsArray)) @@ -4630,7 +4657,7 @@ static void maybeThrowExceptionIfSerializationFailed(JSGlobalObject& lexicalGlob static Exception exceptionForSerializationFailure(SerializationReturnCode code) { ASSERT(code != SerializationReturnCode::SuccessfullyCompleted); - + switch (code) { case SerializationReturnCode::StackOverflowError: return Exception { StackOverflowError }; @@ -4694,7 +4721,7 @@ static bool canDetachRTCDataChannels(const Vector>& channels RefPtr SerializedScriptValue::create(JSC::JSGlobalObject& globalObject, JSC::JSValue value, SerializationForStorage forStorage, SerializationErrorMode throwExceptions, SerializationContext serializationContext) { Vector> dummyPorts; - auto result = create(globalObject, value, { }, dummyPorts, forStorage, throwExceptions, serializationContext); + auto result = create(globalObject, value, {}, dummyPorts, forStorage, throwExceptions, serializationContext); if (result.hasException()) return nullptr; return result.releaseReturnValue(); @@ -4849,20 +4876,22 @@ ExceptionOr> SerializedScriptValue::create(JSGlobalOb return adoptRef(*new SerializedScriptValue(WTFMove(buffer), WTFMove(blobHandles), arrayBufferContentsArray.releaseReturnValue(), context == SerializationContext::WorkerPostMessage ? WTFMove(sharedBuffers) : nullptr, WTFMove(backingStores) #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) - , WTFMove(detachedCanvases) + , + WTFMove(detachedCanvases) #endif #if ENABLE(WEB_RTC) - , WTFMove(detachedRTCDataChannels) + , + WTFMove(detachedRTCDataChannels) #endif #if ENABLE(WEBASSEMBLY) - , makeUnique(wasmModules) - , context == SerializationContext::WorkerPostMessage ? makeUnique(wasmMemoryHandles) : nullptr + , + makeUnique(wasmModules), context == SerializationContext::WorkerPostMessage ? makeUnique(wasmMemoryHandles) : nullptr #endif #if ENABLE(WEB_CODECS) - , WTFMove(serializedVideoChunks) - , WTFMove(serializedVideoFrameData) + , + WTFMove(serializedVideoChunks), WTFMove(serializedVideoFrameData) #endif - )); + )); } RefPtr SerializedScriptValue::create(StringView string) @@ -4899,7 +4928,7 @@ String SerializedScriptValue::toString() const JSValue SerializedScriptValue::deserialize(JSGlobalObject& lexicalGlobalObject, JSGlobalObject* globalObject, SerializationErrorMode throwExceptions, bool* didFail) { - return deserialize(lexicalGlobalObject, globalObject, { }, throwExceptions, didFail); + return deserialize(lexicalGlobalObject, globalObject, {}, throwExceptions, didFail); } JSValue SerializedScriptValue::deserialize(JSGlobalObject& lexicalGlobalObject, JSGlobalObject* globalObject, const Vector>& messagePorts, SerializationErrorMode throwExceptions, bool* didFail) @@ -4913,21 +4942,24 @@ JSValue SerializedScriptValue::deserialize(JSGlobalObject& lexicalGlobalObject, { DeserializationResult result = CloneDeserializer::deserialize(&lexicalGlobalObject, globalObject, messagePorts, WTFMove(m_backingStores) #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) - , WTFMove(m_detachedOffscreenCanvases) + , + WTFMove(m_detachedOffscreenCanvases) #endif #if ENABLE(WEB_RTC) - , WTFMove(m_detachedRTCDataChannels) + , + WTFMove(m_detachedRTCDataChannels) #endif - , m_arrayBufferContentsArray.get(), m_data, blobURLs, blobFilePaths, m_sharedBufferContentsArray.get() + , + m_arrayBufferContentsArray.get(), m_data, blobURLs, blobFilePaths, m_sharedBufferContentsArray.get() #if ENABLE(WEBASSEMBLY) - , m_wasmModulesArray.get() - , m_wasmMemoryHandlesArray.get() + , + m_wasmModulesArray.get(), m_wasmMemoryHandlesArray.get() #endif #if ENABLE(WEB_CODECS) - , WTFMove(m_serializedVideoChunks) - , WTFMove(m_serializedVideoFrames) + , + WTFMove(m_serializedVideoChunks), WTFMove(m_serializedVideoFrames) #endif - ); + ); if (didFail) *didFail = result.second != SerializationReturnCode::SuccessfullyCompleted; if (throwExceptions == SerializationErrorMode::Throwing) @@ -4975,13 +5007,13 @@ void SerializedScriptValue::writeBlobsToDiskForIndexedDB(CompletionHandler; #if ENABLE(WEBASSEMBLY) @@ -76,6 +82,7 @@ using WasmMemoryHandleArray = Vector>; DECLARE_ALLOCATOR_WITH_HEAP_IDENTIFIER(SerializedScriptValue); class SerializedScriptValue : public ThreadSafeRefCounted { WTF_MAKE_FAST_ALLOCATED_WITH_HEAP_IDENTIFIER(SerializedScriptValue); + public: WEBCORE_EXPORT static ExceptionOr> create(JSC::JSGlobalObject&, JSC::JSValue, Vector>&& transfer, Vector>&, SerializationForStorage = SerializationForStorage::No, SerializationContext = SerializationContext::Default); WEBCORE_EXPORT static RefPtr create(JSC::JSGlobalObject&, JSC::JSValue, SerializationForStorage = SerializationForStorage::No, SerializationErrorMode = SerializationErrorMode::Throwing, SerializationContext = SerializationContext::Default); @@ -120,30 +127,33 @@ class SerializedScriptValue : public ThreadSafeRefCounted static ExceptionOr> create(JSC::JSGlobalObject&, JSC::JSValue, Vector>&& transfer, Vector>&, SerializationForStorage, SerializationErrorMode, SerializationContext); WEBCORE_EXPORT SerializedScriptValue(Vector&&, std::unique_ptr&& = nullptr #if ENABLE(WEB_RTC) - , Vector>&& = { } + , + Vector>&& = {} #endif #if ENABLE(WEB_CODECS) - , Vector>&& = { } - , Vector&& = { } + , + Vector>&& = {}, Vector&& = {} #endif - ); + ); SerializedScriptValue(Vector&&, Vector&& blobHandles, std::unique_ptr, std::unique_ptr sharedBuffers, Vector>&& backingStores #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) - , Vector>&& = { } + , + Vector>&& = {} #endif #if ENABLE(WEB_RTC) - , Vector>&& = { } + , + Vector>&& = {} #endif #if ENABLE(WEBASSEMBLY) - , std::unique_ptr = nullptr - , std::unique_ptr = nullptr + , + std::unique_ptr = nullptr, std::unique_ptr = nullptr #endif #if ENABLE(WEB_CODECS) - , Vector>&& = { } - , Vector&& = { } + , + Vector>&& = {}, Vector&& = {} #endif - ); + ); size_t computeMemoryCost() const; @@ -185,16 +195,16 @@ void SerializedScriptValue::encode(Encoder& encoder) const #if ENABLE(WEB_RTC) encoder << static_cast(m_detachedRTCDataChannels.size()); - for (const auto &channel : m_detachedRTCDataChannels) + for (const auto& channel : m_detachedRTCDataChannels) encoder << *channel; #endif #if ENABLE(WEB_CODECS) encoder << static_cast(m_serializedVideoChunks.size()); - for (const auto &videoChunk : m_serializedVideoChunks) + for (const auto& videoChunk : m_serializedVideoChunks) encoder << videoChunk->data(); - // FIXME: encode video frames + // FIXME: encode video frames #endif } @@ -266,16 +276,18 @@ RefPtr SerializedScriptValue::decode(Decoder& decoder) return adoptRef(*new SerializedScriptValue(WTFMove(data), WTFMove(arrayBufferContentsArray) #if ENABLE(WEB_RTC) - , WTFMove(detachedRTCDataChannels) + , + WTFMove(detachedRTCDataChannels) #endif #if ENABLE(WEB_CODECS) - , WTFMove(serializedVideoChunks) + , + WTFMove(serializedVideoChunks) #endif #if ENABLE(WEB_CODECS) - , WTFMove(serializedVideoFrames) + , + WTFMove(serializedVideoFrames) #endif - )); + )); } - } From 3068b1094b906f751b2deedcace56ce154923be9 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Tue, 11 Jul 2023 15:00:07 -0700 Subject: [PATCH 03/14] make `SerializedScriptValue` compile --- src/bun.js/bindings/ProcessIdentifier.cpp | 54 + src/bun.js/bindings/ProcessIdentifier.h | 41 + .../bindings/ScriptExecutionContext.cpp | 7 + src/bun.js/bindings/ScriptExecutionContext.h | 15 + src/bun.js/bindings/ZigGlobalObject.cpp | 36 +- src/bun.js/bindings/ZigGlobalObject.h | 3 + .../webcore/SerializedScriptValue.cpp | 2108 ++++++++++------- .../bindings/webcore/SerializedScriptValue.h | 68 +- 8 files changed, 1450 insertions(+), 882 deletions(-) create mode 100644 src/bun.js/bindings/ProcessIdentifier.cpp create mode 100644 src/bun.js/bindings/ProcessIdentifier.h diff --git a/src/bun.js/bindings/ProcessIdentifier.cpp b/src/bun.js/bindings/ProcessIdentifier.cpp new file mode 100644 index 000000000000..5e82f4fe9326 --- /dev/null +++ b/src/bun.js/bindings/ProcessIdentifier.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "ProcessIdentifier.h" + +#include + +namespace WebCore { +namespace ProcessIdent { + +static std::optional globalIdentifier; + +void setIdentifier(ProcessIdentifier processIdentifier) +{ + ASSERT(isUIThread()); + globalIdentifier = processIdentifier; +} + +ProcessIdentifier identifier() +{ + static std::once_flag onceFlag; + std::call_once(onceFlag, [] { + if (!globalIdentifier) + globalIdentifier = ProcessIdentifier::generate(); + }); + + return *globalIdentifier; +} + +} // namespace ProcessIdent +} // namespace WebCore diff --git a/src/bun.js/bindings/ProcessIdentifier.h b/src/bun.js/bindings/ProcessIdentifier.h new file mode 100644 index 000000000000..e6141bdc0508 --- /dev/null +++ b/src/bun.js/bindings/ProcessIdentifier.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2017 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include + +namespace WebCore { + +enum ProcessIdentifierType {}; +using ProcessIdentifier = ObjectIdentifier; + +namespace ProcessIdent { + +WEBCORE_EXPORT void setIdentifier(ProcessIdentifier); +WEBCORE_EXPORT ProcessIdentifier identifier(); + +} // namespace ProcessIdent +} // namespace WebCore diff --git a/src/bun.js/bindings/ScriptExecutionContext.cpp b/src/bun.js/bindings/ScriptExecutionContext.cpp index e8cae5e338a9..6e2996e662fd 100644 --- a/src/bun.js/bindings/ScriptExecutionContext.cpp +++ b/src/bun.js/bindings/ScriptExecutionContext.cpp @@ -131,4 +131,11 @@ void ScriptExecutionContext::removeFromContextsMap() // allScriptExecutionContextsMap().remove(m_identifier); } +ScriptExecutionContext* executionContext(JSC::JSGlobalObject* globalObject) +{ + if (!globalObject || !globalObject->inherits()) + return nullptr; + return JSC::jsCast(globalObject)->scriptExecutionContext(); +} + } diff --git a/src/bun.js/bindings/ScriptExecutionContext.h b/src/bun.js/bindings/ScriptExecutionContext.h index 5f6c56a90403..3317b13e953d 100644 --- a/src/bun.js/bindings/ScriptExecutionContext.h +++ b/src/bun.js/bindings/ScriptExecutionContext.h @@ -110,6 +110,18 @@ class ScriptExecutionContext : public CanMakeWeakPtr { // { // } +#if ENABLE(WEB_CRYPTO) + // These two methods are used when CryptoKeys are serialized into IndexedDB. As a side effect, it is also + // used for things that utilize the same structure clone algorithm, for example, message passing between + // worker and document. + + // For now these will return false. In the future, we will want to implement these similar to how WorkerGlobalScope.cpp does. + // virtual bool wrapCryptoKey(const Vector& key, Vector& wrappedKey) = 0; + // virtual bool unwrapCryptoKey(const Vector& wrappedKey, Vector& key) = 0; + bool wrapCryptoKey(const Vector& key, Vector& wrappedKey) { return false; } + bool unwrapCryptoKey(const Vector& wrappedKey, Vector& key) { return false; } +#endif + static bool postTaskTo(ScriptExecutionContextIdentifier identifier, Function&& task); void regenerateIdentifier(); @@ -188,4 +200,7 @@ class ScriptExecutionContext : public CanMakeWeakPtr { } } }; + +ScriptExecutionContext* executionContext(JSC::JSGlobalObject*); + } \ No newline at end of file diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index 3291b204eef4..6d2988cc585e 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -106,6 +106,8 @@ #include "BunJSCModule.h" #include "ModuleLoader.h" #include "NodeVMScript.h" +#include "ProcessIdentifier.h" +#include "SerializedScriptValue.h" #include "ZigGeneratedClasses.h" #include "JavaScriptCore/DateInstance.h" @@ -644,6 +646,19 @@ extern "C" bool Zig__GlobalObject__resetModuleRegistryMap(JSC__JSGlobalObject* g #define PUT_WEBCORE_GENERATED_CONSTRUCTOR(name, ConstructorName) \ putDirectCustomAccessor(vm, JSC::PropertyName(JSC::Identifier::fromString(vm, name)), JSC::CustomGetterSetter::create(vm, ConstructorName##_getter, ConstructorName##_setter), 0) +String GlobalObject::defaultAgentClusterID() +{ + return makeString(ProcessIdent::identifier().toUInt64(), "-default"); +} + +String GlobalObject::agentClusterID() const +{ + // TODO: workers + // if (is(scriptExecutionContext())) + // return makeString(WProcess::identifier().toUInt64(), "-sharedworker"); + return defaultAgentClusterID(); +} + namespace Zig { using namespace WebCore; @@ -1195,6 +1210,19 @@ JSC_DEFINE_HOST_FUNCTION(functionClearTimeout, return Bun__Timer__clearTimeout(globalObject, JSC::JSValue::encode(num)); } +JSC_DEFINE_HOST_FUNCTION(functionStructuredClone, + (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + JSC::VM& vm = globalObject->vm(); + + JSC::JSValue value = callFrame->argument(0); + + auto serialized = SerializedScriptValue::convert(*globalObject, value); + JSValue deserialized = serialized->deserialize(*globalObject, globalObject, SerializationErrorMode::Throwing); + + return JSValue::encode(deserialized); +} + JSC_DEFINE_HOST_FUNCTION(functionBTOA, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { @@ -3648,7 +3676,7 @@ void GlobalObject::addBuiltinGlobals(JSC::VM& vm) auto& builtinNames = WebCore::builtinNames(vm); WTF::Vector extraStaticGlobals; - extraStaticGlobals.reserveCapacity(44); + extraStaticGlobals.reserveCapacity(45); JSC::Identifier queueMicrotaskIdentifier = JSC::Identifier::fromString(vm, "queueMicrotask"_s); extraStaticGlobals.uncheckedAppend( @@ -3672,6 +3700,12 @@ void GlobalObject::addBuiltinGlobals(JSC::VM& vm) "clearImmediate"_s, functionClearTimeout, ImplementationVisibility::Public), JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0 }); + extraStaticGlobals.uncheckedAppend( + GlobalPropertyInfo { JSC::Identifier::fromString(vm, "structuredClone"_s), + JSC::JSFunction::create(vm, JSC::jsCast(globalObject()), 2, + "structuredClone"_s, functionStructuredClone, ImplementationVisibility::Public), + JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0 }); + JSC::Identifier setTimeoutIdentifier = JSC::Identifier::fromString(vm, "setTimeout"_s); extraStaticGlobals.uncheckedAppend( GlobalPropertyInfo { setTimeoutIdentifier, diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index f44212da1dbd..d079ad71ca31 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -373,6 +373,9 @@ class GlobalObject : public JSC::JSGlobalObject { JSObject* navigatorObject(); JSFunction* nativeMicrotaskTrampoline() { return m_nativeMicrotaskTrampoline.getInitializedOnMainThread(this); } + String agentClusterID() const; + static String defaultAgentClusterID(); + void trackFFIFunction(JSC::JSFunction* function) { this->m_ffiFunctions.append(JSC::Strong { vm(), function }); diff --git a/src/bun.js/bindings/webcore/SerializedScriptValue.cpp b/src/bun.js/bindings/webcore/SerializedScriptValue.cpp index d52b50326faf..c105784733d7 100644 --- a/src/bun.js/bindings/webcore/SerializedScriptValue.cpp +++ b/src/bun.js/bindings/webcore/SerializedScriptValue.cpp @@ -27,8 +27,8 @@ #include "config.h" #include "SerializedScriptValue.h" -#include "BlobRegistry.h" -#include "ByteArrayPixelBuffer.h" +// #include "BlobRegistry.h" +// #include "ByteArrayPixelBuffer.h" #include "CryptoKeyAES.h" #include "CryptoKeyEC.h" #include "CryptoKeyHMAC.h" @@ -36,33 +36,33 @@ #include "CryptoKeyRSA.h" #include "CryptoKeyRSAComponents.h" #include "CryptoKeyRaw.h" -#include "IDBValue.h" -#include "ImageBitmapBacking.h" -#include "JSBlob.h" +// #include "IDBValue.h" +// #include "ImageBitmapBacking.h" +// #include "JSBlob.h" #include "JSCryptoKey.h" #include "JSDOMBinding.h" #include "JSDOMConvertBufferSource.h" #include "JSDOMException.h" #include "JSDOMGlobalObject.h" -#include "JSDOMMatrix.h" -#include "JSDOMPoint.h" -#include "JSDOMQuad.h" -#include "JSDOMRect.h" -#include "JSExecState.h" -#include "JSFile.h" -#include "JSFileList.h" -#include "JSIDBSerializationGlobalObject.h" -#include "JSImageBitmap.h" -#include "JSImageData.h" -#include "JSMessagePort.h" -#include "JSNavigator.h" -#include "JSRTCCertificate.h" -#include "JSRTCDataChannel.h" -#include "JSWebCodecsEncodedVideoChunk.h" -#include "JSWebCodecsVideoFrame.h" +// #include "JSDOMMatrix.h" +// #include "JSDOMPoint.h" +// #include "JSDOMQuad.h" +// #include "JSDOMRect.h" +// #include "JSExecState.h" +// #include "JSFile.h" +// #include "JSFileList.h" +// #include "JSIDBSerializationGlobalObject.h" +// #include "JSImageBitmap.h" +// #include "JSImageData.h" +// #include "JSMessagePort.h" +// #include "JSNavigator.h" +// #include "JSRTCCertificate.h" +// #include "JSRTCDataChannel.h" +// #include "JSWebCodecsEncodedVideoChunk.h" +// #include "JSWebCodecsVideoFrame.h" #include "ScriptExecutionContext.h" #include "SharedBuffer.h" -#include "WebCodecsEncodedVideoChunk.h" +// #include "WebCodecsEncodedVideoChunk.h" #include "WebCoreJSClientData.h" #include #include @@ -664,7 +664,44 @@ class CloneSerializer : CloneBase { WTF_FORBID_HEAP_ALLOCATION; public: - static SerializationReturnCode serialize(JSGlobalObject* lexicalGlobalObject, JSValue value, Vector>& messagePorts, Vector>& arrayBuffers, const Vector>& imageBitmaps, + // static SerializationReturnCode serialize(JSGlobalObject* lexicalGlobalObject, JSValue value, Vector>& messagePorts, Vector>& arrayBuffers, const Vector>& imageBitmaps, + // #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + // const Vector>& offscreenCanvases, + // #endif + // #if ENABLE(WEB_RTC) + // const Vector>& rtcDataChannels, + // #endif + // #if ENABLE(WEB_CODECS) + // Vector>& serializedVideoChunks, + // Vector>& serializedVideoFrames, + // #endif + // #if ENABLE(WEBASSEMBLY) + // WasmModuleArray& wasmModules, + // WasmMemoryHandleArray& wasmMemoryHandles, + // #endif + // Vector& blobHandles, Vector& out, SerializationContext context, ArrayBufferContentsArray& sharedBuffers, + // SerializationForStorage forStorage) + // { + // CloneSerializer serializer(lexicalGlobalObject, messagePorts, arrayBuffers, imageBitmaps, + // #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + // offscreenCanvases, + // #endif + // #if ENABLE(WEB_RTC) + // rtcDataChannels, + // #endif + // #if ENABLE(WEB_CODECS) + // serializedVideoChunks, + // serializedVideoFrames, + // #endif + // #if ENABLE(WEBASSEMBLY) + // wasmModules, + // wasmMemoryHandles, + // #endif + // blobHandles, out, context, sharedBuffers, forStorage); + // return serializer.serialize(value); + // } + + static SerializationReturnCode serialize(JSGlobalObject* lexicalGlobalObject, JSValue value, Vector>& arrayBuffers, #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) const Vector>& offscreenCanvases, #endif @@ -679,10 +716,10 @@ class CloneSerializer : CloneBase { WasmModuleArray& wasmModules, WasmMemoryHandleArray& wasmMemoryHandles, #endif - Vector& blobHandles, Vector& out, SerializationContext context, ArrayBufferContentsArray& sharedBuffers, + Vector& out, SerializationContext context, ArrayBufferContentsArray& sharedBuffers, SerializationForStorage forStorage) { - CloneSerializer serializer(lexicalGlobalObject, messagePorts, arrayBuffers, imageBitmaps, + CloneSerializer serializer(lexicalGlobalObject, arrayBuffers, #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) offscreenCanvases, #endif @@ -697,7 +734,7 @@ class CloneSerializer : CloneBase { wasmModules, wasmMemoryHandles, #endif - blobHandles, out, context, sharedBuffers, forStorage); + out, context, sharedBuffers, forStorage); return serializer.serialize(value); } @@ -720,7 +757,51 @@ class CloneSerializer : CloneBase { private: typedef HashMap ObjectPool; - CloneSerializer(JSGlobalObject* lexicalGlobalObject, Vector>& messagePorts, Vector>& arrayBuffers, const Vector>& imageBitmaps, + // CloneSerializer(JSGlobalObject* lexicalGlobalObject, Vector>& messagePorts, Vector>& arrayBuffers, const Vector>& imageBitmaps, + // #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + // const Vector>& offscreenCanvases, + // #endif + // #if ENABLE(WEB_RTC) + // const Vector>& rtcDataChannels, + // #endif + // #if ENABLE(WEB_CODECS) + // Vector>& serializedVideoChunks, + // Vector>& serializedVideoFrames, + // #endif + // #if ENABLE(WEBASSEMBLY) + // WasmModuleArray& wasmModules, + // WasmMemoryHandleArray& wasmMemoryHandles, + // #endif + // Vector& blobHandles, Vector& out, SerializationContext context, ArrayBufferContentsArray& sharedBuffers, SerializationForStorage forStorage) + // : CloneBase(lexicalGlobalObject) + // , m_buffer(out) + // , m_blobHandles(blobHandles) + // , m_emptyIdentifier(Identifier::fromString(lexicalGlobalObject->vm(), emptyString())) + // , m_context(context) + // , m_sharedBuffers(sharedBuffers) + // #if ENABLE(WEBASSEMBLY) + // , m_wasmModules(wasmModules) + // , m_wasmMemoryHandles(wasmMemoryHandles) + // #endif + // #if ENABLE(WEB_CODECS) + // , m_serializedVideoChunks(serializedVideoChunks) + // , m_serializedVideoFrames(serializedVideoFrames) + // #endif + // , m_forStorage(forStorage) + // { + // write(CurrentVersion); + // fillTransferMap(messagePorts, m_transferredMessagePorts); + // fillTransferMap(arrayBuffers, m_transferredArrayBuffers); + // fillTransferMap(imageBitmaps, m_transferredImageBitmaps); + // #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + // fillTransferMap(offscreenCanvases, m_transferredOffscreenCanvases); + // #endif + // #if ENABLE(WEB_RTC) + // fillTransferMap(rtcDataChannels, m_transferredRTCDataChannels); + // #endif + // } + + CloneSerializer(JSGlobalObject* lexicalGlobalObject, Vector>& arrayBuffers, #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) const Vector>& offscreenCanvases, #endif @@ -735,10 +816,9 @@ class CloneSerializer : CloneBase { WasmModuleArray& wasmModules, WasmMemoryHandleArray& wasmMemoryHandles, #endif - Vector& blobHandles, Vector& out, SerializationContext context, ArrayBufferContentsArray& sharedBuffers, SerializationForStorage forStorage) + Vector& out, SerializationContext context, ArrayBufferContentsArray& sharedBuffers, SerializationForStorage forStorage) : CloneBase(lexicalGlobalObject) , m_buffer(out) - , m_blobHandles(blobHandles) , m_emptyIdentifier(Identifier::fromString(lexicalGlobalObject->vm(), emptyString())) , m_context(context) , m_sharedBuffers(sharedBuffers) @@ -753,9 +833,7 @@ class CloneSerializer : CloneBase { , m_forStorage(forStorage) { write(CurrentVersion); - fillTransferMap(messagePorts, m_transferredMessagePorts); fillTransferMap(arrayBuffers, m_transferredArrayBuffers); - fillTransferMap(imageBitmaps, m_transferredImageBitmaps); #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) fillTransferMap(offscreenCanvases, m_transferredOffscreenCanvases); #endif @@ -1089,137 +1167,137 @@ class CloneSerializer : CloneBase { return dumpIfTerminal(toJSArrayBuffer(*arrayBuffer), code); } - void dumpDOMPoint(const DOMPointReadOnly& point) - { - write(point.x()); - write(point.y()); - write(point.z()); - write(point.w()); - } - - void dumpDOMPoint(JSObject* obj) - { - if (obj->inherits()) - write(DOMPointTag); - else - write(DOMPointReadOnlyTag); - - dumpDOMPoint(jsCast(obj)->wrapped()); - } - - void dumpDOMRect(JSObject* obj) - { - if (obj->inherits()) - write(DOMRectTag); - else - write(DOMRectReadOnlyTag); - - auto& rect = jsCast(obj)->wrapped(); - write(rect.x()); - write(rect.y()); - write(rect.width()); - write(rect.height()); - } - - void dumpDOMMatrix(JSObject* obj) - { - if (obj->inherits()) - write(DOMMatrixTag); - else - write(DOMMatrixReadOnlyTag); - - auto& matrix = jsCast(obj)->wrapped(); - bool is2D = matrix.is2D(); - write(static_cast(is2D)); - if (is2D) { - write(matrix.m11()); - write(matrix.m12()); - write(matrix.m21()); - write(matrix.m22()); - write(matrix.m41()); - write(matrix.m42()); - } else { - write(matrix.m11()); - write(matrix.m12()); - write(matrix.m13()); - write(matrix.m14()); - write(matrix.m21()); - write(matrix.m22()); - write(matrix.m23()); - write(matrix.m24()); - write(matrix.m31()); - write(matrix.m32()); - write(matrix.m33()); - write(matrix.m34()); - write(matrix.m41()); - write(matrix.m42()); - write(matrix.m43()); - write(matrix.m44()); - } - } - - void dumpDOMQuad(JSObject* obj) - { - write(DOMQuadTag); - - auto& quad = jsCast(obj)->wrapped(); - dumpDOMPoint(quad.p1()); - dumpDOMPoint(quad.p2()); - dumpDOMPoint(quad.p3()); - dumpDOMPoint(quad.p4()); - } - - void dumpImageBitmap(JSObject* obj, SerializationReturnCode& code) - { - auto index = m_transferredImageBitmaps.find(obj); - if (index != m_transferredImageBitmaps.end()) { - write(ImageBitmapTransferTag); - write(index->value); - return; - } - - auto& imageBitmap = jsCast(obj)->wrapped(); - if (!imageBitmap.originClean()) { - code = SerializationReturnCode::DataCloneError; - return; - } - - auto* buffer = imageBitmap.buffer(); - if (!buffer) { - code = SerializationReturnCode::ValidationError; - return; - } - - // FIXME: We should try to avoid converting pixel format. - PixelBufferFormat format { AlphaPremultiplication::Premultiplied, PixelFormat::RGBA8, buffer->colorSpace() }; - const IntSize& logicalSize = buffer->truncatedLogicalSize(); - auto pixelBuffer = buffer->getPixelBuffer(format, { IntPoint::zero(), logicalSize }); - if (!is(pixelBuffer)) { - code = SerializationReturnCode::ValidationError; - return; - } - - auto arrayBuffer = downcast(*pixelBuffer).data().possiblySharedBuffer(); - if (!arrayBuffer) { - code = SerializationReturnCode::ValidationError; - return; - } - - write(ImageBitmapTag); - write(static_cast(imageBitmap.serializationState().toRaw())); - write(static_cast(logicalSize.width())); - write(static_cast(logicalSize.height())); - write(static_cast(buffer->resolutionScale())); - write(buffer->colorSpace()); - - CheckedUint32 byteLength = arrayBuffer->byteLength(); - if (byteLength.hasOverflowed()) { - code = SerializationReturnCode::ValidationError; - return; - } - write(byteLength); - write(static_cast(arrayBuffer->data()), byteLength); - } + // void dumpDOMPoint(const DOMPointReadOnly& point) + // { + // write(point.x()); + // write(point.y()); + // write(point.z()); + // write(point.w()); + // } + + // void dumpDOMPoint(JSObject* obj) + // { + // if (obj->inherits()) + // write(DOMPointTag); + // else + // write(DOMPointReadOnlyTag); + + // dumpDOMPoint(jsCast(obj)->wrapped()); + // } + + // void dumpDOMRect(JSObject* obj) + // { + // if (obj->inherits()) + // write(DOMRectTag); + // else + // write(DOMRectReadOnlyTag); + + // auto& rect = jsCast(obj)->wrapped(); + // write(rect.x()); + // write(rect.y()); + // write(rect.width()); + // write(rect.height()); + // } + + // void dumpDOMMatrix(JSObject* obj) + // { + // if (obj->inherits()) + // write(DOMMatrixTag); + // else + // write(DOMMatrixReadOnlyTag); + + // auto& matrix = jsCast(obj)->wrapped(); + // bool is2D = matrix.is2D(); + // write(static_cast(is2D)); + // if (is2D) { + // write(matrix.m11()); + // write(matrix.m12()); + // write(matrix.m21()); + // write(matrix.m22()); + // write(matrix.m41()); + // write(matrix.m42()); + // } else { + // write(matrix.m11()); + // write(matrix.m12()); + // write(matrix.m13()); + // write(matrix.m14()); + // write(matrix.m21()); + // write(matrix.m22()); + // write(matrix.m23()); + // write(matrix.m24()); + // write(matrix.m31()); + // write(matrix.m32()); + // write(matrix.m33()); + // write(matrix.m34()); + // write(matrix.m41()); + // write(matrix.m42()); + // write(matrix.m43()); + // write(matrix.m44()); + // } + // } + + // void dumpDOMQuad(JSObject* obj) + // { + // write(DOMQuadTag); + + // auto& quad = jsCast(obj)->wrapped(); + // dumpDOMPoint(quad.p1()); + // dumpDOMPoint(quad.p2()); + // dumpDOMPoint(quad.p3()); + // dumpDOMPoint(quad.p4()); + // } + + // void dumpImageBitmap(JSObject* obj, SerializationReturnCode& code) + // { + // auto index = m_transferredImageBitmaps.find(obj); + // if (index != m_transferredImageBitmaps.end()) { + // write(ImageBitmapTransferTag); + // write(index->value); + // return; + // } + + // auto& imageBitmap = jsCast(obj)->wrapped(); + // if (!imageBitmap.originClean()) { + // code = SerializationReturnCode::DataCloneError; + // return; + // } + + // auto* buffer = imageBitmap.buffer(); + // if (!buffer) { + // code = SerializationReturnCode::ValidationError; + // return; + // } + + // // FIXME: We should try to avoid converting pixel format. + // PixelBufferFormat format { AlphaPremultiplication::Premultiplied, PixelFormat::RGBA8, buffer->colorSpace() }; + // const IntSize& logicalSize = buffer->truncatedLogicalSize(); + // auto pixelBuffer = buffer->getPixelBuffer(format, { IntPoint::zero(), logicalSize }); + // if (!is(pixelBuffer)) { + // code = SerializationReturnCode::ValidationError; + // return; + // } + + // auto arrayBuffer = downcast(*pixelBuffer).data().possiblySharedBuffer(); + // if (!arrayBuffer) { + // code = SerializationReturnCode::ValidationError; + // return; + // } + + // write(ImageBitmapTag); + // write(static_cast(imageBitmap.serializationState().toRaw())); + // write(static_cast(logicalSize.width())); + // write(static_cast(logicalSize.height())); + // write(static_cast(buffer->resolutionScale())); + // write(buffer->colorSpace()); + + // CheckedUint32 byteLength = arrayBuffer->byteLength(); + // if (byteLength.hasOverflowed()) { + // code = SerializationReturnCode::ValidationError; + // return; + // } + // write(byteLength); + // write(static_cast(arrayBuffer->data()), byteLength); + // } #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) void dumpOffscreenCanvas(JSObject* obj, SerializationReturnCode& code) @@ -1356,61 +1434,61 @@ class CloneSerializer : CloneBase { dumpBigIntData(bigIntValue); return true; } - if (auto* file = JSFile::toWrapped(vm, obj)) { - write(FileTag); - write(*file); - return true; - } - if (auto* list = JSFileList::toWrapped(vm, obj)) { - write(FileListTag); - write(list->length()); - for (auto& file : list->files()) - write(file.get()); - return true; - } - if (auto* blob = JSBlob::toWrapped(vm, obj)) { - write(BlobTag); - m_blobHandles.append(blob->handle().isolatedCopy()); - write(blob->url().string()); - write(blob->type()); - static_assert(sizeof(uint64_t) == sizeof(decltype(blob->size()))); - uint64_t size = blob->size(); - write(size); - uint64_t memoryCost = blob->memoryCost(); - write(memoryCost); - return true; - } - if (auto* data = JSImageData::toWrapped(vm, obj)) { - write(ImageDataTag); - write(data->width()); - write(data->height()); - CheckedUint32 dataLength = data->data().length(); - if (dataLength.hasOverflowed()) { - code = SerializationReturnCode::DataCloneError; - return true; - } - write(dataLength); - write(data->data().data(), dataLength); - write(data->colorSpace()); - return true; - } + // if (auto* file = JSFile::toWrapped(vm, obj)) { + // write(FileTag); + // write(*file); + // return true; + // } + // if (auto* list = JSFileList::toWrapped(vm, obj)) { + // write(FileListTag); + // write(list->length()); + // for (auto& file : list->files()) + // write(file.get()); + // return true; + // } + // if (auto* blob = JSBlob::toWrapped(vm, obj)) { + // write(BlobTag); + // m_blobHandles.append(blob->handle().isolatedCopy()); + // write(blob->url().string()); + // write(blob->type()); + // static_assert(sizeof(uint64_t) == sizeof(decltype(blob->size()))); + // uint64_t size = blob->size(); + // write(size); + // uint64_t memoryCost = blob->memoryCost(); + // write(memoryCost); + // return true; + // } + // if (auto* data = JSImageData::toWrapped(vm, obj)) { + // write(ImageDataTag); + // write(data->width()); + // write(data->height()); + // CheckedUint32 dataLength = data->data().length(); + // if (dataLength.hasOverflowed()) { + // code = SerializationReturnCode::DataCloneError; + // return true; + // } + // write(dataLength); + // write(data->data().data(), dataLength); + // write(data->colorSpace()); + // return true; + // } if (auto* regExp = jsDynamicCast(obj)) { write(RegExpTag); write(regExp->regExp()->pattern()); write(String::fromLatin1(JSC::Yarr::flagsString(regExp->regExp()->flags()).data())); return true; } - if (obj->inherits()) { - auto index = m_transferredMessagePorts.find(obj); - if (index != m_transferredMessagePorts.end()) { - write(MessagePortReferenceTag); - write(index->value); - return true; - } - // MessagePort object could not be found in transferred message ports - code = SerializationReturnCode::ValidationError; - return true; - } + // if (obj->inherits()) { + // auto index = m_transferredMessagePorts.find(obj); + // if (index != m_transferredMessagePorts.end()) { + // write(MessagePortReferenceTag); + // write(index->value); + // return true; + // } + // // MessagePort object could not be found in transferred message ports + // code = SerializationReturnCode::ValidationError; + // return true; + // } if (auto* arrayBuffer = toPossiblySharedArrayBuffer(vm, obj)) { if (arrayBuffer->isDetached()) { code = SerializationReturnCode::ValidationError; @@ -1468,8 +1546,8 @@ class CloneSerializer : CloneBase { if (auto* key = JSCryptoKey::toWrapped(vm, obj)) { write(CryptoKeyTag); Vector serializedKey; - Vector dummyBlobHandles; - Vector> dummyMessagePorts; + // Vector dummyBlobHandles; + // Vector> dummyMessagePorts; Vector> dummyArrayBuffers; #if ENABLE(WEB_CODECS) Vector> dummyVideoChunks; @@ -1480,7 +1558,23 @@ class CloneSerializer : CloneBase { WasmMemoryHandleArray dummyMemoryHandles; #endif ArrayBufferContentsArray dummySharedBuffers; - CloneSerializer rawKeySerializer(m_lexicalGlobalObject, dummyMessagePorts, dummyArrayBuffers, {}, + // CloneSerializer rawKeySerializer(m_lexicalGlobalObject, dummyMessagePorts, dummyArrayBuffers, {}, + // #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + // {}, + // #endif + // #if ENABLE(WEB_RTC) + // {}, + // #endif + // #if ENABLE(WEB_CODECS) + // dummyVideoChunks, + // dummyVideoFrames, + // #endif + // #if ENABLE(WEBASSEMBLY) + // dummyModules, + // dummyMemoryHandles, + // #endif + // dummyBlobHandles, serializedKey, SerializationContext::Default, dummySharedBuffers, m_forStorage); + CloneSerializer rawKeySerializer(m_lexicalGlobalObject, dummyArrayBuffers, #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) {}, #endif @@ -1495,7 +1589,7 @@ class CloneSerializer : CloneBase { dummyModules, dummyMemoryHandles, #endif - dummyBlobHandles, serializedKey, SerializationContext::Default, dummySharedBuffers, m_forStorage); + serializedKey, SerializationContext::Default, dummySharedBuffers, m_forStorage); rawKeySerializer.write(key); Vector wrappedKey; if (!wrapCryptoKey(m_lexicalGlobalObject, serializedKey, wrappedKey)) @@ -1548,26 +1642,26 @@ class CloneSerializer : CloneBase { return true; } #endif - if (obj->inherits()) { - dumpDOMPoint(obj); - return true; - } - if (obj->inherits()) { - dumpDOMRect(obj); - return true; - } - if (obj->inherits()) { - dumpDOMMatrix(obj); - return true; - } - if (obj->inherits()) { - dumpDOMQuad(obj); - return true; - } - if (obj->inherits()) { - dumpImageBitmap(obj, code); - return true; - } + // if (obj->inherits()) { + // dumpDOMPoint(obj); + // return true; + // } + // if (obj->inherits()) { + // dumpDOMRect(obj); + // return true; + // } + // if (obj->inherits()) { + // dumpDOMMatrix(obj); + // return true; + // } + // if (obj->inherits()) { + // dumpDOMQuad(obj); + // return true; + // } + // if (obj->inherits()) { + // dumpImageBitmap(obj, code); + // return true; + // } #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) if (obj->inherits()) { dumpOffscreenCanvas(obj, code); @@ -1752,29 +1846,29 @@ class CloneSerializer : CloneBase { writeLittleEndian(m_buffer, vector.data(), size); } - void write(const File& file) - { - m_blobHandles.append(file.handle().isolatedCopy()); - write(file.path()); - write(file.url().string()); - write(file.type()); - write(file.name()); - write(static_cast(file.lastModifiedOverride().value_or(-1))); - } - - void write(PredefinedColorSpace colorSpace) - { - switch (colorSpace) { - case PredefinedColorSpace::SRGB: - writeLittleEndian(m_buffer, static_cast(PredefinedColorSpaceTag::SRGB)); - break; -#if ENABLE(PREDEFINED_COLOR_SPACE_DISPLAY_P3) - case PredefinedColorSpace::DisplayP3: - writeLittleEndian(m_buffer, static_cast(PredefinedColorSpaceTag::DisplayP3)); - break; -#endif - } - } + // void write(const File& file) + // { + // m_blobHandles.append(file.handle().isolatedCopy()); + // write(file.path()); + // write(file.url().string()); + // write(file.type()); + // write(file.name()); + // write(static_cast(file.lastModifiedOverride().value_or(-1))); + // } + + // void write(PredefinedColorSpace colorSpace) + // { + // switch (colorSpace) { + // case PredefinedColorSpace::SRGB: + // writeLittleEndian(m_buffer, static_cast(PredefinedColorSpaceTag::SRGB)); + // break; + // #if ENABLE(PREDEFINED_COLOR_SPACE_DISPLAY_P3) + // case PredefinedColorSpace::DisplayP3: + // writeLittleEndian(m_buffer, static_cast(PredefinedColorSpaceTag::DisplayP3)); + // break; + // #endif + // } + // } #if PLATFORM(COCOA) void write(const RetainPtr& data) @@ -1785,58 +1879,58 @@ class CloneSerializer : CloneBase { } #endif - void write(DestinationColorSpace destinationColorSpace) - { - if (destinationColorSpace == DestinationColorSpace::SRGB()) { - write(DestinationColorSpaceSRGBTag); - return; - } - -#if ENABLE(DESTINATION_COLOR_SPACE_LINEAR_SRGB) - if (destinationColorSpace == DestinationColorSpace::LinearSRGB()) { - write(DestinationColorSpaceLinearSRGBTag); - return; - } -#endif - -#if ENABLE(DESTINATION_COLOR_SPACE_DISPLAY_P3) - if (destinationColorSpace == DestinationColorSpace::DisplayP3()) { - write(DestinationColorSpaceDisplayP3Tag); - return; - } -#endif - -#if PLATFORM(COCOA) - auto colorSpace = destinationColorSpace.platformColorSpace(); - - if (auto name = CGColorSpaceGetName(colorSpace)) { - auto data = adoptCF(CFStringCreateExternalRepresentation(nullptr, name, kCFStringEncodingUTF8, 0)); - if (!data) { - write(DestinationColorSpaceSRGBTag); - return; - } - - write(DestinationColorSpaceCGColorSpaceNameTag); - write(data); - return; - } - - if (auto propertyList = adoptCF(CGColorSpaceCopyPropertyList(colorSpace))) { - auto data = adoptCF(CFPropertyListCreateData(nullptr, propertyList.get(), kCFPropertyListBinaryFormat_v1_0, 0, nullptr)); - if (!data) { - write(DestinationColorSpaceSRGBTag); - return; - } - - write(DestinationColorSpaceCGColorSpacePropertyListTag); - write(data); - return; - } -#endif - - ASSERT_NOT_REACHED(); - write(DestinationColorSpaceSRGBTag); - } + // void write(DestinationColorSpace destinationColorSpace) + // { + // if (destinationColorSpace == DestinationColorSpace::SRGB()) { + // write(DestinationColorSpaceSRGBTag); + // return; + // } + + // #if ENABLE(DESTINATION_COLOR_SPACE_LINEAR_SRGB) + // if (destinationColorSpace == DestinationColorSpace::LinearSRGB()) { + // write(DestinationColorSpaceLinearSRGBTag); + // return; + // } + // #endif + + // #if ENABLE(DESTINATION_COLOR_SPACE_DISPLAY_P3) + // if (destinationColorSpace == DestinationColorSpace::DisplayP3()) { + // write(DestinationColorSpaceDisplayP3Tag); + // return; + // } + // #endif + + // #if PLATFORM(COCOA) + // auto colorSpace = destinationColorSpace.platformColorSpace(); + + // if (auto name = CGColorSpaceGetName(colorSpace)) { + // auto data = adoptCF(CFStringCreateExternalRepresentation(nullptr, name, kCFStringEncodingUTF8, 0)); + // if (!data) { + // write(DestinationColorSpaceSRGBTag); + // return; + // } + + // write(DestinationColorSpaceCGColorSpaceNameTag); + // write(data); + // return; + // } + + // if (auto propertyList = adoptCF(CGColorSpaceCopyPropertyList(colorSpace))) { + // auto data = adoptCF(CFPropertyListCreateData(nullptr, propertyList.get(), kCFPropertyListBinaryFormat_v1_0, 0, nullptr)); + // if (!data) { + // write(DestinationColorSpaceSRGBTag); + // return; + // } + + // write(DestinationColorSpaceCGColorSpacePropertyListTag); + // write(data); + // return; + // } + // #endif + + // ASSERT_NOT_REACHED(); + // write(DestinationColorSpaceSRGBTag); + // } #if ENABLE(WEB_CRYPTO) void write(CryptoKeyOKP::NamedCurve curve) @@ -2048,7 +2142,7 @@ class CloneSerializer : CloneBase { } Vector& m_buffer; - Vector& m_blobHandles; + // Vector& m_blobHandles; ObjectPool m_objectPool; ObjectPool m_transferredMessagePorts; ObjectPool m_transferredArrayBuffers; @@ -2357,7 +2451,53 @@ class CloneDeserializer : CloneBase { return str; } - static DeserializationResult deserialize(JSGlobalObject* lexicalGlobalObject, JSGlobalObject* globalObject, const Vector>& messagePorts, Vector>&& backingStores + // static DeserializationResult deserialize(JSGlobalObject* lexicalGlobalObject, JSGlobalObject* globalObject, const Vector>& messagePorts, Vector>&& backingStores + // #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + // , + // Vector>&& detachedOffscreenCanvases + // #endif + // #if ENABLE(WEB_RTC) + // , + // Vector>&& detachedRTCDataChannels + // #endif + // , + // ArrayBufferContentsArray* arrayBufferContentsArray, const Vector& buffer, const Vector& blobURLs, const Vector blobFilePaths, ArrayBufferContentsArray* sharedBuffers + // #if ENABLE(WEBASSEMBLY) + // , + // WasmModuleArray* wasmModules, WasmMemoryHandleArray* wasmMemoryHandles + // #endif + // #if ENABLE(WEB_CODECS) + // , + // Vector>&& serializedVideoChunks, Vector&& serializedVideoFrames + // #endif + // ) + // { + // if (!buffer.size()) + // return std::make_pair(jsNull(), SerializationReturnCode::UnspecifiedError); + // CloneDeserializer deserializer(lexicalGlobalObject, globalObject, messagePorts, arrayBufferContentsArray, buffer, blobURLs, blobFilePaths, sharedBuffers, WTFMove(backingStores) + // #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + // , + // WTFMove(detachedOffscreenCanvases) + // #endif + // #if ENABLE(WEB_RTC) + // , + // WTFMove(detachedRTCDataChannels) + // #endif + // #if ENABLE(WEBASSEMBLY) + // , + // wasmModules, wasmMemoryHandles + // #endif + // #if ENABLE(WEB_CODECS) + // , + // WTFMove(serializedVideoChunks), WTFMove(serializedVideoFrames) + // #endif + // ); + // if (!deserializer.isValid()) + // return std::make_pair(JSValue(), SerializationReturnCode::ValidationError); + // return deserializer.deserialize(); + // } + + static DeserializationResult deserialize(JSGlobalObject* lexicalGlobalObject, JSGlobalObject* globalObject #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) , Vector>&& detachedOffscreenCanvases @@ -2380,9 +2520,9 @@ class CloneDeserializer : CloneBase { { if (!buffer.size()) return std::make_pair(jsNull(), SerializationReturnCode::UnspecifiedError); - CloneDeserializer deserializer(lexicalGlobalObject, globalObject, messagePorts, arrayBufferContentsArray, buffer, blobURLs, blobFilePaths, sharedBuffers, WTFMove(backingStores) + CloneDeserializer deserializer(lexicalGlobalObject, globalObject, arrayBufferContentsArray, buffer, blobURLs, blobFilePaths, sharedBuffers #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) - , + , WTFMove(detachedOffscreenCanvases) #endif #if ENABLE(WEB_RTC) @@ -2447,7 +2587,60 @@ class CloneDeserializer : CloneBase { size_t m_index; }; - CloneDeserializer(JSGlobalObject* lexicalGlobalObject, JSGlobalObject* globalObject, const Vector>& messagePorts, ArrayBufferContentsArray* arrayBufferContents, Vector>&& backingStores, const Vector& buffer + // CloneDeserializer(JSGlobalObject* lexicalGlobalObject, JSGlobalObject* globalObject, const Vector>& messagePorts, ArrayBufferContentsArray* arrayBufferContents, Vector>&& backingStores, const Vector& buffer + // #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + // , + // Vector>&& detachedOffscreenCanvases = {} + // #endif + // #if ENABLE(WEB_RTC) + // , + // Vector>&& detachedRTCDataChannels = {} + // #endif + // #if ENABLE(WEBASSEMBLY) + // , + // WasmModuleArray* wasmModules = nullptr, WasmMemoryHandleArray* wasmMemoryHandles = nullptr + // #endif + // #if ENABLE(WEB_CODECS) + // , + // Vector>&& serializedVideoChunks = {}, Vector&& serializedVideoFrames = {} + // #endif + // ) + // : CloneBase(lexicalGlobalObject) + // , m_globalObject(globalObject) + // , m_isDOMGlobalObject(globalObject->inherits()) + // , m_canCreateDOMObject(m_isDOMGlobalObject && !globalObject->inherits()) + // , m_ptr(buffer.data()) + // , m_end(buffer.data() + buffer.size()) + // , m_version(0xFFFFFFFF) + // , m_messagePorts(messagePorts) + // , m_arrayBufferContents(arrayBufferContents) + // , m_arrayBuffers(arrayBufferContents ? arrayBufferContents->size() : 0) + // , m_backingStores(WTFMove(backingStores)) + // , m_imageBitmaps(m_backingStores.size()) + // #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + // , m_detachedOffscreenCanvases(WTFMove(detachedOffscreenCanvases)) + // , m_offscreenCanvases(m_detachedOffscreenCanvases.size()) + // #endif + // #if ENABLE(WEB_RTC) + // , m_detachedRTCDataChannels(WTFMove(detachedRTCDataChannels)) + // , m_rtcDataChannels(m_detachedRTCDataChannels.size()) + // #endif + // #if ENABLE(WEBASSEMBLY) + // , m_wasmModules(wasmModules) + // , m_wasmMemoryHandles(wasmMemoryHandles) + // #endif + // #if ENABLE(WEB_CODECS) + // , m_serializedVideoChunks(WTFMove(serializedVideoChunks)) + // , m_videoChunks(m_serializedVideoChunks.size()) + // , m_serializedVideoFrames(WTFMove(serializedVideoFrames)) + // , m_videoFrames(m_serializedVideoFrames.size()) + // #endif + // { + // if (!read(m_version)) + // m_version = 0xFFFFFFFF; + // } + + CloneDeserializer(JSGlobalObject* lexicalGlobalObject, JSGlobalObject* globalObject, ArrayBufferContentsArray* arrayBufferContents, const Vector& buffer #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) , Vector>&& detachedOffscreenCanvases = {} @@ -2468,15 +2661,12 @@ class CloneDeserializer : CloneBase { : CloneBase(lexicalGlobalObject) , m_globalObject(globalObject) , m_isDOMGlobalObject(globalObject->inherits()) - , m_canCreateDOMObject(m_isDOMGlobalObject && !globalObject->inherits()) + , m_canCreateDOMObject(m_isDOMGlobalObject) , m_ptr(buffer.data()) , m_end(buffer.data() + buffer.size()) , m_version(0xFFFFFFFF) - , m_messagePorts(messagePorts) , m_arrayBufferContents(arrayBufferContents) , m_arrayBuffers(arrayBufferContents ? arrayBufferContents->size() : 0) - , m_backingStores(WTFMove(backingStores)) - , m_imageBitmaps(m_backingStores.size()) #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) , m_detachedOffscreenCanvases(WTFMove(detachedOffscreenCanvases)) , m_offscreenCanvases(m_detachedOffscreenCanvases.size()) @@ -2500,7 +2690,63 @@ class CloneDeserializer : CloneBase { m_version = 0xFFFFFFFF; } - CloneDeserializer(JSGlobalObject* lexicalGlobalObject, JSGlobalObject* globalObject, const Vector>& messagePorts, ArrayBufferContentsArray* arrayBufferContents, const Vector& buffer, const Vector& blobURLs, const Vector blobFilePaths, ArrayBufferContentsArray* sharedBuffers, Vector>&& backingStores + // CloneDeserializer(JSGlobalObject* lexicalGlobalObject, JSGlobalObject* globalObject, const Vector>& messagePorts, ArrayBufferContentsArray* arrayBufferContents, const Vector& buffer, const Vector& blobURLs, const Vector blobFilePaths, ArrayBufferContentsArray* sharedBuffers, Vector>&& backingStores + // #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + // , + // Vector>&& detachedOffscreenCanvases + // #endif + // #if ENABLE(WEB_RTC) + // , + // Vector>&& detachedRTCDataChannels + // #endif + // #if ENABLE(WEBASSEMBLY) + // , + // WasmModuleArray* wasmModules, WasmMemoryHandleArray* wasmMemoryHandles + // #endif + // #if ENABLE(WEB_CODECS) + // , + // Vector>&& serializedVideoChunks = {}, Vector&& serializedVideoFrames = {} + // #endif + // ) + // : CloneBase(lexicalGlobalObject) + // , m_globalObject(globalObject) + // , m_isDOMGlobalObject(globalObject->inherits()) + // , m_canCreateDOMObject(m_isDOMGlobalObject && !globalObject->inherits()) + // , m_ptr(buffer.data()) + // , m_end(buffer.data() + buffer.size()) + // , m_version(0xFFFFFFFF) + // , m_messagePorts(messagePorts) + // , m_arrayBufferContents(arrayBufferContents) + // , m_arrayBuffers(arrayBufferContents ? arrayBufferContents->size() : 0) + // , m_blobURLs(blobURLs) + // , m_blobFilePaths(blobFilePaths) + // , m_sharedBuffers(sharedBuffers) + // , m_backingStores(WTFMove(backingStores)) + // , m_imageBitmaps(m_backingStores.size()) + // #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + // , m_detachedOffscreenCanvases(WTFMove(detachedOffscreenCanvases)) + // , m_offscreenCanvases(m_detachedOffscreenCanvases.size()) + // #endif + // #if ENABLE(WEB_RTC) + // , m_detachedRTCDataChannels(WTFMove(detachedRTCDataChannels)) + // , m_rtcDataChannels(m_detachedRTCDataChannels.size()) + // #endif + // #if ENABLE(WEBASSEMBLY) + // , m_wasmModules(wasmModules) + // , m_wasmMemoryHandles(wasmMemoryHandles) + // #endif + // #if ENABLE(WEB_CODECS) + // , m_serializedVideoChunks(WTFMove(serializedVideoChunks)) + // , m_videoChunks(m_serializedVideoChunks.size()) + // , m_serializedVideoFrames(WTFMove(serializedVideoFrames)) + // , m_videoFrames(m_serializedVideoFrames.size()) + // #endif + // { + // if (!read(m_version)) + // m_version = 0xFFFFFFFF; + // } + + CloneDeserializer(JSGlobalObject* lexicalGlobalObject, JSGlobalObject* globalObject, ArrayBufferContentsArray* arrayBufferContents, const Vector& buffer, const Vector& blobURLs, const Vector blobFilePaths, ArrayBufferContentsArray* sharedBuffers #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) , Vector>&& detachedOffscreenCanvases @@ -2521,18 +2767,15 @@ class CloneDeserializer : CloneBase { : CloneBase(lexicalGlobalObject) , m_globalObject(globalObject) , m_isDOMGlobalObject(globalObject->inherits()) - , m_canCreateDOMObject(m_isDOMGlobalObject && !globalObject->inherits()) + , m_canCreateDOMObject(m_isDOMGlobalObject) , m_ptr(buffer.data()) , m_end(buffer.data() + buffer.size()) , m_version(0xFFFFFFFF) - , m_messagePorts(messagePorts) , m_arrayBufferContents(arrayBufferContents) , m_arrayBuffers(arrayBufferContents ? arrayBufferContents->size() : 0) , m_blobURLs(blobURLs) , m_blobFilePaths(blobFilePaths) , m_sharedBuffers(sharedBuffers) - , m_backingStores(WTFMove(backingStores)) - , m_imageBitmaps(m_backingStores.size()) #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) , m_detachedOffscreenCanvases(WTFMove(detachedOffscreenCanvases)) , m_offscreenCanvases(m_detachedOffscreenCanvases.size()) @@ -2759,40 +3002,40 @@ class CloneDeserializer : CloneBase { object->putDirectMayBeIndex(m_lexicalGlobalObject, property, value); } - bool readFile(RefPtr& file) - { - CachedStringRef path; - if (!readStringData(path)) - return false; - CachedStringRef url; - if (!readStringData(url)) - return false; - CachedStringRef type; - if (!readStringData(type)) - return false; - CachedStringRef name; - if (!readStringData(name)) - return false; - std::optional optionalLastModified; - if (m_version > 6) { - double lastModified; - if (!read(lastModified)) - return false; - if (lastModified >= 0) - optionalLastModified = lastModified; - } - - // If the blob URL for this file has an associated blob file path, prefer that one over the "built-in" path. - String filePath = blobFilePathForBlobURL(url->string()); - if (filePath.isEmpty()) - filePath = path->string(); - - if (!m_canCreateDOMObject) - return true; - - file = File::deserialize(executionContext(m_lexicalGlobalObject), filePath, URL { url->string() }, type->string(), name->string(), optionalLastModified); - return true; - } + // bool readFile(RefPtr& file) + // { + // CachedStringRef path; + // if (!readStringData(path)) + // return false; + // CachedStringRef url; + // if (!readStringData(url)) + // return false; + // CachedStringRef type; + // if (!readStringData(type)) + // return false; + // CachedStringRef name; + // if (!readStringData(name)) + // return false; + // std::optional optionalLastModified; + // if (m_version > 6) { + // double lastModified; + // if (!read(lastModified)) + // return false; + // if (lastModified >= 0) + // optionalLastModified = lastModified; + // } + + // // If the blob URL for this file has an associated blob file path, prefer that one over the "built-in" path. + // String filePath = blobFilePathForBlobURL(url->string()); + // if (filePath.isEmpty()) + // filePath = path->string(); + + // if (!m_canCreateDOMObject) + // return true; + + // file = File::deserialize(executionContext(m_lexicalGlobalObject), filePath, URL { url->string() }, type->string(), name->string(), optionalLastModified); + // return true; + // } template bool readArrayBufferImpl(RefPtr& arrayBuffer) @@ -2935,33 +3178,33 @@ class CloneDeserializer : CloneBase { return true; } - bool read(PredefinedColorSpace& result) - { - uint8_t tag; - if (!read(tag)) - return false; - - switch (static_cast(tag)) { - case PredefinedColorSpaceTag::SRGB: - result = PredefinedColorSpace::SRGB; - return true; -#if ENABLE(PREDEFINED_COLOR_SPACE_DISPLAY_P3) - case PredefinedColorSpaceTag::DisplayP3: - result = PredefinedColorSpace::DisplayP3; - return true; -#endif - default: - return false; - } - } - - bool read(DestinationColorSpaceTag& tag) - { - if (m_ptr >= m_end) - return false; - tag = static_cast(*m_ptr++); - return true; - } + // bool read(PredefinedColorSpace& result) + // { + // uint8_t tag; + // if (!read(tag)) + // return false; + + // switch (static_cast(tag)) { + // case PredefinedColorSpaceTag::SRGB: + // result = PredefinedColorSpace::SRGB; + // return true; + // #if ENABLE(PREDEFINED_COLOR_SPACE_DISPLAY_P3) + // case PredefinedColorSpaceTag::DisplayP3: + // result = PredefinedColorSpace::DisplayP3; + // return true; + // #endif + // default: + // return false; + // } + // } + + // bool read(DestinationColorSpaceTag& tag) + // { + // if (m_ptr >= m_end) + // return false; + // tag = static_cast(*m_ptr++); + // return true; + // } #if PLATFORM(COCOA) bool read(RetainPtr& data) @@ -2979,65 +3222,65 @@ class CloneDeserializer : CloneBase { } #endif - bool read(DestinationColorSpace& destinationColorSpace) - { - DestinationColorSpaceTag tag; - if (!read(tag)) - return false; - - switch (tag) { - case DestinationColorSpaceSRGBTag: - destinationColorSpace = DestinationColorSpace::SRGB(); - return true; -#if ENABLE(DESTINATION_COLOR_SPACE_LINEAR_SRGB) - case DestinationColorSpaceLinearSRGBTag: - destinationColorSpace = DestinationColorSpace::LinearSRGB(); - return true; -#endif -#if ENABLE(DESTINATION_COLOR_SPACE_DISPLAY_P3) - case DestinationColorSpaceDisplayP3Tag: - destinationColorSpace = DestinationColorSpace::DisplayP3(); - return true; -#endif -#if PLATFORM(COCOA) - case DestinationColorSpaceCGColorSpaceNameTag: { - RetainPtr data; - if (!read(data)) - return false; - - auto name = adoptCF(CFStringCreateFromExternalRepresentation(nullptr, data.get(), kCFStringEncodingUTF8)); - if (!name) - return false; - - auto colorSpace = adoptCF(CGColorSpaceCreateWithName(name.get())); - if (!colorSpace) - return false; - - destinationColorSpace = DestinationColorSpace(colorSpace.get()); - return true; - } - case DestinationColorSpaceCGColorSpacePropertyListTag: { - RetainPtr data; - if (!read(data)) - return false; - - auto propertyList = adoptCF(CFPropertyListCreateWithData(nullptr, data.get(), kCFPropertyListImmutable, nullptr, nullptr)); - if (!propertyList) - return false; - - auto colorSpace = adoptCF(CGColorSpaceCreateWithPropertyList(propertyList.get())); - if (!colorSpace) - return false; - - destinationColorSpace = DestinationColorSpace(colorSpace.get()); - return true; - } -#endif - } - - ASSERT_NOT_REACHED(); - return false; - } + // bool read(DestinationColorSpace& destinationColorSpace) + // { + // DestinationColorSpaceTag tag; + // if (!read(tag)) + // return false; + + // switch (tag) { + // case DestinationColorSpaceSRGBTag: + // destinationColorSpace = DestinationColorSpace::SRGB(); + // return true; + // #if ENABLE(DESTINATION_COLOR_SPACE_LINEAR_SRGB) + // case DestinationColorSpaceLinearSRGBTag: + // destinationColorSpace = DestinationColorSpace::LinearSRGB(); + // return true; + // #endif + // #if ENABLE(DESTINATION_COLOR_SPACE_DISPLAY_P3) + // case DestinationColorSpaceDisplayP3Tag: + // destinationColorSpace = DestinationColorSpace::DisplayP3(); + // return true; + // #endif + // #if PLATFORM(COCOA) + // case DestinationColorSpaceCGColorSpaceNameTag: { + // RetainPtr data; + // if (!read(data)) + // return false; + + // auto name = adoptCF(CFStringCreateFromExternalRepresentation(nullptr, data.get(), kCFStringEncodingUTF8)); + // if (!name) + // return false; + + // auto colorSpace = adoptCF(CGColorSpaceCreateWithName(name.get())); + // if (!colorSpace) + // return false; + + // destinationColorSpace = DestinationColorSpace(colorSpace.get()); + // return true; + // } + // case DestinationColorSpaceCGColorSpacePropertyListTag: { + // RetainPtr data; + // if (!read(data)) + // return false; + + // auto propertyList = adoptCF(CFPropertyListCreateWithData(nullptr, data.get(), kCFPropertyListImmutable, nullptr, nullptr)); + // if (!propertyList) + // return false; + + // auto colorSpace = adoptCF(CGColorSpaceCreateWithPropertyList(propertyList.get())); + // if (!colorSpace) + // return false; + + // destinationColorSpace = DestinationColorSpace(colorSpace.get()); + // return true; + // } + // #endif + // } + + // ASSERT_NOT_REACHED(); + // return false; + // } #if ENABLE(WEB_CRYPTO) bool read(CryptoKeyOKP::NamedCurve& result) @@ -3416,178 +3659,178 @@ class CloneDeserializer : CloneBase { return toJS(m_lexicalGlobalObject, jsCast(m_globalObject), std::forward(nativeObj)); } - template - JSValue readDOMPoint() - { - double x; - if (!read(x)) - return {}; - double y; - if (!read(y)) - return {}; - double z; - if (!read(z)) - return {}; - double w; - if (!read(w)) - return {}; - - return toJSNewlyCreated(m_lexicalGlobalObject, jsCast(m_globalObject), T::create(x, y, z, w)); - } - - template - JSValue readDOMMatrix() - { - uint8_t is2D; - if (!read(is2D)) - return {}; - - if (is2D) { - double m11; - if (!read(m11)) - return {}; - double m12; - if (!read(m12)) - return {}; - double m21; - if (!read(m21)) - return {}; - double m22; - if (!read(m22)) - return {}; - double m41; - if (!read(m41)) - return {}; - double m42; - if (!read(m42)) - return {}; - - TransformationMatrix matrix(m11, m12, m21, m22, m41, m42); - return toJSNewlyCreated(m_lexicalGlobalObject, jsCast(m_globalObject), T::create(WTFMove(matrix), DOMMatrixReadOnly::Is2D::Yes)); - } else { - double m11; - if (!read(m11)) - return {}; - double m12; - if (!read(m12)) - return {}; - double m13; - if (!read(m13)) - return {}; - double m14; - if (!read(m14)) - return {}; - double m21; - if (!read(m21)) - return {}; - double m22; - if (!read(m22)) - return {}; - double m23; - if (!read(m23)) - return {}; - double m24; - if (!read(m24)) - return {}; - double m31; - if (!read(m31)) - return {}; - double m32; - if (!read(m32)) - return {}; - double m33; - if (!read(m33)) - return {}; - double m34; - if (!read(m34)) - return {}; - double m41; - if (!read(m41)) - return {}; - double m42; - if (!read(m42)) - return {}; - double m43; - if (!read(m43)) - return {}; - double m44; - if (!read(m44)) - return {}; - - TransformationMatrix matrix(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44); - return toJSNewlyCreated(m_lexicalGlobalObject, jsCast(m_globalObject), T::create(WTFMove(matrix), DOMMatrixReadOnly::Is2D::No)); - } - } - - template - JSValue readDOMRect() - { - double x; - if (!read(x)) - return {}; - double y; - if (!read(y)) - return {}; - double width; - if (!read(width)) - return {}; - double height; - if (!read(height)) - return {}; - - return toJSNewlyCreated(m_lexicalGlobalObject, jsCast(m_globalObject), T::create(x, y, width, height)); - } - - std::optional readDOMPointInit() - { - DOMPointInit point; - if (!read(point.x)) - return std::nullopt; - if (!read(point.y)) - return std::nullopt; - if (!read(point.z)) - return std::nullopt; - if (!read(point.w)) - return std::nullopt; - - return point; - } - - JSValue readDOMQuad() - { - auto p1 = readDOMPointInit(); - if (!p1) - return JSValue(); - auto p2 = readDOMPointInit(); - if (!p2) - return JSValue(); - auto p3 = readDOMPointInit(); - if (!p3) - return JSValue(); - auto p4 = readDOMPointInit(); - if (!p4) - return JSValue(); - - return toJSNewlyCreated(m_lexicalGlobalObject, jsCast(m_globalObject), DOMQuad::create(p1.value(), p2.value(), p3.value(), p4.value())); - } - - JSValue readTransferredImageBitmap() - { - uint32_t index; - bool indexSuccessfullyRead = read(index); - if (!indexSuccessfullyRead || index >= m_backingStores.size()) { - fail(); - return JSValue(); - } - - if (!m_imageBitmaps[index]) { - m_backingStores.at(index)->connect(*executionContext(m_lexicalGlobalObject)); - m_imageBitmaps[index] = ImageBitmap::create(WTFMove(m_backingStores.at(index))); - } - - auto bitmap = m_imageBitmaps[index].get(); - return getJSValue(bitmap); - } + // template + // JSValue readDOMPoint() + // { + // double x; + // if (!read(x)) + // return {}; + // double y; + // if (!read(y)) + // return {}; + // double z; + // if (!read(z)) + // return {}; + // double w; + // if (!read(w)) + // return {}; + + // return toJSNewlyCreated(m_lexicalGlobalObject, jsCast(m_globalObject), T::create(x, y, z, w)); + // } + + // template + // JSValue readDOMMatrix() + // { + // uint8_t is2D; + // if (!read(is2D)) + // return {}; + + // if (is2D) { + // double m11; + // if (!read(m11)) + // return {}; + // double m12; + // if (!read(m12)) + // return {}; + // double m21; + // if (!read(m21)) + // return {}; + // double m22; + // if (!read(m22)) + // return {}; + // double m41; + // if (!read(m41)) + // return {}; + // double m42; + // if (!read(m42)) + // return {}; + + // TransformationMatrix matrix(m11, m12, m21, m22, m41, m42); + // return toJSNewlyCreated(m_lexicalGlobalObject, jsCast(m_globalObject), T::create(WTFMove(matrix), DOMMatrixReadOnly::Is2D::Yes)); + // } else { + // double m11; + // if (!read(m11)) + // return {}; + // double m12; + // if (!read(m12)) + // return {}; + // double m13; + // if (!read(m13)) + // return {}; + // double m14; + // if (!read(m14)) + // return {}; + // double m21; + // if (!read(m21)) + // return {}; + // double m22; + // if (!read(m22)) + // return {}; + // double m23; + // if (!read(m23)) + // return {}; + // double m24; + // if (!read(m24)) + // return {}; + // double m31; + // if (!read(m31)) + // return {}; + // double m32; + // if (!read(m32)) + // return {}; + // double m33; + // if (!read(m33)) + // return {}; + // double m34; + // if (!read(m34)) + // return {}; + // double m41; + // if (!read(m41)) + // return {}; + // double m42; + // if (!read(m42)) + // return {}; + // double m43; + // if (!read(m43)) + // return {}; + // double m44; + // if (!read(m44)) + // return {}; + + // TransformationMatrix matrix(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44); + // return toJSNewlyCreated(m_lexicalGlobalObject, jsCast(m_globalObject), T::create(WTFMove(matrix), DOMMatrixReadOnly::Is2D::No)); + // } + // } + + // template + // JSValue readDOMRect() + // { + // double x; + // if (!read(x)) + // return {}; + // double y; + // if (!read(y)) + // return {}; + // double width; + // if (!read(width)) + // return {}; + // double height; + // if (!read(height)) + // return {}; + + // return toJSNewlyCreated(m_lexicalGlobalObject, jsCast(m_globalObject), T::create(x, y, width, height)); + // } + + // std::optional readDOMPointInit() + // { + // DOMPointInit point; + // if (!read(point.x)) + // return std::nullopt; + // if (!read(point.y)) + // return std::nullopt; + // if (!read(point.z)) + // return std::nullopt; + // if (!read(point.w)) + // return std::nullopt; + + // return point; + // } + + // JSValue readDOMQuad() + // { + // auto p1 = readDOMPointInit(); + // if (!p1) + // return JSValue(); + // auto p2 = readDOMPointInit(); + // if (!p2) + // return JSValue(); + // auto p3 = readDOMPointInit(); + // if (!p3) + // return JSValue(); + // auto p4 = readDOMPointInit(); + // if (!p4) + // return JSValue(); + + // return toJSNewlyCreated(m_lexicalGlobalObject, jsCast(m_globalObject), DOMQuad::create(p1.value(), p2.value(), p3.value(), p4.value())); + // } + + // JSValue readTransferredImageBitmap() + // { + // uint32_t index; + // bool indexSuccessfullyRead = read(index); + // if (!indexSuccessfullyRead || index >= m_backingStores.size()) { + // fail(); + // return JSValue(); + // } + + // if (!m_imageBitmaps[index]) { + // m_backingStores.at(index)->connect(*executionContext(m_lexicalGlobalObject)); + // m_imageBitmaps[index] = ImageBitmap::create(WTFMove(m_backingStores.at(index))); + // } + + // auto bitmap = m_imageBitmaps[index].get(); + // return getJSValue(bitmap); + // } #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) JSValue readOffscreenCanvas() @@ -3702,42 +3945,42 @@ class CloneDeserializer : CloneBase { } #endif - JSValue readImageBitmap() - { - uint8_t serializationState; - int32_t logicalWidth; - int32_t logicalHeight; - double resolutionScale; - auto colorSpace = DestinationColorSpace::SRGB(); - RefPtr arrayBuffer; - - if (!read(serializationState) || !read(logicalWidth) || !read(logicalHeight) || !read(resolutionScale) || (m_version > 8 && !read(colorSpace)) || !readArrayBufferImpl(arrayBuffer)) { - fail(); - return JSValue(); - } + // JSValue readImageBitmap() + // { + // uint8_t serializationState; + // int32_t logicalWidth; + // int32_t logicalHeight; + // double resolutionScale; + // auto colorSpace = DestinationColorSpace::SRGB(); + // RefPtr arrayBuffer; - auto logicalSize = IntSize(logicalWidth, logicalHeight); - auto imageDataSize = logicalSize; - imageDataSize.scale(resolutionScale); + // if (!read(serializationState) || !read(logicalWidth) || !read(logicalHeight) || !read(resolutionScale) || (m_version > 8 && !read(colorSpace)) || !readArrayBufferImpl(arrayBuffer)) { + // fail(); + // return JSValue(); + // } - auto buffer = ImageBitmap::createImageBuffer(*executionContext(m_lexicalGlobalObject), logicalSize, RenderingMode::Unaccelerated, colorSpace, resolutionScale); - if (!buffer) { - fail(); - return JSValue(); - } + // auto logicalSize = IntSize(logicalWidth, logicalHeight); + // auto imageDataSize = logicalSize; + // imageDataSize.scale(resolutionScale); - PixelBufferFormat format { AlphaPremultiplication::Premultiplied, PixelFormat::RGBA8, colorSpace }; - auto pixelBuffer = ByteArrayPixelBuffer::tryCreate(format, imageDataSize, arrayBuffer.releaseNonNull()); - if (!pixelBuffer) { - fail(); - return JSValue(); - } + // auto buffer = ImageBitmap::createImageBuffer(*executionContext(m_lexicalGlobalObject), logicalSize, RenderingMode::Unaccelerated, colorSpace, resolutionScale); + // if (!buffer) { + // fail(); + // return JSValue(); + // } - buffer->putPixelBuffer(*pixelBuffer, { IntPoint::zero(), logicalSize }); + // PixelBufferFormat format { AlphaPremultiplication::Premultiplied, PixelFormat::RGBA8, colorSpace }; + // auto pixelBuffer = ByteArrayPixelBuffer::tryCreate(format, imageDataSize, arrayBuffer.releaseNonNull()); + // if (!pixelBuffer) { + // fail(); + // return JSValue(); + // } - auto bitmap = ImageBitmap::create(ImageBitmapBacking(WTFMove(buffer), OptionSet::fromRaw(serializationState))); - return getJSValue(bitmap); - } + // buffer->putPixelBuffer(*pixelBuffer, { IntPoint::zero(), logicalSize }); + + // auto bitmap = ImageBitmap::create(ImageBitmapBacking(WTFMove(buffer), OptionSet::fromRaw(serializationState))); + // return getJSValue(bitmap); + // } JSValue readDOMException() { @@ -3907,90 +4150,90 @@ class CloneDeserializer : CloneBase { return JSValue(); return DateInstance::create(m_lexicalGlobalObject->vm(), m_globalObject->dateStructure(), d); } - case FileTag: { - RefPtr file; - if (!readFile(file)) - return JSValue(); - if (!m_canCreateDOMObject) - return jsNull(); - return toJS(m_lexicalGlobalObject, jsCast(m_globalObject), file.get()); - } - case FileListTag: { - unsigned length = 0; - if (!read(length)) - return JSValue(); - ASSERT(m_globalObject->inherits()); - Vector> files; - for (unsigned i = 0; i < length; i++) { - RefPtr file; - if (!readFile(file)) - return JSValue(); - if (m_canCreateDOMObject) - files.append(file.releaseNonNull()); - } - if (!m_canCreateDOMObject) - return jsNull(); - return getJSValue(FileList::create(WTFMove(files)).get()); - } - case ImageDataTag: { - uint32_t width; - if (!read(width)) - return JSValue(); - uint32_t height; - if (!read(height)) - return JSValue(); - uint32_t length; - if (!read(length)) - return JSValue(); - if (static_cast(m_end - m_ptr) < length) { - fail(); - return JSValue(); - } - auto bufferStart = m_ptr; - m_ptr += length; - - auto resultColorSpace = PredefinedColorSpace::SRGB; - if (m_version > 7) { - if (!read(resultColorSpace)) - return JSValue(); - } - - if (length && (IntSize(width, height).area() * 4) != length) { - fail(); - return JSValue(); - } - - if (!m_isDOMGlobalObject) - return jsNull(); - - auto result = ImageData::createUninitialized(width, height, resultColorSpace); - if (result.hasException()) { - fail(); - return JSValue(); - } - if (length) - memcpy(result.returnValue()->data().data(), bufferStart, length); - else - result.returnValue()->data().zeroFill(); - return getJSValue(result.releaseReturnValue()); - } - case BlobTag: { - CachedStringRef url; - if (!readStringData(url)) - return JSValue(); - CachedStringRef type; - if (!readStringData(type)) - return JSValue(); - uint64_t size = 0; - if (!read(size)) - return JSValue(); - uint64_t memoryCost = 0; - if (m_version >= 11 && !read(memoryCost)) - return JSValue(); - if (!m_canCreateDOMObject) - return jsNull(); - return getJSValue(Blob::deserialize(executionContext(m_lexicalGlobalObject), URL { url->string() }, type->string(), size, memoryCost, blobFilePathForBlobURL(url->string())).get()); - } + // case FileTag: { + // RefPtr file; + // if (!readFile(file)) + // return JSValue(); + // if (!m_canCreateDOMObject) + // return jsNull(); + // return toJS(m_lexicalGlobalObject, jsCast(m_globalObject), file.get()); + // } + // case FileListTag: { + // unsigned length = 0; + // if (!read(length)) + // return JSValue(); + // ASSERT(m_globalObject->inherits()); + // Vector> files; + // for (unsigned i = 0; i < length; i++) { + // RefPtr file; + // if (!readFile(file)) + // return JSValue(); + // if (m_canCreateDOMObject) + // files.append(file.releaseNonNull()); + // } + // if (!m_canCreateDOMObject) + // return jsNull(); + // return getJSValue(FileList::create(WTFMove(files)).get()); + // } + // case ImageDataTag: { + // uint32_t width; + // if (!read(width)) + // return JSValue(); + // uint32_t height; + // if (!read(height)) + // return JSValue(); + // uint32_t length; + // if (!read(length)) + // return JSValue(); + // if (static_cast(m_end - m_ptr) < length) { + // fail(); + // return JSValue(); + // } + // auto bufferStart = m_ptr; + // m_ptr += length; + + // auto resultColorSpace = PredefinedColorSpace::SRGB; + // if (m_version > 7) { + // if (!read(resultColorSpace)) + // return JSValue(); + // } + + // if (length && (IntSize(width, height).area() * 4) != length) { + // fail(); + // return JSValue(); + // } + + // if (!m_isDOMGlobalObject) + // return jsNull(); + + // auto result = ImageData::createUninitialized(width, height, resultColorSpace); + // if (result.hasException()) { + // fail(); + // return JSValue(); + // } + // if (length) + // memcpy(result.returnValue()->data().data(), bufferStart, length); + // else + // result.returnValue()->data().zeroFill(); + // return getJSValue(result.releaseReturnValue()); + // } + // case BlobTag: { + // CachedStringRef url; + // if (!readStringData(url)) + // return JSValue(); + // CachedStringRef type; + // if (!readStringData(type)) + // return JSValue(); + // uint64_t size = 0; + // if (!read(size)) + // return JSValue(); + // uint64_t memoryCost = 0; + // if (m_version >= 11 && !read(memoryCost)) + // return JSValue(); + // if (!m_canCreateDOMObject) + // return jsNull(); + // return getJSValue(Blob::deserialize(executionContext(m_lexicalGlobalObject), URL { url->string() }, type->string(), size, memoryCost, blobFilePathForBlobURL(url->string())).get()); + // } case StringTag: { CachedStringRef cachedString; if (!readStringData(cachedString)) @@ -4034,15 +4277,15 @@ class CloneDeserializer : CloneBase { } return m_gcBuffer.at(index); } - case MessagePortReferenceTag: { - uint32_t index; - bool indexSuccessfullyRead = read(index); - if (!indexSuccessfullyRead || index >= m_messagePorts.size()) { - fail(); - return JSValue(); - } - return getJSValue(m_messagePorts[index].get()); - } + // case MessagePortReferenceTag: { + // uint32_t index; + // bool indexSuccessfullyRead = read(index); + // if (!indexSuccessfullyRead || index >= m_messagePorts.size()) { + // fail(); + // return JSValue(); + // } + // return getJSValue(m_messagePorts[index].get()); + // } #if ENABLE(WEBASSEMBLY) case WasmModuleTag: { if (m_version >= 12) { @@ -4195,8 +4438,9 @@ class CloneDeserializer : CloneBase { return JSValue(); } JSValue cryptoKey; - Vector> dummyMessagePorts; - CloneDeserializer rawKeyDeserializer(m_lexicalGlobalObject, m_globalObject, dummyMessagePorts, nullptr, {}, serializedKey); + // Vector> dummyMessagePorts; + // CloneDeserializer rawKeyDeserializer(m_lexicalGlobalObject, m_globalObject, dummyMessagePorts, nullptr, {}, serializedKey); + CloneDeserializer rawKeyDeserializer(m_lexicalGlobalObject, m_globalObject, nullptr, serializedKey); if (!rawKeyDeserializer.readCryptoKey(cryptoKey)) { fail(); return JSValue(); @@ -4205,29 +4449,29 @@ class CloneDeserializer : CloneBase { return cryptoKey; } #endif - case DOMPointReadOnlyTag: - return readDOMPoint(); - case DOMPointTag: - return readDOMPoint(); - case DOMRectReadOnlyTag: - return readDOMRect(); - case DOMRectTag: - return readDOMRect(); - case DOMMatrixReadOnlyTag: - return readDOMMatrix(); - case DOMMatrixTag: - return readDOMMatrix(); - case DOMQuadTag: - return readDOMQuad(); - case ImageBitmapTransferTag: - return readTransferredImageBitmap(); + // case DOMPointReadOnlyTag: + // return readDOMPoint(); + // case DOMPointTag: + // return readDOMPoint(); + // case DOMRectReadOnlyTag: + // return readDOMRect(); + // case DOMRectTag: + // return readDOMRect(); + // case DOMMatrixReadOnlyTag: + // return readDOMMatrix(); + // case DOMMatrixTag: + // return readDOMMatrix(); + // case DOMQuadTag: + // return readDOMQuad(); + // case ImageBitmapTransferTag: + // return readTransferredImageBitmap(); #if ENABLE(WEB_RTC) case RTCCertificateTag: return readRTCCertificate(); #endif - case ImageBitmapTag: - return readImageBitmap(); + // case ImageBitmapTag: + // return readImageBitmap(); #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) case OffscreenCanvasTransferTag: return readOffscreenCanvas(); @@ -4267,14 +4511,14 @@ class CloneDeserializer : CloneBase { const uint8_t* const m_end; unsigned m_version; Vector m_constantPool; - const Vector>& m_messagePorts; + // const Vector>& m_messagePorts; ArrayBufferContentsArray* m_arrayBufferContents; Vector> m_arrayBuffers; Vector m_blobURLs; Vector m_blobFilePaths; ArrayBufferContentsArray* m_sharedBuffers; - Vector> m_backingStores; - Vector> m_imageBitmaps; + // Vector> m_backingStores; + // Vector> m_imageBitmaps; #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) Vector> m_detachedOffscreenCanvases; Vector> m_offscreenCanvases; @@ -4514,7 +4758,48 @@ SerializedScriptValue::SerializedScriptValue(Vector&& buffer, std::uniq m_memoryCost = computeMemoryCost(); } -SerializedScriptValue::SerializedScriptValue(Vector&& buffer, Vector&& blobHandles, std::unique_ptr arrayBufferContentsArray, std::unique_ptr sharedBufferContentsArray, Vector>&& backingStores +// SerializedScriptValue::SerializedScriptValue(Vector&& buffer, Vector&& blobHandles, std::unique_ptr arrayBufferContentsArray, std::unique_ptr sharedBufferContentsArray, Vector>&& backingStores +// #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) +// , +// Vector>&& detachedOffscreenCanvases +// #endif +// #if ENABLE(WEB_RTC) +// , +// Vector>&& detachedRTCDataChannels +// #endif +// #if ENABLE(WEBASSEMBLY) +// , +// std::unique_ptr wasmModulesArray, std::unique_ptr wasmMemoryHandlesArray +// #endif +// #if ENABLE(WEB_CODECS) +// , +// Vector>&& serializedVideoChunks, Vector&& serializedVideoFrames +// #endif +// ) +// : m_data(WTFMove(buffer)) +// , m_arrayBufferContentsArray(WTFMove(arrayBufferContentsArray)) +// , m_sharedBufferContentsArray(WTFMove(sharedBufferContentsArray)) +// , m_backingStores(WTFMove(backingStores)) +// #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) +// , m_detachedOffscreenCanvases(WTFMove(detachedOffscreenCanvases)) +// #endif +// #if ENABLE(WEB_RTC) +// , m_detachedRTCDataChannels(WTFMove(detachedRTCDataChannels)) +// #endif +// #if ENABLE(WEBASSEMBLY) +// , m_wasmModulesArray(WTFMove(wasmModulesArray)) +// , m_wasmMemoryHandlesArray(WTFMove(wasmMemoryHandlesArray)) +// #endif +// #if ENABLE(WEB_CODECS) +// , m_serializedVideoChunks(WTFMove(serializedVideoChunks)) +// , m_serializedVideoFrames(WTFMove(serializedVideoFrames)) +// #endif +// , m_blobHandles(crossThreadCopy(WTFMove(blobHandles))) +// { +// m_memoryCost = computeMemoryCost(); +// } + +SerializedScriptValue::SerializedScriptValue(Vector&& buffer, std::unique_ptr arrayBufferContentsArray, std::unique_ptr sharedBufferContentsArray #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) , Vector>&& detachedOffscreenCanvases @@ -4535,7 +4820,6 @@ SerializedScriptValue::SerializedScriptValue(Vector&& buffer, Vector&& buffer, Vectorbuffer() : nullptr) - cost += buffer->memoryCost(); - } + // for (auto& backingStore : m_backingStores) { + // if (auto buffer = backingStore ? backingStore->buffer() : nullptr) + // cost += buffer->memoryCost(); + // } #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) for (auto& canvas : m_detachedOffscreenCanvases) { @@ -4602,8 +4885,8 @@ size_t SerializedScriptValue::computeMemoryCost() const cost += frame.memoryCost(); #endif - for (auto& handle : m_blobHandles) - cost += handle.url().string().sizeInBytes(); + // for (auto& handle : m_blobHandles) + // cost += handle.url().string().sizeInBytes(); return cost; } @@ -4678,15 +4961,15 @@ static Exception exceptionForSerializationFailure(SerializationReturnCode code) return Exception { TypeError }; } -static bool containsDuplicates(const Vector>& imageBitmaps) -{ - HashSet visited; - for (auto& imageBitmap : imageBitmaps) { - if (!visited.add(imageBitmap.get())) - return true; - } - return false; -} +// static bool containsDuplicates(const Vector>& imageBitmaps) +// { +// HashSet visited; +// for (auto& imageBitmap : imageBitmaps) { +// if (!visited.add(imageBitmap.get())) +// return true; +// } +// return false; +// } #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) static bool canOffscreenCanvasesDetach(const Vector>& offscreenCanvases) @@ -4720,23 +5003,30 @@ static bool canDetachRTCDataChannels(const Vector>& channels RefPtr SerializedScriptValue::create(JSC::JSGlobalObject& globalObject, JSC::JSValue value, SerializationForStorage forStorage, SerializationErrorMode throwExceptions, SerializationContext serializationContext) { - Vector> dummyPorts; - auto result = create(globalObject, value, {}, dummyPorts, forStorage, throwExceptions, serializationContext); + // Vector> dummyPorts; + // auto result = create(globalObject, value, {}, dummyPorts, forStorage, throwExceptions, serializationContext); + auto result = create(globalObject, value, {}, forStorage, throwExceptions, serializationContext); if (result.hasException()) return nullptr; return result.releaseReturnValue(); } -ExceptionOr> SerializedScriptValue::create(JSGlobalObject& globalObject, JSValue value, Vector>&& transferList, Vector>& messagePorts, SerializationForStorage forStorage, SerializationContext serializationContext) +// ExceptionOr> SerializedScriptValue::create(JSGlobalObject& globalObject, JSValue value, Vector>&& transferList, Vector>& messagePorts, SerializationForStorage forStorage, SerializationContext serializationContext) +// { +// return create(globalObject, value, WTFMove(transferList), messagePorts, forStorage, SerializationErrorMode::NonThrowing, serializationContext); +// } + +ExceptionOr> SerializedScriptValue::create(JSGlobalObject& globalObject, JSValue value, Vector>&& transferList, SerializationForStorage forStorage, SerializationContext serializationContext) { - return create(globalObject, value, WTFMove(transferList), messagePorts, forStorage, SerializationErrorMode::NonThrowing, serializationContext); + return create(globalObject, value, WTFMove(transferList), forStorage, SerializationErrorMode::NonThrowing, serializationContext); } -ExceptionOr> SerializedScriptValue::create(JSGlobalObject& lexicalGlobalObject, JSValue value, Vector>&& transferList, Vector>& messagePorts, SerializationForStorage forStorage, SerializationErrorMode throwExceptions, SerializationContext context) +// ExceptionOr> SerializedScriptValue::create(JSGlobalObject& lexicalGlobalObject, JSValue value, Vector>&& transferList, Vector>& messagePorts, SerializationForStorage forStorage, SerializationErrorMode throwExceptions, SerializationContext context) +ExceptionOr> SerializedScriptValue::create(JSGlobalObject& lexicalGlobalObject, JSValue value, Vector>&& transferList, SerializationForStorage forStorage, SerializationErrorMode throwExceptions, SerializationContext context) { VM& vm = lexicalGlobalObject.vm(); Vector> arrayBuffers; - Vector> imageBitmaps; + // Vector> imageBitmaps; #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) Vector> offscreenCanvases; #endif @@ -4762,22 +5052,22 @@ ExceptionOr> SerializedScriptValue::create(JSGlobalOb arrayBuffers.append(WTFMove(arrayBuffer)); continue; } - if (auto port = JSMessagePort::toWrapped(vm, transferable.get())) { - if (port->isDetached()) - return Exception { DataCloneError, "MessagePort is detached"_s }; - messagePorts.append(WTFMove(port)); - continue; - } + // if (auto port = JSMessagePort::toWrapped(vm, transferable.get())) { + // if (port->isDetached()) + // return Exception { DataCloneError, "MessagePort is detached"_s }; + // messagePorts.append(WTFMove(port)); + // continue; + // } - if (auto imageBitmap = JSImageBitmap::toWrapped(vm, transferable.get())) { - if (imageBitmap->isDetached()) - return Exception { DataCloneError }; - if (!imageBitmap->originClean()) - return Exception { DataCloneError }; + // if (auto imageBitmap = JSImageBitmap::toWrapped(vm, transferable.get())) { + // if (imageBitmap->isDetached()) + // return Exception { DataCloneError }; + // if (!imageBitmap->originClean()) + // return Exception { DataCloneError }; - imageBitmaps.append(WTFMove(imageBitmap)); - continue; - } + // imageBitmaps.append(WTFMove(imageBitmap)); + // continue; + // } #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) if (auto offscreenCanvas = JSOffscreenCanvas::toWrapped(vm, transferable.get())) { @@ -4804,8 +5094,8 @@ ExceptionOr> SerializedScriptValue::create(JSGlobalOb return Exception { DataCloneError }; } - if (containsDuplicates(imageBitmaps)) - return Exception { DataCloneError }; + // if (containsDuplicates(imageBitmaps)) + // return Exception { DataCloneError }; #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) if (!canOffscreenCanvasesDetach(offscreenCanvases)) return Exception { InvalidStateError }; @@ -4816,7 +5106,7 @@ ExceptionOr> SerializedScriptValue::create(JSGlobalOb #endif Vector buffer; - Vector blobHandles; + // Vector blobHandles; #if ENABLE(WEBASSEMBLY) WasmModuleArray wasmModules; WasmMemoryHandleArray wasmMemoryHandles; @@ -4826,7 +5116,24 @@ ExceptionOr> SerializedScriptValue::create(JSGlobalOb Vector> serializedVideoChunks; Vector> serializedVideoFrames; #endif - auto code = CloneSerializer::serialize(&lexicalGlobalObject, value, messagePorts, arrayBuffers, imageBitmaps, + // auto code = CloneSerializer::serialize(&lexicalGlobalObject, value, messagePorts, arrayBuffers, imageBitmaps, + // #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + // offscreenCanvases, + // #endif + // #if ENABLE(WEB_RTC) + // dataChannels, + // #endif + // #if ENABLE(WEB_CODECS) + // serializedVideoChunks, + // serializedVideoFrames, + // #endif + // #if ENABLE(WEBASSEMBLY) + // wasmModules, + // wasmMemoryHandles, + // #endif + // blobHandles, buffer, context, *sharedBuffers, forStorage); + + auto code = CloneSerializer::serialize(&lexicalGlobalObject, value, arrayBuffers, #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) offscreenCanvases, #endif @@ -4841,7 +5148,7 @@ ExceptionOr> SerializedScriptValue::create(JSGlobalOb wasmModules, wasmMemoryHandles, #endif - blobHandles, buffer, context, *sharedBuffers, forStorage); + buffer, context, *sharedBuffers, forStorage); if (throwExceptions == SerializationErrorMode::Throwing) maybeThrowExceptionIfSerializationFailed(lexicalGlobalObject, code); @@ -4853,7 +5160,7 @@ ExceptionOr> SerializedScriptValue::create(JSGlobalOb if (arrayBufferContentsArray.hasException()) return arrayBufferContentsArray.releaseException(); - auto backingStores = ImageBitmap::detachBitmaps(WTFMove(imageBitmaps)); + // auto backingStores = ImageBitmap::detachBitmaps(WTFMove(imageBitmaps)); #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) Vector> detachedCanvases; @@ -4874,9 +5181,27 @@ ExceptionOr> SerializedScriptValue::create(JSGlobalOb videoFrame->close(); #endif - return adoptRef(*new SerializedScriptValue(WTFMove(buffer), WTFMove(blobHandles), arrayBufferContentsArray.releaseReturnValue(), context == SerializationContext::WorkerPostMessage ? WTFMove(sharedBuffers) : nullptr, WTFMove(backingStores) + // return adoptRef(*new SerializedScriptValue(WTFMove(buffer), WTFMove(blobHandles), arrayBufferContentsArray.releaseReturnValue(), context == SerializationContext::WorkerPostMessage ? WTFMove(sharedBuffers) : nullptr, WTFMove(backingStores) + // #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + // , + // WTFMove(detachedCanvases) + // #endif + // #if ENABLE(WEB_RTC) + // , + // WTFMove(detachedRTCDataChannels) + // #endif + // #if ENABLE(WEBASSEMBLY) + // , + // makeUnique(wasmModules), context == SerializationContext::WorkerPostMessage ? makeUnique(wasmMemoryHandles) : nullptr + // #endif + // #if ENABLE(WEB_CODECS) + // , + // WTFMove(serializedVideoChunks), WTFMove(serializedVideoFrameData) + // #endif + // )); + return adoptRef(*new SerializedScriptValue(WTFMove(buffer), arrayBufferContentsArray.releaseReturnValue(), context == SerializationContext::WorkerPostMessage ? WTFMove(sharedBuffers) : nullptr #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) - , + , WTFMove(detachedCanvases) #endif #if ENABLE(WEB_RTC) @@ -4926,23 +5251,78 @@ String SerializedScriptValue::toString() const return CloneDeserializer::deserializeString(m_data); } -JSValue SerializedScriptValue::deserialize(JSGlobalObject& lexicalGlobalObject, JSGlobalObject* globalObject, SerializationErrorMode throwExceptions, bool* didFail) -{ - return deserialize(lexicalGlobalObject, globalObject, {}, throwExceptions, didFail); -} +// JSValue SerializedScriptValue::deserialize(JSGlobalObject& lexicalGlobalObject, JSGlobalObject* globalObject, SerializationErrorMode throwExceptions, bool* didFail) +// { +// return deserialize(lexicalGlobalObject, globalObject, {}, throwExceptions, didFail); +// } + +// JSValue SerializedScriptValue::deserialize(JSGlobalObject& lexicalGlobalObject, JSGlobalObject* globalObject, const Vector>& messagePorts, SerializationErrorMode throwExceptions, bool* didFail) +// { +// Vector dummyBlobs; +// Vector dummyPaths; +// return deserialize(lexicalGlobalObject, globalObject, messagePorts, dummyBlobs, dummyPaths, throwExceptions, didFail); +// } -JSValue SerializedScriptValue::deserialize(JSGlobalObject& lexicalGlobalObject, JSGlobalObject* globalObject, const Vector>& messagePorts, SerializationErrorMode throwExceptions, bool* didFail) +JSValue SerializedScriptValue::deserialize(JSGlobalObject& lexicalGlobalObject, JSGlobalObject* globalObject, SerializationErrorMode throwExceptions, bool* didFail) { Vector dummyBlobs; Vector dummyPaths; - return deserialize(lexicalGlobalObject, globalObject, messagePorts, dummyBlobs, dummyPaths, throwExceptions, didFail); + return deserialize(lexicalGlobalObject, globalObject, dummyBlobs, dummyPaths, throwExceptions, didFail); } -JSValue SerializedScriptValue::deserialize(JSGlobalObject& lexicalGlobalObject, JSGlobalObject* globalObject, const Vector>& messagePorts, const Vector& blobURLs, const Vector& blobFilePaths, SerializationErrorMode throwExceptions, bool* didFail) +// JSValue SerializedScriptValue::deserialize(JSGlobalObject& lexicalGlobalObject, JSGlobalObject* globalObject, const Vector>& messagePorts, const Vector& blobURLs, const Vector& blobFilePaths, SerializationErrorMode throwExceptions, bool* didFail) +// { +// DeserializationResult result = CloneDeserializer::deserialize(&lexicalGlobalObject, globalObject, messagePorts, WTFMove(m_backingStores) +// #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) +// , +// WTFMove(m_detachedOffscreenCanvases) +// #endif +// #if ENABLE(WEB_RTC) +// , +// WTFMove(m_detachedRTCDataChannels) +// #endif +// , +// m_arrayBufferContentsArray.get(), m_data, blobURLs, blobFilePaths, m_sharedBufferContentsArray.get() +// #if ENABLE(WEBASSEMBLY) +// , +// m_wasmModulesArray.get(), m_wasmMemoryHandlesArray.get() +// #endif +// #if ENABLE(WEB_CODECS) +// , +// WTFMove(m_serializedVideoChunks), WTFMove(m_serializedVideoFrames) +// #endif +// ); +// if (didFail) +// *didFail = result.second != SerializationReturnCode::SuccessfullyCompleted; +// if (throwExceptions == SerializationErrorMode::Throwing) +// maybeThrowExceptionIfSerializationFailed(lexicalGlobalObject, result.second); +// return result.first ? result.first : jsNull(); +// } +JSValue SerializedScriptValue::deserialize(JSGlobalObject& lexicalGlobalObject, JSGlobalObject* globalObject, const Vector& blobURLs, const Vector& blobFilePaths, SerializationErrorMode throwExceptions, bool* didFail) { - DeserializationResult result = CloneDeserializer::deserialize(&lexicalGlobalObject, globalObject, messagePorts, WTFMove(m_backingStores) + // DeserializationResult result = CloneDeserializer::deserialize(&lexicalGlobalObject, globalObject, messagePorts, WTFMove(m_backingStores) + // #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + // , + // WTFMove(m_detachedOffscreenCanvases) + // #endif + // #if ENABLE(WEB_RTC) + // , + // WTFMove(m_detachedRTCDataChannels) + // #endif + // , + // m_arrayBufferContentsArray.get(), m_data, blobURLs, blobFilePaths, m_sharedBufferContentsArray.get() + // #if ENABLE(WEBASSEMBLY) + // , + // m_wasmModulesArray.get(), m_wasmMemoryHandlesArray.get() + // #endif + // #if ENABLE(WEB_CODECS) + // , + // WTFMove(m_serializedVideoChunks), WTFMove(m_serializedVideoFrames) + // #endif + // ); + DeserializationResult result = CloneDeserializer::deserialize(&lexicalGlobalObject, globalObject #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) - , + , WTFMove(m_detachedOffscreenCanvases) #endif #if ENABLE(WEB_RTC) @@ -4995,51 +5375,51 @@ uint32_t SerializedScriptValue::wireFormatVersion() return CurrentVersion; } -Vector SerializedScriptValue::blobURLs() const -{ - return m_blobHandles.map([](auto& handle) { - return handle.url().string().isolatedCopy(); - }); -} - -void SerializedScriptValue::writeBlobsToDiskForIndexedDB(CompletionHandler&& completionHandler) -{ - ASSERT(isMainThread()); - ASSERT(hasBlobURLs()); - - blobRegistry().writeBlobsToTemporaryFilesForIndexedDB(blobURLs(), [completionHandler = WTFMove(completionHandler), this, protectedThis = Ref { *this }](auto&& blobFilePaths) mutable { - ASSERT(isMainThread()); - - if (blobFilePaths.isEmpty()) { - // We should have successfully written blobs to temporary files. - // If we failed, then we can't successfully store this record. - completionHandler({}); - return; - } - - ASSERT(m_blobHandles.size() == blobFilePaths.size()); - - completionHandler({ *this, blobURLs(), blobFilePaths }); - }); -} - -IDBValue SerializedScriptValue::writeBlobsToDiskForIndexedDBSynchronously() -{ - ASSERT(!isMainThread()); - - BinarySemaphore semaphore; - IDBValue value; - callOnMainThread([this, &semaphore, &value] { - writeBlobsToDiskForIndexedDB([&semaphore, &value](IDBValue&& result) { - ASSERT(isMainThread()); - value.setAsIsolatedCopy(result); - - semaphore.signal(); - }); - }); - semaphore.wait(); - - return value; -} +// Vector SerializedScriptValue::blobURLs() const +// { +// return m_blobHandles.map([](auto& handle) { +// return handle.url().string().isolatedCopy(); +// }); +// } + +// void SerializedScriptValue::writeBlobsToDiskForIndexedDB(CompletionHandler&& completionHandler) +// { +// ASSERT(isMainThread()); +// ASSERT(hasBlobURLs()); + +// blobRegistry().writeBlobsToTemporaryFilesForIndexedDB(blobURLs(), [completionHandler = WTFMove(completionHandler), this, protectedThis = Ref { *this }](auto&& blobFilePaths) mutable { +// ASSERT(isMainThread()); + +// if (blobFilePaths.isEmpty()) { +// // We should have successfully written blobs to temporary files. +// // If we failed, then we can't successfully store this record. +// completionHandler({}); +// return; +// } + +// ASSERT(m_blobHandles.size() == blobFilePaths.size()); + +// completionHandler({ *this, blobURLs(), blobFilePaths }); +// }); +// } + +// IDBValue SerializedScriptValue::writeBlobsToDiskForIndexedDBSynchronously() +// { +// ASSERT(!isMainThread()); + +// BinarySemaphore semaphore; +// IDBValue value; +// callOnMainThread([this, &semaphore, &value] { +// writeBlobsToDiskForIndexedDB([&semaphore, &value](IDBValue&& result) { +// ASSERT(isMainThread()); +// value.setAsIsolatedCopy(result); + +// semaphore.signal(); +// }); +// }); +// semaphore.wait(); + +// return value; +// } } // namespace WebCore diff --git a/src/bun.js/bindings/webcore/SerializedScriptValue.h b/src/bun.js/bindings/webcore/SerializedScriptValue.h index 0b3c8d0e017f..80873f8920d0 100644 --- a/src/bun.js/bindings/webcore/SerializedScriptValue.h +++ b/src/bun.js/bindings/webcore/SerializedScriptValue.h @@ -26,8 +26,8 @@ #pragma once -#include "Blob.h" -#include "DetachedRTCDataChannel.h" +// #include "Blob.h" +// #include "DetachedRTCDataChannel.h" #include "ExceptionOr.h" #include #include @@ -59,9 +59,9 @@ namespace WebCore { #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) class DetachedOffscreenCanvas; #endif -class IDBValue; -class MessagePort; -class ImageBitmapBacking; +// class IDBValue; +// class MessagePort; +// class ImageBitmapBacking; class FragmentedSharedBuffer; enum class SerializationReturnCode; @@ -84,8 +84,11 @@ class SerializedScriptValue : public ThreadSafeRefCounted WTF_MAKE_FAST_ALLOCATED_WITH_HEAP_IDENTIFIER(SerializedScriptValue); public: - WEBCORE_EXPORT static ExceptionOr> create(JSC::JSGlobalObject&, JSC::JSValue, Vector>&& transfer, Vector>&, SerializationForStorage = SerializationForStorage::No, SerializationContext = SerializationContext::Default); + // WEBCORE_EXPORT static ExceptionOr> create(JSC::JSGlobalObject&, JSC::JSValue, Vector>&& transfer, Vector>&, SerializationForStorage = SerializationForStorage::No, SerializationContext = SerializationContext::Default); + WEBCORE_EXPORT static ExceptionOr> create(JSC::JSGlobalObject&, JSC::JSValue, Vector>&& transfer, SerializationForStorage = SerializationForStorage::No, SerializationContext = SerializationContext::Default); + WEBCORE_EXPORT static RefPtr create(JSC::JSGlobalObject&, JSC::JSValue, SerializationForStorage = SerializationForStorage::No, SerializationErrorMode = SerializationErrorMode::Throwing, SerializationContext = SerializationContext::Default); + static RefPtr convert(JSC::JSGlobalObject& globalObject, JSC::JSValue value) { return create(globalObject, value, SerializationForStorage::Yes); } WEBCORE_EXPORT static RefPtr create(StringView); @@ -93,8 +96,10 @@ class SerializedScriptValue : public ThreadSafeRefCounted static Ref nullValue(); WEBCORE_EXPORT JSC::JSValue deserialize(JSC::JSGlobalObject&, JSC::JSGlobalObject*, SerializationErrorMode = SerializationErrorMode::Throwing, bool* didFail = nullptr); - WEBCORE_EXPORT JSC::JSValue deserialize(JSC::JSGlobalObject&, JSC::JSGlobalObject*, const Vector>&, SerializationErrorMode = SerializationErrorMode::Throwing, bool* didFail = nullptr); - JSC::JSValue deserialize(JSC::JSGlobalObject&, JSC::JSGlobalObject*, const Vector>&, const Vector& blobURLs, const Vector& blobFilePaths, SerializationErrorMode = SerializationErrorMode::Throwing, bool* didFail = nullptr); + // WEBCORE_EXPORT JSC::JSValue deserialize(JSC::JSGlobalObject&, JSC::JSGlobalObject*, const Vector>&, SerializationErrorMode = SerializationErrorMode::Throwing, bool* didFail = nullptr); + + // JSC::JSValue deserialize(JSC::JSGlobalObject&, JSC::JSGlobalObject*, const Vector>&, const Vector& blobURLs, const Vector& blobFilePaths, SerializationErrorMode = SerializationErrorMode::Throwing, bool* didFail = nullptr); + JSC::JSValue deserialize(JSC::JSGlobalObject&, JSC::JSGlobalObject*, const Vector& blobURLs, const Vector& blobFilePaths, SerializationErrorMode = SerializationErrorMode::Throwing, bool* didFail = nullptr); static uint32_t wireFormatVersion(); @@ -104,12 +109,12 @@ class SerializedScriptValue : public ThreadSafeRefCounted WEBCORE_EXPORT static RefPtr create(JSContextRef, JSValueRef, JSValueRef* exception); WEBCORE_EXPORT JSValueRef deserialize(JSContextRef, JSValueRef* exception); - bool hasBlobURLs() const { return !m_blobHandles.isEmpty(); } + // bool hasBlobURLs() const { return !m_blobHandles.isEmpty(); } - Vector blobURLs() const; - Vector blobHandles() const { return crossThreadCopy(m_blobHandles); } - void writeBlobsToDiskForIndexedDB(CompletionHandler&&); - IDBValue writeBlobsToDiskForIndexedDBSynchronously(); + // Vector blobURLs() const; + // Vector blobHandles() const { return crossThreadCopy(m_blobHandles); } + // void writeBlobsToDiskForIndexedDB(CompletionHandler&&); + // IDBValue writeBlobsToDiskForIndexedDBSynchronously(); static Ref createFromWireBytes(Vector&& data) { return adoptRef(*new SerializedScriptValue(WTFMove(data))); @@ -124,7 +129,18 @@ class SerializedScriptValue : public ThreadSafeRefCounted WEBCORE_EXPORT ~SerializedScriptValue(); private: - static ExceptionOr> create(JSC::JSGlobalObject&, JSC::JSValue, Vector>&& transfer, Vector>&, SerializationForStorage, SerializationErrorMode, SerializationContext); + // static ExceptionOr> create(JSC::JSGlobalObject&, JSC::JSValue, Vector>&& transfer, Vector>&, SerializationForStorage, SerializationErrorMode, SerializationContext); + // WEBCORE_EXPORT SerializedScriptValue(Vector&&, std::unique_ptr&& = nullptr + // #if ENABLE(WEB_RTC) + // , + // Vector>&& = {} + // #endif + // #if ENABLE(WEB_CODECS) + // , + // Vector>&& = {}, Vector&& = {} + // #endif + // ); + static ExceptionOr> create(JSC::JSGlobalObject&, JSC::JSValue, Vector>&& transfer, SerializationForStorage, SerializationErrorMode, SerializationContext); WEBCORE_EXPORT SerializedScriptValue(Vector&&, std::unique_ptr&& = nullptr #if ENABLE(WEB_RTC) , @@ -136,7 +152,25 @@ class SerializedScriptValue : public ThreadSafeRefCounted #endif ); - SerializedScriptValue(Vector&&, Vector&& blobHandles, std::unique_ptr, std::unique_ptr sharedBuffers, Vector>&& backingStores + // SerializedScriptValue(Vector&&, Vector&& blobHandles, std::unique_ptr, std::unique_ptr sharedBuffers, Vector>&& backingStores + // #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) + // , + // Vector>&& = {} + // #endif + // #if ENABLE(WEB_RTC) + // , + // Vector>&& = {} + // #endif + // #if ENABLE(WEBASSEMBLY) + // , + // std::unique_ptr = nullptr, std::unique_ptr = nullptr + // #endif + // #if ENABLE(WEB_CODECS) + // , + // Vector>&& = {}, Vector&& = {} + // #endif + // ); + SerializedScriptValue(Vector&&, std::unique_ptr, std::unique_ptr sharedBuffers #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) , Vector>&& = {} @@ -160,7 +194,7 @@ class SerializedScriptValue : public ThreadSafeRefCounted Vector m_data; std::unique_ptr m_arrayBufferContentsArray; std::unique_ptr m_sharedBufferContentsArray; - Vector> m_backingStores; + // Vector> m_backingStores; #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) Vector> m_detachedOffscreenCanvases; #endif @@ -175,7 +209,7 @@ class SerializedScriptValue : public ThreadSafeRefCounted Vector> m_serializedVideoChunks; Vector m_serializedVideoFrames; #endif - Vector m_blobHandles; + // Vector m_blobHandles; size_t m_memoryCost { 0 }; }; From 9af8e62e4d05f674ca3ff6f089b726e3f61cfdc6 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Wed, 12 Jul 2023 11:11:36 -0700 Subject: [PATCH 04/14] add `transfer` option --- src/bun.js/bindings/ZigGlobalObject.cpp | 36 +++++++++++++++++-- .../webcore/SerializedScriptValue.cpp | 2 +- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index 6d2988cc585e..a555489c8091 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -1214,11 +1214,43 @@ JSC_DEFINE_HOST_FUNCTION(functionStructuredClone, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { JSC::VM& vm = globalObject->vm(); + auto throwScope = DECLARE_THROW_SCOPE(vm); + + if (callFrame->argumentCount() == 0) { + throwTypeError(globalObject, throwScope, "structuredClone requires 1 argument"_s); + return JSValue::encode(jsUndefined()); + } JSC::JSValue value = callFrame->argument(0); + JSC::JSValue options = callFrame->argument(1); + + Vector> transferList; + + if (options.isObject()) { + JSC::JSObject* optionsObject = options.getObject(); + JSC::JSValue transferListValue = optionsObject->get(globalObject, vm.propertyNames->transfer); + if (transferListValue.isObject()) { + JSC::JSObject* transferListObject = transferListValue.getObject(); + if (transferListObject->inherits()) { + JSC::JSArray* transferListArray = JSC::jsCast(transferListObject); + for (unsigned i = 0; i < transferListArray->length(); i++) { + JSC::JSValue transferListValue = transferListArray->get(globalObject, i); + if (transferListValue.isObject()) { + JSC::JSObject* transferListObject = transferListValue.getObject(); + transferList.append(JSC::Strong(vm, transferListObject)); + } + } + } + } + } + + ExceptionOr> serialized = SerializedScriptValue::create(*globalObject, value, WTFMove(transferList)); + if (serialized.hasException()) { + WebCore::propagateException(*globalObject, throwScope, serialized.releaseException()); + return JSValue::encode(jsUndefined()); + } - auto serialized = SerializedScriptValue::convert(*globalObject, value); - JSValue deserialized = serialized->deserialize(*globalObject, globalObject, SerializationErrorMode::Throwing); + JSValue deserialized = serialized.returnValue()->deserialize(*globalObject, globalObject); return JSValue::encode(deserialized); } diff --git a/src/bun.js/bindings/webcore/SerializedScriptValue.cpp b/src/bun.js/bindings/webcore/SerializedScriptValue.cpp index c105784733d7..ac3b52ef1029 100644 --- a/src/bun.js/bindings/webcore/SerializedScriptValue.cpp +++ b/src/bun.js/bindings/webcore/SerializedScriptValue.cpp @@ -5018,7 +5018,7 @@ RefPtr SerializedScriptValue::create(JSC::JSGlobalObject& ExceptionOr> SerializedScriptValue::create(JSGlobalObject& globalObject, JSValue value, Vector>&& transferList, SerializationForStorage forStorage, SerializationContext serializationContext) { - return create(globalObject, value, WTFMove(transferList), forStorage, SerializationErrorMode::NonThrowing, serializationContext); + return create(globalObject, value, WTFMove(transferList), forStorage, SerializationErrorMode::Throwing, serializationContext); } // ExceptionOr> SerializedScriptValue::create(JSGlobalObject& lexicalGlobalObject, JSValue value, Vector>&& transferList, Vector>& messagePorts, SerializationForStorage forStorage, SerializationErrorMode throwExceptions, SerializationContext context) From 519814522300447ffecc25d6b4b2662b735cb1f9 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Wed, 12 Jul 2023 11:11:48 -0700 Subject: [PATCH 05/14] tests --- test/js/web/workers/structured-clone.test.ts | 119 +++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 test/js/web/workers/structured-clone.test.ts diff --git a/test/js/web/workers/structured-clone.test.ts b/test/js/web/workers/structured-clone.test.ts new file mode 100644 index 000000000000..8bebe1f99810 --- /dev/null +++ b/test/js/web/workers/structured-clone.test.ts @@ -0,0 +1,119 @@ +describe("structured clone", () => { + let primitives_tests = [ + { description: "primitive undefined", value: undefined }, + { description: "primitive null", value: null }, + { description: "primitive true", value: true }, + { description: "primitive false", value: false }, + { description: "primitive string, empty string", value: "" }, + { description: "primitive string, lone high surrogate", value: "\uD800" }, + { description: "primitive string, lone low surrogate", value: "\uDC00" }, + { description: "primitive string, NUL", value: "\u0000" }, + { description: "primitive string, astral character", value: "\uDBFF\uDFFD" }, + { description: "primitive number, 0.2", value: 0.2 }, + { description: "primitive number, 0", value: 0 }, + { description: "primitive number, -0", value: -0 }, + { description: "primitive number, NaN", value: NaN }, + { description: "primitive number, Infinity", value: Infinity }, + { description: "primitive number, -Infinity", value: -Infinity }, + { description: "primitive number, 9007199254740992", value: 9007199254740992 }, + { description: "primitive number, -9007199254740992", value: -9007199254740992 }, + { description: "primitive number, 9007199254740994", value: 9007199254740994 }, + { description: "primitive number, -9007199254740994", value: -9007199254740994 }, + { description: "primitive BigInt, 0n", value: 0n }, + { description: "primitive BigInt, -0n", value: -0n }, + { description: "primitive BigInt, -9007199254740994000n", value: -9007199254740994000n }, + { + description: "primitive BigInt, -9007199254740994000900719925474099400090071992547409940009007199254740994000n", + value: -9007199254740994000900719925474099400090071992547409940009007199254740994000n, + }, + ]; + for (let { description, value } of primitives_tests) { + test(description, () => { + var cloned = structuredClone(value); + expect(cloned).toBe(value); + }); + } + + test("Array with primitives", () => { + var input = [ + undefined, + null, + true, + false, + "", + "\uD800", + "\uDC00", + "\u0000", + "\uDBFF\uDFFD", + 0.2, + 0, + -0, + NaN, + Infinity, + -Infinity, + 9007199254740992, + -9007199254740992, + 9007199254740994, + -9007199254740994, + -12n, + -0n, + 0n, + ]; + var cloned = structuredClone(input); + expect(cloned).toBeInstanceOf(Array); + expect(cloned).not.toBe(input); + expect(cloned.length).toEqual(input.length); + for (const x in input) { + expect(cloned[x]).toBe(input[x]); + } + }); + test("Object with primitives", () => { + var input: any = { + undefined: undefined, + null: null, + true: true, + false: false, + empty: "", + "high surrogate": "\uD800", + "low surrogate": "\uDC00", + nul: "\u0000", + astral: "\uDBFF\uDFFD", + "0.2": 0.2, + "0": 0, + "-0": -0, + NaN: NaN, + Infinity: Infinity, + "-Infinity": -Infinity, + "9007199254740992": 9007199254740992, + "-9007199254740992": -9007199254740992, + "9007199254740994": 9007199254740994, + "-9007199254740994": -9007199254740994, + "-12n": -12n, + "-0n": -0n, + "0n": 0n, + }; + var cloned = structuredClone(input); + expect(cloned).toBeInstanceOf(Object); + expect(cloned).not.toBeInstanceOf(Array); + expect(cloned).not.toBe(input); + for (const x in input) { + expect(cloned[x]).toBe(input[x]); + } + }); + + describe("transferrables", () => { + test("ArrayBuffer", () => { + const buffer = Uint8Array.from([1]).buffer; + const cloned = structuredClone(buffer, { transfer: [buffer] }); + expect(buffer.byteLength).toBe(0); + expect(cloned.byteLength).toBe(1); + }); + test("A detached ArrayBuffer cannot be transferred", () => { + const buffer = new ArrayBuffer(); + const cloned = structuredClone(buffer, { transfer: [buffer] }); + expect(() => { + structuredClone(buffer, { transfer: [buffer] }); + }).toThrow(DOMException); + }); + }); +}); From a61925e852f6ab46cd69ca053c3519386560fbcd Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Thu, 13 Jul 2023 16:44:34 -0700 Subject: [PATCH 06/14] serialize/deserialize blobs --- src/bun.js/bindings/ZigGeneratedClasses.cpp | 46 ++++++ src/bun.js/bindings/ZigGeneratedClasses.h | 24 +++ src/bun.js/bindings/generated_classes.zig | 10 ++ .../webcore/SerializedScriptValue.cpp | 46 +++++- .../bindings/webcore/SerializedScriptValue.h | 4 + src/bun.js/scripts/class-definitions.ts | 3 + src/bun.js/scripts/generate-classes.ts | 138 +++++++++++++++++- src/bun.js/test/expect.zig | 3 - src/bun.js/webcore/blob.zig | 81 ++++++++++ src/bun.js/webcore/response.classes.ts | 1 + 10 files changed, 341 insertions(+), 15 deletions(-) diff --git a/src/bun.js/bindings/ZigGeneratedClasses.cpp b/src/bun.js/bindings/ZigGeneratedClasses.cpp index b4d672328ed1..dac3acf29e1f 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses.cpp +++ b/src/bun.js/bindings/ZigGeneratedClasses.cpp @@ -98,6 +98,11 @@ class JSBlobConstructor final : public JSC::InternalFunction { extern "C" void* BlobClass__construct(JSC::JSGlobalObject*, JSC::CallFrame*); JSC_DECLARE_CUSTOM_GETTER(jsBlobConstructor); + +extern "C" void Blob__onStructuredCloneSerialize(void*, JSC::JSGlobalObject*, void*, void (*)(CloneSerializer*, const uint8_t*, uint32_t)); + +extern "C" JSC::EncodedJSValue Blob__onStructuredCloneDeserialize(JSC::JSGlobalObject*, const uint8_t*, const uint8_t*); + extern "C" void BlobClass__finalize(void*); extern "C" EncodedJSValue BlobPrototype__getArrayBuffer(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); @@ -1250,6 +1255,7 @@ class JSBuildMessageConstructor final : public JSC::InternalFunction { extern "C" void* BuildMessageClass__construct(JSC::JSGlobalObject*, JSC::CallFrame*); JSC_DECLARE_CUSTOM_GETTER(jsBuildMessageConstructor); + extern "C" void BuildMessageClass__finalize(void*); extern "C" EncodedJSValue BuildMessagePrototype__toPrimitive(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); @@ -1727,6 +1733,7 @@ class JSCryptoHasherConstructor final : public JSC::InternalFunction { extern "C" void* CryptoHasherClass__construct(JSC::JSGlobalObject*, JSC::CallFrame*); JSC_DECLARE_CUSTOM_GETTER(jsCryptoHasherConstructor); + extern "C" void CryptoHasherClass__finalize(void*); extern "C" JSC::EncodedJSValue CryptoHasherPrototype__getAlgorithm(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject); @@ -2155,6 +2162,7 @@ class JSDirentConstructor final : public JSC::InternalFunction { extern "C" void* DirentClass__construct(JSC::JSGlobalObject*, JSC::CallFrame*); JSC_DECLARE_CUSTOM_GETTER(jsDirentConstructor); + extern "C" void DirentClass__finalize(void*); extern "C" EncodedJSValue DirentPrototype__isBlockDevice(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); @@ -2681,6 +2689,7 @@ class JSExpectConstructor final : public JSC::InternalFunction { extern "C" void* ExpectClass__construct(JSC::JSGlobalObject*, JSC::CallFrame*); JSC_DECLARE_CUSTOM_GETTER(jsExpectConstructor); + extern "C" void ExpectClass__finalize(void*); extern "C" JSC_DECLARE_HOST_FUNCTION(ExpectClass__call); @@ -5783,6 +5792,7 @@ class JSFileSystemRouterConstructor final : public JSC::InternalFunction { extern "C" void* FileSystemRouterClass__construct(JSC::JSGlobalObject*, JSC::CallFrame*); JSC_DECLARE_CUSTOM_GETTER(jsFileSystemRouterConstructor); + extern "C" void FileSystemRouterClass__finalize(void*); extern "C" EncodedJSValue FileSystemRouterPrototype__match(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); @@ -6191,6 +6201,7 @@ class JSListenerPrototype final : public JSC::JSNonFinalObject { extern "C" void* ListenerClass__construct(JSC::JSGlobalObject*, JSC::CallFrame*); JSC_DECLARE_CUSTOM_GETTER(jsListenerConstructor); + extern "C" void ListenerClass__finalize(void*); extern "C" JSC::EncodedJSValue ListenerPrototype__getData(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject); @@ -6640,6 +6651,7 @@ class JSMD4Constructor final : public JSC::InternalFunction { extern "C" void* MD4Class__construct(JSC::JSGlobalObject*, JSC::CallFrame*); JSC_DECLARE_CUSTOM_GETTER(jsMD4Constructor); + extern "C" void MD4Class__finalize(void*); extern "C" JSC::EncodedJSValue MD4Prototype__getByteLength(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject); @@ -6967,6 +6979,7 @@ class JSMD5Constructor final : public JSC::InternalFunction { extern "C" void* MD5Class__construct(JSC::JSGlobalObject*, JSC::CallFrame*); JSC_DECLARE_CUSTOM_GETTER(jsMD5Constructor); + extern "C" void MD5Class__finalize(void*); extern "C" JSC::EncodedJSValue MD5Prototype__getByteLength(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject); @@ -7257,6 +7270,7 @@ class JSMatchedRoutePrototype final : public JSC::JSNonFinalObject { extern "C" void* MatchedRouteClass__construct(JSC::JSGlobalObject*, JSC::CallFrame*); JSC_DECLARE_CUSTOM_GETTER(jsMatchedRouteConstructor); + extern "C" void MatchedRouteClass__finalize(void*); extern "C" JSC::EncodedJSValue MatchedRoutePrototype__getFilePath(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject); @@ -7756,6 +7770,7 @@ class JSNodeJSFSConstructor final : public JSC::InternalFunction { extern "C" void* NodeJSFSClass__construct(JSC::JSGlobalObject*, JSC::CallFrame*); JSC_DECLARE_CUSTOM_GETTER(jsNodeJSFSConstructor); + extern "C" void NodeJSFSClass__finalize(void*); extern "C" EncodedJSValue NodeJSFSPrototype__access(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); @@ -10537,6 +10552,7 @@ class JSRequestConstructor final : public JSC::InternalFunction { extern "C" void* RequestClass__construct(JSC::JSGlobalObject*, JSC::CallFrame*); JSC_DECLARE_CUSTOM_GETTER(jsRequestConstructor); + extern "C" void RequestClass__finalize(void*); extern "C" EncodedJSValue RequestPrototype__getArrayBuffer(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); @@ -11309,6 +11325,7 @@ class JSResolveMessageConstructor final : public JSC::InternalFunction { extern "C" void* ResolveMessageClass__construct(JSC::JSGlobalObject*, JSC::CallFrame*); JSC_DECLARE_CUSTOM_GETTER(jsResolveMessageConstructor); + extern "C" void ResolveMessageClass__finalize(void*); extern "C" EncodedJSValue ResolveMessagePrototype__toPrimitive(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); @@ -11898,6 +11915,7 @@ class JSResponseConstructor final : public JSC::InternalFunction { extern "C" void* ResponseClass__construct(JSC::JSGlobalObject*, JSC::CallFrame*); JSC_DECLARE_CUSTOM_GETTER(jsResponseConstructor); + extern "C" void ResponseClass__finalize(void*); extern "C" EncodedJSValue ResponsePrototype__getArrayBuffer(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); @@ -12599,6 +12617,7 @@ class JSSHA1Constructor final : public JSC::InternalFunction { extern "C" void* SHA1Class__construct(JSC::JSGlobalObject*, JSC::CallFrame*); JSC_DECLARE_CUSTOM_GETTER(jsSHA1Constructor); + extern "C" void SHA1Class__finalize(void*); extern "C" JSC::EncodedJSValue SHA1Prototype__getByteLength(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject); @@ -12926,6 +12945,7 @@ class JSSHA224Constructor final : public JSC::InternalFunction { extern "C" void* SHA224Class__construct(JSC::JSGlobalObject*, JSC::CallFrame*); JSC_DECLARE_CUSTOM_GETTER(jsSHA224Constructor); + extern "C" void SHA224Class__finalize(void*); extern "C" JSC::EncodedJSValue SHA224Prototype__getByteLength(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject); @@ -13253,6 +13273,7 @@ class JSSHA256Constructor final : public JSC::InternalFunction { extern "C" void* SHA256Class__construct(JSC::JSGlobalObject*, JSC::CallFrame*); JSC_DECLARE_CUSTOM_GETTER(jsSHA256Constructor); + extern "C" void SHA256Class__finalize(void*); extern "C" JSC::EncodedJSValue SHA256Prototype__getByteLength(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject); @@ -13580,6 +13601,7 @@ class JSSHA384Constructor final : public JSC::InternalFunction { extern "C" void* SHA384Class__construct(JSC::JSGlobalObject*, JSC::CallFrame*); JSC_DECLARE_CUSTOM_GETTER(jsSHA384Constructor); + extern "C" void SHA384Class__finalize(void*); extern "C" JSC::EncodedJSValue SHA384Prototype__getByteLength(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject); @@ -13907,6 +13929,7 @@ class JSSHA512Constructor final : public JSC::InternalFunction { extern "C" void* SHA512Class__construct(JSC::JSGlobalObject*, JSC::CallFrame*); JSC_DECLARE_CUSTOM_GETTER(jsSHA512Constructor); + extern "C" void SHA512Class__finalize(void*); extern "C" JSC::EncodedJSValue SHA512Prototype__getByteLength(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject); @@ -14234,6 +14257,7 @@ class JSSHA512_256Constructor final : public JSC::InternalFunction { extern "C" void* SHA512_256Class__construct(JSC::JSGlobalObject*, JSC::CallFrame*); JSC_DECLARE_CUSTOM_GETTER(jsSHA512_256Constructor); + extern "C" void SHA512_256Class__finalize(void*); extern "C" JSC::EncodedJSValue SHA512_256Prototype__getByteLength(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject); @@ -14561,6 +14585,7 @@ class JSServerWebSocketConstructor final : public JSC::InternalFunction { extern "C" void* ServerWebSocketClass__construct(JSC::JSGlobalObject*, JSC::CallFrame*); JSC_DECLARE_CUSTOM_GETTER(jsServerWebSocketConstructor); + extern "C" void ServerWebSocketClass__finalize(void*); extern "C" JSC::EncodedJSValue ServerWebSocketPrototype__getBinaryType(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject); @@ -15411,6 +15436,7 @@ class JSStatsConstructor final : public JSC::InternalFunction { extern "C" void* StatsClass__construct(JSC::JSGlobalObject*, JSC::CallFrame*); JSC_DECLARE_CUSTOM_GETTER(jsStatsConstructor); + extern "C" void StatsClass__finalize(void*); extern "C" JSC::EncodedJSValue StatsPrototype__atime(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject); @@ -16340,6 +16366,7 @@ class JSSubprocessPrototype final : public JSC::JSNonFinalObject { extern "C" void* SubprocessClass__construct(JSC::JSGlobalObject*, JSC::CallFrame*); JSC_DECLARE_CUSTOM_GETTER(jsSubprocessConstructor); + extern "C" void SubprocessClass__finalize(void*); extern "C" JSC::EncodedJSValue SubprocessPrototype__getExitCode(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject); @@ -16870,6 +16897,7 @@ class JSTCPSocketPrototype final : public JSC::JSNonFinalObject { extern "C" void* TCPSocketClass__construct(JSC::JSGlobalObject*, JSC::CallFrame*); JSC_DECLARE_CUSTOM_GETTER(jsTCPSocketConstructor); + extern "C" void TCPSocketClass__finalize(void*); extern "C" JSC::EncodedJSValue TCPSocketPrototype__getALPNProtocol(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject); @@ -17555,6 +17583,7 @@ class JSTLSSocketPrototype final : public JSC::JSNonFinalObject { extern "C" void* TLSSocketClass__construct(JSC::JSGlobalObject*, JSC::CallFrame*); JSC_DECLARE_CUSTOM_GETTER(jsTLSSocketConstructor); + extern "C" void TLSSocketClass__finalize(void*); extern "C" JSC::EncodedJSValue TLSSocketPrototype__getALPNProtocol(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject); @@ -18277,6 +18306,7 @@ class JSTextDecoderConstructor final : public JSC::InternalFunction { extern "C" void* TextDecoderClass__construct(JSC::JSGlobalObject*, JSC::CallFrame*); JSC_DECLARE_CUSTOM_GETTER(jsTextDecoderConstructor); + extern "C" void TextDecoderClass__finalize(void*); extern "C" EncodedJSValue TextDecoderPrototype__decode(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); @@ -18988,6 +19018,7 @@ class JSTranspilerConstructor final : public JSC::InternalFunction { extern "C" void* TranspilerClass__construct(JSC::JSGlobalObject*, JSC::CallFrame*); JSC_DECLARE_CUSTOM_GETTER(jsTranspilerConstructor); + extern "C" void TranspilerClass__finalize(void*); extern "C" EncodedJSValue TranspilerPrototype__scan(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); @@ -19283,5 +19314,20 @@ extern "C" EncodedJSValue Transpiler__create(Zig::GlobalObject* globalObject, vo return JSValue::encode(instance); } +std::optional StructuredCloneableSerialize::fromJS(JSC::JSValue value) +{ + if (auto* result = jsDynamicCast(value)) { + return StructuredCloneableSerialize { .cppWriteBytes = SerializedScriptValue::writeBytesForBun, .zigFunction = Blob__onStructuredCloneSerialize, .tag = 254, .impl = result->wrapped() }; + } + return std::nullopt; +} + +std::optional StructuredCloneableDeserialize::fromTagDeserialize(uint8_t tag, JSC::JSGlobalObject* globalObject, const uint8_t* ptr, const uint8_t* end) +{ + if (tag == 254) { + return Blob__onStructuredCloneDeserialize(globalObject, ptr, end); + } + return std::nullopt; +} } // namespace WebCore diff --git a/src/bun.js/bindings/ZigGeneratedClasses.h b/src/bun.js/bindings/ZigGeneratedClasses.h index 1631f960e775..f66c65e40e4e 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses.h +++ b/src/bun.js/bindings/ZigGeneratedClasses.h @@ -10,6 +10,7 @@ namespace Zig { #include "JSDOMWrapper.h" #include +#include "SerializedScriptValue.h" namespace WebCore { using namespace Zig; @@ -1998,4 +1999,27 @@ class JSTranspiler final : public JSC::JSDestructibleObject { void finishCreation(JSC::VM&); }; +class StructuredCloneableSerialize { +public: + void (*cppWriteBytes)(CloneSerializer*, const uint8_t*, uint32_t); + + std::function zigFunction; + + uint8_t tag; + + // the type from zig + void* impl; + + static std::optional fromJS(JSC::JSValue); + void write(CloneSerializer* serializer, JSC::JSGlobalObject* globalObject) + { + zigFunction(impl, globalObject, serializer, cppWriteBytes); + } +}; + +class StructuredCloneableDeserialize { +public: + static std::optional fromTagDeserialize(uint8_t tag, JSC::JSGlobalObject*, const uint8_t*, const uint8_t*); +}; + } diff --git a/src/bun.js/bindings/generated_classes.zig b/src/bun.js/bindings/generated_classes.zig index 171bba79215d..8275f345cf62 100644 --- a/src/bun.js/bindings/generated_classes.zig +++ b/src/bun.js/bindings/generated_classes.zig @@ -98,6 +98,14 @@ pub const JSBlob = struct { extern fn Blob__dangerouslySetPtr(JSC.JSValue, ?*Blob) bool; comptime { + if (@TypeOf(Blob.onStructuredCloneSerialize) != (fn (*Blob, globalThis: *JSC.JSGlobalObject, ctx: *anyopaque, writeBytes: *const fn (*anyopaque, ptr: [*]const u8, len: u32) callconv(.C) void) callconv(.C) void)) { + @compileLog("Blob.onStructuredCloneSerialize is not a structured clone serialize function"); + } + + if (@TypeOf(Blob.onStructuredCloneDeserialize) != (fn (globalThis: *JSC.JSGlobalObject, ptr: [*]u8, end: [*]u8) callconv(.C) JSC.JSValue)) { + @compileLog("Blob.onStructuredCloneDeserialize is not a structured clone deserialize function"); + } + if (@TypeOf(Blob.constructor) != (fn (*JSC.JSGlobalObject, *JSC.CallFrame) callconv(.C) ?*Blob)) { @compileLog("Blob.constructor is not a constructor"); } @@ -149,6 +157,8 @@ pub const JSBlob = struct { @export(Blob.getText, .{ .name = "BlobPrototype__getText" }); @export(Blob.getType, .{ .name = "BlobPrototype__getType" }); @export(Blob.getWriter, .{ .name = "BlobPrototype__getWriter" }); + @export(Blob.onStructuredCloneDeserialize, .{ .name = "Blob__onStructuredCloneDeserialize" }); + @export(Blob.onStructuredCloneSerialize, .{ .name = "Blob__onStructuredCloneSerialize" }); } } }; diff --git a/src/bun.js/bindings/webcore/SerializedScriptValue.cpp b/src/bun.js/bindings/webcore/SerializedScriptValue.cpp index ac3b52ef1029..250930c55d62 100644 --- a/src/bun.js/bindings/webcore/SerializedScriptValue.cpp +++ b/src/bun.js/bindings/webcore/SerializedScriptValue.cpp @@ -101,6 +101,9 @@ #include #include +#include "blob.h" +#include "ZigGeneratedClasses.h" + #if USE(CG) #include #endif @@ -223,6 +226,10 @@ enum SerializationTag { WebCodecsVideoFrameTag = 53, #endif ResizableArrayBufferTag = 54, + + Bun__BlobTag = 254, + // bun types start at 254 and decrease with each addition + ErrorTag = 255 }; @@ -664,6 +671,12 @@ class CloneSerializer : CloneBase { WTF_FORBID_HEAP_ALLOCATION; public: + Vector& m_buffer; + + void write(const uint8_t* data, unsigned length) + { + writeLittleEndian(m_buffer, data, length); + } // static SerializationReturnCode serialize(JSGlobalObject* lexicalGlobalObject, JSValue value, Vector>& messagePorts, Vector>& arrayBuffers, const Vector>& imageBitmaps, // #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) // const Vector>& offscreenCanvases, @@ -1446,6 +1459,15 @@ class CloneSerializer : CloneBase { // write(file.get()); // return true; // } + + // write bun types + if (auto _cloneable = StructuredCloneableSerialize::fromJS(value)) { + StructuredCloneableSerialize cloneable = WTFMove(_cloneable.value()); + write(cloneable.tag); + cloneable.write(this, m_lexicalGlobalObject); + return true; + } + // if (auto* blob = JSBlob::toWrapped(vm, obj)) { // write(BlobTag); // m_blobHandles.append(blob->handle().isolatedCopy()); @@ -2135,13 +2157,6 @@ class CloneSerializer : CloneBase { } } #endif - - void write(const uint8_t* data, unsigned length) - { - m_buffer.append(data, length); - } - - Vector& m_buffer; // Vector& m_blobHandles; ObjectPool m_objectPool; ObjectPool m_transferredMessagePorts; @@ -2169,6 +2184,12 @@ class CloneSerializer : CloneBase { SerializationForStorage m_forStorage; }; +void SerializedScriptValue::writeBytesForBun(CloneSerializer* ctx, const uint8_t* data, uint32_t size) +{ + writeLittleEndian(ctx->m_buffer, size); + ctx->write(data, size); +} + SerializationReturnCode CloneSerializer::serialize(JSValue in) { VM& vm = m_lexicalGlobalObject->vm(); @@ -4088,6 +4109,17 @@ class CloneDeserializer : CloneBase { JSValue readTerminal() { SerializationTag tag = readTag(); + + // read bun types + if (auto value = StructuredCloneableDeserialize::fromTagDeserialize(tag, m_lexicalGlobalObject, m_ptr, m_end)) { + JSValue deserialized = JSValue::decode(value.value()); + if (deserialized.isEmpty()) { + fail(); + return JSValue(); + } + return deserialized; + } + switch (tag) { case UndefinedTag: return jsUndefined(); diff --git a/src/bun.js/bindings/webcore/SerializedScriptValue.h b/src/bun.js/bindings/webcore/SerializedScriptValue.h index 80873f8920d0..0eb76ceda8fe 100644 --- a/src/bun.js/bindings/webcore/SerializedScriptValue.h +++ b/src/bun.js/bindings/webcore/SerializedScriptValue.h @@ -62,6 +62,7 @@ class DetachedOffscreenCanvas; // class IDBValue; // class MessagePort; // class ImageBitmapBacking; +class CloneSerializer; class FragmentedSharedBuffer; enum class SerializationReturnCode; @@ -84,6 +85,9 @@ class SerializedScriptValue : public ThreadSafeRefCounted WTF_MAKE_FAST_ALLOCATED_WITH_HEAP_IDENTIFIER(SerializedScriptValue); public: + // write the length of the bytes in the first 32 bits, then follows with the bytes + static void writeBytesForBun(CloneSerializer*, const uint8_t*, uint32_t); + // WEBCORE_EXPORT static ExceptionOr> create(JSC::JSGlobalObject&, JSC::JSValue, Vector>&& transfer, Vector>&, SerializationForStorage = SerializationForStorage::No, SerializationContext = SerializationContext::Default); WEBCORE_EXPORT static ExceptionOr> create(JSC::JSGlobalObject&, JSC::JSValue, Vector>&& transfer, SerializationForStorage = SerializationForStorage::No, SerializationContext = SerializationContext::Default); diff --git a/src/bun.js/scripts/class-definitions.ts b/src/bun.js/scripts/class-definitions.ts index 59b8f6f05871..281cd62c5dc9 100644 --- a/src/bun.js/scripts/class-definitions.ts +++ b/src/bun.js/scripts/class-definitions.ts @@ -41,6 +41,7 @@ export interface ClassDefinition { configurable?: boolean; enumerable?: boolean; + structuredClone?: boolean | { transferrable: boolean; tag: number }; } export interface CustomField { @@ -58,6 +59,7 @@ export function define( estimatedSize = false, call = false, construct = false, + structuredClone = false, ...rest } = {} as ClassDefinition, ): ClassDefinition { @@ -66,6 +68,7 @@ export function define( call, construct, estimatedSize, + structuredClone, values, klass: Object.fromEntries(Object.entries(klass).sort(([a], [b]) => a.localeCompare(b))), proto: Object.fromEntries(Object.entries(proto).sort(([a], [b]) => a.localeCompare(b))), diff --git a/src/bun.js/scripts/generate-classes.ts b/src/bun.js/scripts/generate-classes.ts index ff6954753290..fb44196428c6 100644 --- a/src/bun.js/scripts/generate-classes.ts +++ b/src/bun.js/scripts/generate-classes.ts @@ -345,6 +345,25 @@ ${ JSC_DECLARE_CUSTOM_GETTER(js${typeName}Constructor);` : "" } + +${ + obj.structuredClone + ? `extern "C" void ${symbolName( + typeName, + "onStructuredCloneSerialize", + )}(void*, JSC::JSGlobalObject*, void*, void (*) (CloneSerializer*, const uint8_t*, uint32_t));` + : "" +} + +${ + obj.structuredClone + ? `extern "C" JSC::EncodedJSValue ${symbolName( + typeName, + "onStructuredCloneDeserialize", + )}(JSC::JSGlobalObject*, const uint8_t*, const uint8_t*);` + : "" +} + ${"finalize" in obj ? `extern "C" void ${classSymbolName(typeName, "finalize")}(void*);` : ""} ${obj.call ? `extern "C" JSC_DECLARE_HOST_FUNCTION(${classSymbolName(typeName, "call")});` : ""} @@ -1200,6 +1219,7 @@ function generateZig( call = false, values = [], hasPendingActivity = false, + structuredClone = false, } = {} as ClassDefinition, ) { const exports = new Map(); @@ -1226,6 +1246,16 @@ function generateZig( Object.values(klass).map(a => appendSymbols(exports, name => classSymbolName(typeName, name), a)); Object.values(proto).map(a => appendSymbols(exports, name => protoSymbolName(typeName, name), a)); + if (structuredClone) { + exports.set("onStructuredCloneSerialize", symbolName(typeName, "onStructuredCloneSerialize")); + + if (structuredClone === "transferrable") { + exports.set("onStructuredCloneTransfer", symbolName(typeName, "onStructuredCloneTransfer")); + } + + exports.set("onStructuredCloneDeserialize", symbolName(typeName, "onStructuredCloneDeserialize")); + } + const externs = Object.entries({ ...proto, ...Object.fromEntries((values || []).map(a => [a, { internal: true }])), @@ -1269,6 +1299,29 @@ function generateZig( `; } + if (structuredClone) { + output += ` + if (@TypeOf(${typeName}.onStructuredCloneSerialize) != (fn(*${typeName}, globalThis: *JSC.JSGlobalObject, ctx: *anyopaque, writeBytes: *const fn(*anyopaque, ptr: [*]const u8, len: u32) callconv(.C) void) callconv(.C) void)) { + @compileLog("${typeName}.onStructuredCloneSerialize is not a structured clone serialize function"); + } + `; + + if (structuredClone === "transferrable") { + exports.set("structuredClone", symbolName(typeName, "onTransferrableStructuredClone")); + output += ` + if (@TypeOf(${typeName}.onStructuredCloneTransfer) != (fn(*${typeName}, globalThis: *JSC.JSGlobalObject, ctx: *anyopaque, write: *const fn(*anyopaque, ptr: [*]const u8, len: usize) callconv(.C) void) callconv(.C) void)) { + @compileLog("${typeName}.onStructuredCloneTransfer is not a structured clone transfer function"); + } + `; + } + + output += ` + if (@TypeOf(${typeName}.onStructuredCloneDeserialize) != (fn(globalThis: *JSC.JSGlobalObject, ptr: [*]u8, end: [*]u8) callconv(.C) JSC.JSValue)) { + @compileLog("${typeName}.onStructuredCloneDeserialize is not a structured clone deserialize function"); + } + `; + } + if (construct && !noConstructor) { output += ` if (@TypeOf(${typeName}.constructor) != (fn(*JSC.JSGlobalObject, *JSC.CallFrame) callconv(.C) ?*${typeName})) { @@ -1532,6 +1585,7 @@ namespace Zig { #include "JSDOMWrapper.h" #include +#include "SerializedScriptValue.h" `, ` @@ -1543,11 +1597,6 @@ using namespace JSC; `, ]; -const GENERATED_CLASSES_FOOTER = ` -} - -`; - const GENERATED_CLASSES_IMPL_HEADER = ` // GENERATED CODE - DO NOT MODIFY BY HAND // Generated by make codegen @@ -1676,6 +1725,84 @@ function writeAndUnlink(path, content) { return Bun.write(path, content); } +const GENERATED_CLASSES_FOOTER = ` + +class StructuredCloneableSerialize { + public: + + void (*cppWriteBytes)(CloneSerializer*, const uint8_t*, uint32_t); + + std::function zigFunction; + + uint8_t tag; + + // the type from zig + void* impl; + + static std::optional fromJS(JSC::JSValue); + void write(CloneSerializer* serializer, JSC::JSGlobalObject* globalObject) + { + zigFunction(impl, globalObject, serializer, cppWriteBytes); + } +}; + +class StructuredCloneableDeserialize { + public: + static std::optional fromTagDeserialize(uint8_t tag, JSC::JSGlobalObject*, const uint8_t*, const uint8_t*); +}; + +} + +`; + +function writeCppSerializers() { + var output = ``; + + var structuredClonable = classes + .filter(a => a.structuredClone) + .sort((a, b) => a.structuredClone.tag < b.structuredClone.tag); + + function fromJSForEachClass(klass) { + return ` + if (auto* result = jsDynamicCast<${className(klass.name)}*>(value)) { + return StructuredCloneableSerialize { .cppWriteBytes = SerializedScriptValue::writeBytesForBun, .zigFunction = ${symbolName( + klass.name, + "onStructuredCloneSerialize", + )}, .tag = ${klass.structuredClone.tag}, .impl = result->wrapped() }; + } + `; + } + + function fromTagDeserializeForEachClass(klass) { + return ` + if (tag == ${klass.structuredClone.tag}) { + return ${symbolName(klass.name, "onStructuredCloneDeserialize")}(globalObject, ptr, end); + } + `; + } + + output += ` + std::optional StructuredCloneableSerialize::fromJS(JSC::JSValue value) + { + ${structuredClonable.map(fromJSForEachClass).join("\n").trim()} + return std::nullopt; + } + `; + + output += ` + std::optional StructuredCloneableDeserialize::fromTagDeserialize(uint8_t tag, JSC::JSGlobalObject* globalObject, const uint8_t* ptr, const uint8_t* end) + { + ${structuredClonable.map(fromTagDeserializeForEachClass).join("\n").trim()} + return std::nullopt; + } + `; + + for (let klass of classes) { + } + + return output; +} + await writeAndUnlink(`${import.meta.dir}/../bindings/generated_classes.zig`, [ ZIG_GENERATED_CLASSES_HEADER, @@ -1699,6 +1826,7 @@ await writeAndUnlink(`${import.meta.dir}/../bindings/ZigGeneratedClasses.h`, [ await writeAndUnlink(`${import.meta.dir}/../bindings/ZigGeneratedClasses.cpp`, [ GENERATED_CLASSES_IMPL_HEADER, ...classes.map(a => generateImpl(a.name, a)), + writeCppSerializers(classes), GENERATED_CLASSES_IMPL_FOOTER, ]); await writeAndUnlink( diff --git a/src/bun.js/test/expect.zig b/src/bun.js/test/expect.zig index e0833f8ed1e3..12f7011fb5cb 100644 --- a/src/bun.js/test/expect.zig +++ b/src/bun.js/test/expect.zig @@ -312,9 +312,6 @@ pub const Expect = struct { const not = this.flags.not; var pass = right.isSameValue(left, globalObject); - if (comptime Environment.allow_assert) { - std.debug.assert(pass == JSC.C.JSValueIsStrictEqual(globalObject, right.asObjectRef(), left.asObjectRef())); - } if (not) pass = !pass; if (pass) return thisValue; diff --git a/src/bun.js/webcore/blob.zig b/src/bun.js/webcore/blob.zig index ef2520049484..213e472ee0cc 100644 --- a/src/bun.js/webcore/blob.zig +++ b/src/bun.js/webcore/blob.zig @@ -208,6 +208,87 @@ pub const Blob = struct { return null; } + pub fn onStructuredCloneSerialize( + this: *Blob, + globalThis: *JSC.JSGlobalObject, + ctx: *anyopaque, + writeBytes: *const fn (*anyopaque, ptr: [*]const u8, len: u32) callconv(.C) void, + ) callconv(.C) void { + _ = globalThis; + this.resolveSize(); + writeBytes(ctx, this.sharedView().ptr, @intCast(u32, this.sharedView().len)); + writeBytes(ctx, this.content_type.ptr, @intCast(u32, this.content_type.len)); + } + + pub fn onStructuredCloneTransfer( + this: *Blob, + globalThis: *JSC.JSGlobalObject, + ctx: *anyopaque, + write: *const fn (*anyopaque, ptr: [*]const u8, len: usize) callconv(.C) void, + ) callconv(.C) void { + _ = write; + _ = ctx; + _ = this; + _ = globalThis; + } + + pub fn onStructuredCloneDeserialize( + globalThis: *JSC.JSGlobalObject, + ptr: [*]u8, + end: [*]u8, + ) callconv(.C) JSValue { + const allocator = globalThis.allocator(); + + const total_length: usize = @intFromPtr(end) - @intFromPtr(ptr); + var slice = ptr[0..total_length]; + + var shared_view_len: u32 = 0; + if (slice.len < 4) return .zero; + shared_view_len |= @as(u32, slice[0]); + shared_view_len |= @as(u32, slice[1]) << 8; + shared_view_len |= @as(u32, slice[2]) << 16; + shared_view_len |= @as(u32, slice[3]) << 24; + + slice = slice[4..]; + + if (slice.len < shared_view_len) return .zero; + const shared_view = slice[0..shared_view_len]; + slice = slice[shared_view_len..]; + + var content_type_len: u32 = 0; + if (slice.len < 4) return .zero; + content_type_len |= @as(u32, slice[0]); + content_type_len |= @as(u32, slice[1]) << 8; + content_type_len |= @as(u32, slice[2]) << 16; + content_type_len |= @as(u32, slice[3]) << 24; + + slice = slice[4..]; + + if (slice.len < content_type_len) return .zero; + const content_type = slice[0..content_type_len]; + slice = slice[content_type_len..]; + + if (Environment.allow_assert) { + std.debug.assert(slice.len == 0); + } + + var blob = Blob.create(shared_view, allocator, globalThis, false); + blob.content_type = brk: { + if (globalThis.bunVM().mimeType(content_type)) |mime| { + break :brk mime.value; + } + + var content_type_buf = allocator.alloc(u8, content_type.len) catch return .zero; + blob.content_type_allocated = true; + break :brk strings.copyLowercase(content_type, content_type_buf); + }; + var blob_ = allocator.create(Blob) catch return .zero; + blob_.* = blob; + blob_.*.allocator = allocator; + + return blob_.toJS(globalThis); + } + const URLSearchParamsConverter = struct { allocator: std.mem.Allocator, buf: []u8 = "", diff --git a/src/bun.js/webcore/response.classes.ts b/src/bun.js/webcore/response.classes.ts index c11cb10b290c..f0d6e14515a3 100644 --- a/src/bun.js/webcore/response.classes.ts +++ b/src/bun.js/webcore/response.classes.ts @@ -125,6 +125,7 @@ export default [ JSType: "0b11101110", klass: {}, configurable: false, + structuredClone: { transferrable: false, tag: 254 }, proto: { text: { fn: "getText" }, json: { fn: "getJSON" }, From 196143833bd63be61fd529d0ad8e8404006456e0 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Thu, 13 Jul 2023 16:45:40 -0700 Subject: [PATCH 07/14] tests for blobs --- test/js/web/workers/structured-clone.test.ts | 35 +++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/test/js/web/workers/structured-clone.test.ts b/test/js/web/workers/structured-clone.test.ts index 8bebe1f99810..aa9722dfda31 100644 --- a/test/js/web/workers/structured-clone.test.ts +++ b/test/js/web/workers/structured-clone.test.ts @@ -101,6 +101,33 @@ describe("structured clone", () => { } }); + describe("bun blobs work", () => { + test("simple", () => { + const blob = new Blob(["hello"], { type: "application/octet-stream" }); + const cloned = structuredClone(blob); + expect(cloned).toBeInstanceOf(Blob); + expect(cloned).not.toBe(blob); + expect(cloned.size).toBe(blob.size); + expect(cloned.type).toBe(blob.type); + }); + test("empty", () => { + const emptyBlob = new Blob([], { type: "" }); + const clonedEmpty = structuredClone(emptyBlob); + expect(clonedEmpty).toBeInstanceOf(Blob); + expect(clonedEmpty).not.toBe(emptyBlob); + expect(clonedEmpty.size).toBe(emptyBlob.size); + expect(clonedEmpty.type).toBe(emptyBlob.type); + }); + test("unknown type", () => { + const blob = new Blob(["hello type"], { type: "this is type" }); + const cloned = structuredClone(blob); + expect(cloned).toBeInstanceOf(Blob); + expect(cloned).not.toBe(blob); + expect(cloned.size).toBe(blob.size); + expect(cloned.type).toBe(blob.type); + }); + }); + describe("transferrables", () => { test("ArrayBuffer", () => { const buffer = Uint8Array.from([1]).buffer; @@ -109,11 +136,17 @@ describe("structured clone", () => { expect(cloned.byteLength).toBe(1); }); test("A detached ArrayBuffer cannot be transferred", () => { - const buffer = new ArrayBuffer(); + const buffer = new ArrayBuffer(2); const cloned = structuredClone(buffer, { transfer: [buffer] }); expect(() => { structuredClone(buffer, { transfer: [buffer] }); }).toThrow(DOMException); }); + test("Transferring a non-transferable platform object fails", () => { + const blob = new Blob(); + expect(() => { + structuredClone(blob, { transfer: [blob] }); + }).toThrow(DOMException); + }); }); }); From 863b620f19003591e3306d2ce4315ecfdc7b2723 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Fri, 14 Jul 2023 17:10:59 -0700 Subject: [PATCH 08/14] serialize/deserialize file blobs --- .../webcore/SerializedScriptValue.cpp | 1 - .../bindings/webcore/SerializedScriptValue.h | 1 - src/bun.js/node/types.zig | 2 + src/bun.js/webcore/blob.zig | 232 ++++++++++++++---- 4 files changed, 190 insertions(+), 46 deletions(-) diff --git a/src/bun.js/bindings/webcore/SerializedScriptValue.cpp b/src/bun.js/bindings/webcore/SerializedScriptValue.cpp index 250930c55d62..e3a741a02e6a 100644 --- a/src/bun.js/bindings/webcore/SerializedScriptValue.cpp +++ b/src/bun.js/bindings/webcore/SerializedScriptValue.cpp @@ -2186,7 +2186,6 @@ class CloneSerializer : CloneBase { void SerializedScriptValue::writeBytesForBun(CloneSerializer* ctx, const uint8_t* data, uint32_t size) { - writeLittleEndian(ctx->m_buffer, size); ctx->write(data, size); } diff --git a/src/bun.js/bindings/webcore/SerializedScriptValue.h b/src/bun.js/bindings/webcore/SerializedScriptValue.h index 0eb76ceda8fe..6e2cbae86183 100644 --- a/src/bun.js/bindings/webcore/SerializedScriptValue.h +++ b/src/bun.js/bindings/webcore/SerializedScriptValue.h @@ -85,7 +85,6 @@ class SerializedScriptValue : public ThreadSafeRefCounted WTF_MAKE_FAST_ALLOCATED_WITH_HEAP_IDENTIFIER(SerializedScriptValue); public: - // write the length of the bytes in the first 32 bits, then follows with the bytes static void writeBytesForBun(CloneSerializer*, const uint8_t*, uint32_t); // WEBCORE_EXPORT static ExceptionOr> create(JSC::JSGlobalObject&, JSC::JSValue, Vector>&& transfer, Vector>&, SerializationForStorage = SerializationForStorage::No, SerializationContext = SerializationContext::Default); diff --git a/src/bun.js/node/types.zig b/src/bun.js/node/types.zig index 642039ba5270..64bd04e94a87 100644 --- a/src/bun.js/node/types.zig +++ b/src/bun.js/node/types.zig @@ -1024,6 +1024,8 @@ pub const PathOrFileDescriptor = union(Tag) { pub const Tag = enum { fd, path }; + pub const SerializeTag = enum(u8) { fd, path }; + /// This will unref() the path string if it is a PathLike. /// Does nothing for file descriptors, **does not** close file descriptors. pub fn deinit(this: PathOrFileDescriptor) void { diff --git a/src/bun.js/webcore/blob.zig b/src/bun.js/webcore/blob.zig index 213e472ee0cc..37cde92422b2 100644 --- a/src/bun.js/webcore/blob.zig +++ b/src/bun.js/webcore/blob.zig @@ -105,6 +105,9 @@ pub const Blob = struct { pub const JSTimeType = u52; pub const init_timestamp = std.math.maxInt(JSTimeType); + const serialization_version: u8 = 1; + const reserved_space_for_serialization: u32 = 512; + pub fn getFormDataEncoding(this: *Blob) ?*bun.FormData.AsyncFormData { var content_type_slice: ZigString.Slice = this.getContentType() orelse return null; defer content_type_slice.deinit(); @@ -208,6 +211,44 @@ pub const Blob = struct { return null; } + const StructuredCloneWriter = struct { + ctx: *anyopaque, + impl: *const fn (*anyopaque, ptr: [*]const u8, len: u32) callconv(.C) void, + + pub const WriteError = error{}; + pub fn write(this: StructuredCloneWriter, bytes: []const u8) WriteError!usize { + this.impl(this.ctx, bytes.ptr, @truncate(u32, bytes.len)); + return bytes.len; + } + }; + + fn _onStructuredCloneSerialize( + this: *Blob, + comptime Writer: type, + writer: Writer, + ) !void { + try writer.writeIntNative(u8, serialization_version); + + try writer.writeIntNative(u32, @truncate(u32, this.content_type.len)); + _ = try writer.write(this.content_type); + try writer.writeIntNative(u8, @intFromBool(this.content_type_was_set)); + + const store_tag: Store.SerializeTag = if (this.store) |store| + if (store.data == .file) .file else .bytes + else + .empty; + + try writer.writeIntNative(u8, @intFromEnum(store_tag)); + + this.resolveSize(); + if (this.store) |store| { + try store.serialize(Writer, writer); + } + + // reserved space for future use + _ = try writer.write(&[_]u8{0} ** reserved_space_for_serialization); + } + pub fn onStructuredCloneSerialize( this: *Blob, globalThis: *JSC.JSGlobalObject, @@ -215,9 +256,16 @@ pub const Blob = struct { writeBytes: *const fn (*anyopaque, ptr: [*]const u8, len: u32) callconv(.C) void, ) callconv(.C) void { _ = globalThis; - this.resolveSize(); - writeBytes(ctx, this.sharedView().ptr, @intCast(u32, this.sharedView().len)); - writeBytes(ctx, this.content_type.ptr, @intCast(u32, this.content_type.len)); + + const Writer = std.io.Writer(StructuredCloneWriter, StructuredCloneWriter.WriteError, StructuredCloneWriter.write); + var writer = Writer{ + .context = .{ + .ctx = ctx, + .impl = writeBytes, + }, + }; + + _onStructuredCloneSerialize(this, Writer, writer) catch return .zero; } pub fn onStructuredCloneTransfer( @@ -232,61 +280,126 @@ pub const Blob = struct { _ = globalThis; } - pub fn onStructuredCloneDeserialize( + fn _onStructuredCloneDeserialize( globalThis: *JSC.JSGlobalObject, - ptr: [*]u8, - end: [*]u8, - ) callconv(.C) JSValue { + comptime Reader: type, + reader: Reader, + ) !JSValue { const allocator = globalThis.allocator(); - const total_length: usize = @intFromPtr(end) - @intFromPtr(ptr); - var slice = ptr[0..total_length]; + const version = try reader.readIntNative(u8); + _ = version; - var shared_view_len: u32 = 0; - if (slice.len < 4) return .zero; - shared_view_len |= @as(u32, slice[0]); - shared_view_len |= @as(u32, slice[1]) << 8; - shared_view_len |= @as(u32, slice[2]) << 16; - shared_view_len |= @as(u32, slice[3]) << 24; + const content_type_len = try reader.readIntNative(u32); - slice = slice[4..]; + var content_type = try allocator.alloc(u8, content_type_len); + _ = try reader.read(content_type); - if (slice.len < shared_view_len) return .zero; - const shared_view = slice[0..shared_view_len]; - slice = slice[shared_view_len..]; + const content_type_was_set: bool = try reader.readIntNative(u8) != 0; - var content_type_len: u32 = 0; - if (slice.len < 4) return .zero; - content_type_len |= @as(u32, slice[0]); - content_type_len |= @as(u32, slice[1]) << 8; - content_type_len |= @as(u32, slice[2]) << 16; - content_type_len |= @as(u32, slice[3]) << 24; + const store_tag = try reader.readEnum(Store.SerializeTag, .Little); - slice = slice[4..]; + switch (store_tag) { + .bytes => { + const bytes_len = try reader.readIntNative(u32); + var bytes = try allocator.alloc(u8, bytes_len); + _ = try reader.read(bytes); - if (slice.len < content_type_len) return .zero; - const content_type = slice[0..content_type_len]; - slice = slice[content_type_len..]; + var blob = Blob.init(bytes, allocator, globalThis); + blob.content_type = content_type; + if (blob.content_type.len > 0) { + blob.content_type_allocated = true; + blob.content_type_was_set = content_type_was_set; + } + var blob_ = try allocator.create(Blob); + blob_.* = blob; - if (Environment.allow_assert) { - std.debug.assert(slice.len == 0); + return blob_.toJS(globalThis); + }, + .file => { + const pathlike_tag = try reader.readEnum(JSC.Node.PathOrFileDescriptor.SerializeTag, .Little); + + switch (pathlike_tag) { + .fd => { + const fd = @intCast(i32, try reader.readIntNative(u32)); + + var blob = try allocator.create(Blob); + blob.* = Blob.findOrCreateFileFromPath( + JSC.Node.PathOrFileDescriptor{ + .fd = fd, + }, + globalThis, + ); + + blob.allocator = allocator; + blob.content_type = content_type; + if (blob.content_type.len > 0) { + blob.content_type_allocated = true; + blob.content_type_was_set = content_type_was_set; + } + + return blob.toJS(globalThis); + }, + .path => { + const path_len = try reader.readIntNative(u32); + + const path = try default_allocator.alloc(u8, path_len); + _ = try reader.read(path); + + var blob = try allocator.create(Blob); + blob.* = Blob.findOrCreateFileFromPath( + JSC.Node.PathOrFileDescriptor{ + .path = .{ + .string = bun.PathString.init(path), + }, + }, + globalThis, + ); + blob.allocator = allocator; + blob.content_type = content_type; + if (blob.content_type.len > 0) { + blob.content_type_allocated = true; + blob.content_type_was_set = content_type_was_set; + } + + return blob.toJS(globalThis); + }, + } + + return .zero; + }, + .empty => { + var blob = try allocator.create(Blob); + blob.* = Blob.initEmpty(globalThis); + blob.content_type = content_type; + if (blob.content_type.len > 0) { + blob.content_type_allocated = true; + blob.content_type_was_set = content_type_was_set; + } + + return blob.toJS(globalThis); + }, } - var blob = Blob.create(shared_view, allocator, globalThis, false); - blob.content_type = brk: { - if (globalThis.bunVM().mimeType(content_type)) |mime| { - break :brk mime.value; - } + return .zero; + } - var content_type_buf = allocator.alloc(u8, content_type.len) catch return .zero; - blob.content_type_allocated = true; - break :brk strings.copyLowercase(content_type, content_type_buf); - }; - var blob_ = allocator.create(Blob) catch return .zero; - blob_.* = blob; - blob_.*.allocator = allocator; + pub fn onStructuredCloneDeserialize( + globalThis: *JSC.JSGlobalObject, + ptr: [*]u8, + end: [*]u8, + ) callconv(.C) JSValue { + const total_length: usize = @intFromPtr(end) - @intFromPtr(ptr); + var buffer_stream = std.io.fixedBufferStream(ptr[0..total_length]); + var reader = buffer_stream.reader(); - return blob_.toJS(globalThis); + const blob = _onStructuredCloneDeserialize(globalThis, @TypeOf(reader), reader) catch return .zero; + + if (Environment.allow_assert) { + std.debug.assert(total_length - reader.context.pos == reserved_space_for_serialization); + } + + return blob; } const URLSearchParamsConverter = struct { @@ -1176,6 +1289,37 @@ pub const Blob = struct { allocator.destroy(this); } + const SerializeTag = enum(u8) { + file = 0, + bytes = 1, + empty = 2, + }; + + pub fn serialize(this: *Store, comptime Writer: type, writer: Writer) !void { + switch (this.data) { + .file => |file| { + const pathlike_tag: JSC.Node.PathOrFileDescriptor.SerializeTag = if (file.pathlike == .fd) .fd else .path; + try writer.writeIntNative(u8, @intFromEnum(pathlike_tag)); + + switch (file.pathlike) { + .fd => |fd| { + try writer.writeIntNative(u32, @intCast(u32, fd)); + }, + .path => |path| { + const path_slice = path.slice(); + try writer.writeIntNative(u32, @truncate(u32, path_slice.len)); + _ = try writer.write(path_slice); + }, + } + }, + .bytes => |bytes| { + const slice = bytes.slice(); + try writer.writeIntNative(u32, @truncate(u32, slice.len)); + _ = try writer.write(slice); + }, + } + } + pub fn fromArrayList(list: std.ArrayListUnmanaged(u8), allocator: std.mem.Allocator) !*Blob.Store { return try Blob.Store.init(list.items, allocator); } From f44b7884b7b3e84a225ac69aed4c9f8ce742d7b9 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Fri, 14 Jul 2023 17:15:53 -0700 Subject: [PATCH 09/14] more tests --- test/js/web/workers/example.txt | 1 + test/js/web/workers/structured-clone.test.ts | 98 +++++++++++++++++--- 2 files changed, 84 insertions(+), 15 deletions(-) create mode 100644 test/js/web/workers/example.txt diff --git a/test/js/web/workers/example.txt b/test/js/web/workers/example.txt new file mode 100644 index 000000000000..96236f8158b1 --- /dev/null +++ b/test/js/web/workers/example.txt @@ -0,0 +1 @@ +example \ No newline at end of file diff --git a/test/js/web/workers/structured-clone.test.ts b/test/js/web/workers/structured-clone.test.ts index aa9722dfda31..75d57e9ab076 100644 --- a/test/js/web/workers/structured-clone.test.ts +++ b/test/js/web/workers/structured-clone.test.ts @@ -1,3 +1,6 @@ +import { join } from "path"; +import { openSync } from "fs"; + describe("structured clone", () => { let primitives_tests = [ { description: "primitive undefined", value: undefined }, @@ -102,29 +105,53 @@ describe("structured clone", () => { }); describe("bun blobs work", () => { - test("simple", () => { + test("simple", async () => { const blob = new Blob(["hello"], { type: "application/octet-stream" }); const cloned = structuredClone(blob); - expect(cloned).toBeInstanceOf(Blob); - expect(cloned).not.toBe(blob); - expect(cloned.size).toBe(blob.size); - expect(cloned.type).toBe(blob.type); + await compareBlobs(blob, cloned); }); - test("empty", () => { + test("empty", async () => { const emptyBlob = new Blob([], { type: "" }); const clonedEmpty = structuredClone(emptyBlob); - expect(clonedEmpty).toBeInstanceOf(Blob); - expect(clonedEmpty).not.toBe(emptyBlob); - expect(clonedEmpty.size).toBe(emptyBlob.size); - expect(clonedEmpty.type).toBe(emptyBlob.type); + await compareBlobs(emptyBlob, clonedEmpty); + }); + test("empty with type", async () => { + const emptyBlob = new Blob([], { type: "application/octet-stream" }); + const clonedEmpty = structuredClone(emptyBlob); + await compareBlobs(emptyBlob, clonedEmpty); }); - test("unknown type", () => { + test("unknown type", async () => { const blob = new Blob(["hello type"], { type: "this is type" }); const cloned = structuredClone(blob); - expect(cloned).toBeInstanceOf(Blob); - expect(cloned).not.toBe(blob); - expect(cloned.size).toBe(blob.size); - expect(cloned.type).toBe(blob.type); + await compareBlobs(blob, cloned); + }); + test("file from path", async () => { + const blob = Bun.file(join(import.meta.dir, "example.txt")); + const cloned = structuredClone(blob); + expect(cloned.lastModified).toBe(blob.lastModified); + expect(cloned.name).toBe(blob.name); + }); + test("file from fd", async () => { + const fd = openSync(join(import.meta.dir, "example.txt"), "r"); + const blob = Bun.file(fd); + const cloned = structuredClone(blob); + expect(cloned.lastModified).toBe(blob.lastModified); + expect(cloned.name).toBe(blob.name); + }); + test("unpaired high surrogate (invalid utf-8)", async () => { + const blob = createBlob(encode_cesu8([0xd800])); + const cloned = structuredClone(blob); + await compareBlobs(blob, cloned); + }); + test("unpaired low surrogate (invalid utf-8)", async () => { + const blob = createBlob(encode_cesu8([0xdc00])); + const cloned = structuredClone(blob); + await compareBlobs(blob, cloned); + }); + test("paired surrogates (invalid utf-8)", async () => { + const blob = createBlob(encode_cesu8([0xd800, 0xdc00])); + const cloned = structuredClone(blob); + await compareBlobs(blob, cloned); }); }); @@ -150,3 +177,44 @@ describe("structured clone", () => { }); }); }); + +async function compareBlobs(original: Blob, cloned: Blob) { + expect(cloned).toBeInstanceOf(Blob); + expect(cloned).not.toBe(original); + expect(cloned.size).toBe(original.size); + expect(cloned.type).toBe(original.type); + const ab1 = await new Response(cloned).arrayBuffer(); + const ab2 = await new Response(original).arrayBuffer(); + expect(ab1.byteLength).toBe(ab2.byteLength); + const ta1 = new Uint8Array(ab1); + const ta2 = new Uint8Array(ab2); + for (let i = 0; i < ta1.length; i++) { + expect(ta1[i]).toBe(ta2[i]); + } +} + +function encode_cesu8(codeunits: number[]): number[] { + // http://www.unicode.org/reports/tr26/ section 2.2 + // only the 3-byte form is supported + const rv: number[] = []; + codeunits.forEach(function (codeunit) { + rv.push(b("11100000") + ((codeunit & b("1111000000000000")) >> 12)); + rv.push(b("10000000") + ((codeunit & b("0000111111000000")) >> 6)); + rv.push(b("10000000") + (codeunit & b("0000000000111111"))); + }); + return rv; +} + +function b(s: string): number { + return parseInt(s, 2); +} + +function createBlob(arr: number[]): Blob { + const buffer = new ArrayBuffer(arr.length); + const view = new DataView(buffer); + for (let i = 0; i < arr.length; i++) { + view.setUint8(i, arr[i]); + } + + return new Blob([view]); +} From b5690068788a8cc30196d4463b970a00f08eba6b Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Fri, 14 Jul 2023 17:43:33 -0700 Subject: [PATCH 10/14] small cleanup --- src/bun.js/webcore/blob.zig | 48 ++++++++++++------------------------- 1 file changed, 15 insertions(+), 33 deletions(-) diff --git a/src/bun.js/webcore/blob.zig b/src/bun.js/webcore/blob.zig index 37cde92422b2..5ccbaac50fd5 100644 --- a/src/bun.js/webcore/blob.zig +++ b/src/bun.js/webcore/blob.zig @@ -299,24 +299,19 @@ pub const Blob = struct { const store_tag = try reader.readEnum(Store.SerializeTag, .Little); - switch (store_tag) { - .bytes => { + const blob: *Blob = switch (store_tag) { + .bytes => brk: { const bytes_len = try reader.readIntNative(u32); var bytes = try allocator.alloc(u8, bytes_len); _ = try reader.read(bytes); var blob = Blob.init(bytes, allocator, globalThis); - blob.content_type = content_type; - if (blob.content_type.len > 0) { - blob.content_type_allocated = true; - blob.content_type_was_set = content_type_was_set; - } var blob_ = try allocator.create(Blob); blob_.* = blob; - return blob_.toJS(globalThis); + break :brk blob_; }, - .file => { + .file => brk: { const pathlike_tag = try reader.readEnum(JSC.Node.PathOrFileDescriptor.SerializeTag, .Little); switch (pathlike_tag) { @@ -331,14 +326,7 @@ pub const Blob = struct { globalThis, ); - blob.allocator = allocator; - blob.content_type = content_type; - if (blob.content_type.len > 0) { - blob.content_type_allocated = true; - blob.content_type_was_set = content_type_was_set; - } - - return blob.toJS(globalThis); + break :brk blob; }, .path => { const path_len = try reader.readIntNative(u32); @@ -355,33 +343,27 @@ pub const Blob = struct { }, globalThis, ); - blob.allocator = allocator; - blob.content_type = content_type; - if (blob.content_type.len > 0) { - blob.content_type_allocated = true; - blob.content_type_was_set = content_type_was_set; - } - return blob.toJS(globalThis); + break :brk blob; }, } return .zero; }, - .empty => { + .empty => brk: { var blob = try allocator.create(Blob); blob.* = Blob.initEmpty(globalThis); - blob.content_type = content_type; - if (blob.content_type.len > 0) { - blob.content_type_allocated = true; - blob.content_type_was_set = content_type_was_set; - } - - return blob.toJS(globalThis); + break :brk blob; }, + }; + blob.allocator = allocator; + if (content_type.len > 0) { + blob.content_type = content_type; + blob.content_type_allocated = true; + blob.content_type_was_set = content_type_was_set; } - return .zero; + return blob.toJS(globalThis); } pub fn onStructuredCloneDeserialize( From d5175f441bc4c8286bb8eed812bdadf1a45c614a Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Fri, 14 Jul 2023 17:49:16 -0700 Subject: [PATCH 11/14] format --- src/js/thirdparty/ws.js | 2 +- test/cli/run/run-process-env.test.ts | 5 ++--- test/js/bun/websocket/websocket-server.test.ts | 3 ++- test/js/first_party/ws/ws.test.ts | 12 ++++++------ 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/js/thirdparty/ws.js b/src/js/thirdparty/ws.js index e88ae67692cb..722d37347229 100644 --- a/src/js/thirdparty/ws.js +++ b/src/js/thirdparty/ws.js @@ -79,7 +79,7 @@ class BunWebSocket extends EventEmitter { } }); } else if (event === "error") { - this.#ws.addEventListener("error", (err) => { + this.#ws.addEventListener("error", err => { this.emit("error", err); }); } else if (event === "ping") { diff --git a/test/cli/run/run-process-env.test.ts b/test/cli/run/run-process-env.test.ts index 1d7cb2c5671f..8303fe19cc24 100644 --- a/test/cli/run/run-process-env.test.ts +++ b/test/cli/run/run-process-env.test.ts @@ -15,16 +15,15 @@ describe("process.env", () => { }); // https://github.com/oven-sh/bun/issues/3589 - test('npm_lifecycle_event should have the value of the last call', () => { + test("npm_lifecycle_event should have the value of the last call", () => { const dir = tempDirWithFiles("processenv_ls_call", { "package.json": `{"scripts": { "first": "${bunExe()} run --cwd lsc second" } }`, "lsc": { "package.json": `{"scripts": { "second": "${bunExe()} run index.ts" } }`, "index.ts": "console.log(process.env.npm_lifecycle_event);", - } + }, }); - const { stdout } = bunRunAsScript(dir, "first"); expect(stdout).toBe("second"); }); diff --git a/test/js/bun/websocket/websocket-server.test.ts b/test/js/bun/websocket/websocket-server.test.ts index eb9b773a399b..7a31fa396766 100644 --- a/test/js/bun/websocket/websocket-server.test.ts +++ b/test/js/bun/websocket/websocket-server.test.ts @@ -107,7 +107,8 @@ describe("Server", () => { const data = new Uint8Array(1 * 1024 * 1024); // send data until backpressure is triggered for (let i = 0; i < 10; i++) { - if (ws.send(data) < 1) { // backpressure or dropped + if (ws.send(data) < 1) { + // backpressure or dropped break; } } diff --git a/test/js/first_party/ws/ws.test.ts b/test/js/first_party/ws/ws.test.ts index c0b56a200195..fc8e63f5f652 100644 --- a/test/js/first_party/ws/ws.test.ts +++ b/test/js/first_party/ws/ws.test.ts @@ -110,11 +110,11 @@ describe("WebSocket", () => { expect(isBinary).toBeTrue(); ws.ping(); }); - ws.on("ping", (data) => { + ws.on("ping", data => { expect(data).toBeInstanceOf(type); ws.pong(); }); - ws.on("pong", (data) => { + ws.on("pong", data => { expect(data).toBeInstanceOf(type); done(); }); @@ -145,7 +145,7 @@ describe("WebSocket", () => { ws.on("open", () => { ws.ping(); }); - ws.on("ping", (data) => { + ws.on("ping", data => { expect(data).toBeInstanceOf(Buffer); done(); }); @@ -155,7 +155,7 @@ describe("WebSocket", () => { ws.on("open", () => { ws.ping(message); }); - ws.on("ping", (data) => { + ws.on("ping", data => { expect(data).toEqual(Buffer.from(bytes)); done(); }); @@ -167,7 +167,7 @@ describe("WebSocket", () => { ws.on("open", () => { ws.pong(); }); - ws.on("pong", (data) => { + ws.on("pong", data => { expect(data).toBeInstanceOf(Buffer); done(); }); @@ -177,7 +177,7 @@ describe("WebSocket", () => { ws.on("open", () => { ws.pong(message); }); - ws.on("pong", (data) => { + ws.on("pong", data => { expect(data).toEqual(Buffer.from(bytes)); done(); }); From 8a97bd2efe45c2d08f1313c7101d931c76880435 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Fri, 14 Jul 2023 18:02:50 -0700 Subject: [PATCH 12/14] small changes + serialize offset --- src/bun.js/bindings/ZigGlobalObject.cpp | 7 +++---- src/bun.js/webcore/blob.zig | 5 +++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index 4c5569ea6644..e6663bfed75a 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -664,7 +664,7 @@ extern "C" bool Zig__GlobalObject__resetModuleRegistryMap(JSC__JSGlobalObject* g String GlobalObject::defaultAgentClusterID() { - return makeString(ProcessIdent::identifier().toUInt64(), "-default"); + return makeString(ProcessIdent::identifier().toUInt64(), "-default"_s); } String GlobalObject::agentClusterID() const @@ -1247,8 +1247,7 @@ JSC_DEFINE_HOST_FUNCTION(functionStructuredClone, JSC::JSValue transferListValue = optionsObject->get(globalObject, vm.propertyNames->transfer); if (transferListValue.isObject()) { JSC::JSObject* transferListObject = transferListValue.getObject(); - if (transferListObject->inherits()) { - JSC::JSArray* transferListArray = JSC::jsCast(transferListObject); + if (auto* transferListArray = jsDynamicCast(transferListObject)) { for (unsigned i = 0; i < transferListArray->length(); i++) { JSC::JSValue transferListValue = transferListArray->get(globalObject, i); if (transferListValue.isObject()) { @@ -1266,7 +1265,7 @@ JSC_DEFINE_HOST_FUNCTION(functionStructuredClone, return JSValue::encode(jsUndefined()); } - JSValue deserialized = serialized.returnValue()->deserialize(*globalObject, globalObject); + JSValue deserialized = serialized.releaseReturnValue()->deserialize(*globalObject, globalObject); return JSValue::encode(deserialized); } diff --git a/src/bun.js/webcore/blob.zig b/src/bun.js/webcore/blob.zig index 5ccbaac50fd5..8eb0a026e311 100644 --- a/src/bun.js/webcore/blob.zig +++ b/src/bun.js/webcore/blob.zig @@ -229,6 +229,8 @@ pub const Blob = struct { ) !void { try writer.writeIntNative(u8, serialization_version); + try writer.writeIntNative(u64, @intCast(u64, this.offset)); + try writer.writeIntNative(u32, @truncate(u32, this.content_type.len)); _ = try writer.write(this.content_type); try writer.writeIntNative(u8, @intFromBool(this.content_type_was_set)); @@ -290,6 +292,8 @@ pub const Blob = struct { const version = try reader.readIntNative(u8); _ = version; + const offset = try reader.readIntNative(u64); + const content_type_len = try reader.readIntNative(u32); var content_type = try allocator.alloc(u8, content_type_len); @@ -357,6 +361,7 @@ pub const Blob = struct { }, }; blob.allocator = allocator; + blob.offset = @intCast(u52, offset); if (content_type.len > 0) { blob.content_type = content_type; blob.content_type_allocated = true; From c14e04b9a02b65d8b161695579c543175a16ce3d Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Fri, 14 Jul 2023 18:16:55 -0700 Subject: [PATCH 13/14] slice helper --- src/bun.js/webcore/blob.zig | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/bun.js/webcore/blob.zig b/src/bun.js/webcore/blob.zig index 8eb0a026e311..0d6dcbc26317 100644 --- a/src/bun.js/webcore/blob.zig +++ b/src/bun.js/webcore/blob.zig @@ -106,7 +106,7 @@ pub const Blob = struct { pub const init_timestamp = std.math.maxInt(JSTimeType); const serialization_version: u8 = 1; - const reserved_space_for_serialization: u32 = 512; + const reserved_space_for_serialization: u32 = 128; pub fn getFormDataEncoding(this: *Blob) ?*bun.FormData.AsyncFormData { var content_type_slice: ZigString.Slice = this.getContentType() orelse return null; @@ -282,6 +282,17 @@ pub const Blob = struct { _ = globalThis; } + fn readSlice( + reader: anytype, + len: usize, + allocator: std.mem.Allocator, + ) ![]u8 { + var slice = try allocator.alloc(u8, len); + slice = slice[0..try reader.read(slice)]; + if (slice.len != len) return error.TooSmall; + return slice; + } + fn _onStructuredCloneDeserialize( globalThis: *JSC.JSGlobalObject, comptime Reader: type, @@ -296,8 +307,7 @@ pub const Blob = struct { const content_type_len = try reader.readIntNative(u32); - var content_type = try allocator.alloc(u8, content_type_len); - _ = try reader.read(content_type); + const content_type = try readSlice(reader, content_type_len, allocator); const content_type_was_set: bool = try reader.readIntNative(u8) != 0; @@ -306,8 +316,7 @@ pub const Blob = struct { const blob: *Blob = switch (store_tag) { .bytes => brk: { const bytes_len = try reader.readIntNative(u32); - var bytes = try allocator.alloc(u8, bytes_len); - _ = try reader.read(bytes); + const bytes = try readSlice(reader, bytes_len, allocator); var blob = Blob.init(bytes, allocator, globalThis); var blob_ = try allocator.create(Blob); @@ -335,8 +344,7 @@ pub const Blob = struct { .path => { const path_len = try reader.readIntNative(u32); - const path = try default_allocator.alloc(u8, path_len); - _ = try reader.read(path); + const path = try readSlice(reader, path_len, default_allocator); var blob = try allocator.create(Blob); blob.* = Blob.findOrCreateFileFromPath( From 90f41df208d9372e163deede1da9418a1d439975 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Fri, 14 Jul 2023 18:32:07 -0700 Subject: [PATCH 14/14] map and set test --- test/js/web/workers/structured-clone.test.ts | 28 ++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test/js/web/workers/structured-clone.test.ts b/test/js/web/workers/structured-clone.test.ts index 75d57e9ab076..61a0c1320605 100644 --- a/test/js/web/workers/structured-clone.test.ts +++ b/test/js/web/workers/structured-clone.test.ts @@ -104,6 +104,34 @@ describe("structured clone", () => { } }); + test("map", () => { + var input = new Map(); + input.set("a", 1); + input.set("b", 2); + input.set("c", 3); + var cloned = structuredClone(input); + expect(cloned).toBeInstanceOf(Map); + expect(cloned).not.toBe(input); + expect(cloned.size).toEqual(input.size); + for (const [key, value] of input) { + expect(cloned.get(key)).toBe(value); + } + }); + + test("set", () => { + var input = new Set(); + input.add("a"); + input.add("b"); + input.add("c"); + var cloned = structuredClone(input); + expect(cloned).toBeInstanceOf(Set); + expect(cloned).not.toBe(input); + expect(cloned.size).toEqual(input.size); + for (const value of input) { + expect(cloned.has(value)).toBe(true); + } + }); + describe("bun blobs work", () => { test("simple", async () => { const blob = new Blob(["hello"], { type: "application/octet-stream" });