| /* |
| * Copyright (C) 2010 Google 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: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * 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. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT |
| * OWNER 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 "bindings/core/v8/SerializedScriptValue.h" |
| |
| #include "bindings/core/v8/ExceptionState.h" |
| #include "bindings/core/v8/V8ArrayBuffer.h" |
| #include "bindings/core/v8/V8ArrayBufferView.h" |
| #include "bindings/core/v8/V8Binding.h" |
| #include "bindings/core/v8/V8Blob.h" |
| #include "bindings/core/v8/V8DataView.h" |
| #include "bindings/core/v8/V8File.h" |
| #include "bindings/core/v8/V8FileList.h" |
| #include "bindings/core/v8/V8Float32Array.h" |
| #include "bindings/core/v8/V8Float64Array.h" |
| #include "bindings/core/v8/V8ImageData.h" |
| #include "bindings/core/v8/V8Int16Array.h" |
| #include "bindings/core/v8/V8Int32Array.h" |
| #include "bindings/core/v8/V8Int8Array.h" |
| #include "bindings/core/v8/V8MessagePort.h" |
| #include "bindings/core/v8/V8Uint16Array.h" |
| #include "bindings/core/v8/V8Uint32Array.h" |
| #include "bindings/core/v8/V8Uint8Array.h" |
| #include "bindings/core/v8/V8Uint8ClampedArray.h" |
| #include "bindings/core/v8/WorkerScriptController.h" |
| #include "bindings/modules/v8/V8CryptoKey.h" |
| #include "bindings/modules/v8/V8DOMFileSystem.h" |
| #include "core/dom/ExceptionCode.h" |
| #include "core/dom/MessagePort.h" |
| #include "core/fileapi/Blob.h" |
| #include "core/fileapi/File.h" |
| #include "core/fileapi/FileList.h" |
| #include "core/html/ImageData.h" |
| #include "core/html/canvas/DataView.h" |
| #include "platform/SharedBuffer.h" |
| #include "platform/heap/Handle.h" |
| #include "public/platform/Platform.h" |
| #include "public/platform/WebBlobInfo.h" |
| #include "public/platform/WebCrypto.h" |
| #include "public/platform/WebCryptoKey.h" |
| #include "public/platform/WebCryptoKeyAlgorithm.h" |
| #include "wtf/ArrayBuffer.h" |
| #include "wtf/ArrayBufferContents.h" |
| #include "wtf/ArrayBufferView.h" |
| #include "wtf/Assertions.h" |
| #include "wtf/ByteOrder.h" |
| #include "wtf/Float32Array.h" |
| #include "wtf/Float64Array.h" |
| #include "wtf/Int16Array.h" |
| #include "wtf/Int32Array.h" |
| #include "wtf/Int8Array.h" |
| #include "wtf/RefCounted.h" |
| #include "wtf/Uint16Array.h" |
| #include "wtf/Uint32Array.h" |
| #include "wtf/Uint8Array.h" |
| #include "wtf/Uint8ClampedArray.h" |
| #include "wtf/Vector.h" |
| #include "wtf/text/StringBuffer.h" |
| #include "wtf/text/StringUTF8Adaptor.h" |
| |
| // FIXME: consider crashing in debug mode on deserialization errors |
| // NOTE: be sure to change wireFormatVersion as necessary! |
| |
| namespace blink { |
| |
| namespace { |
| |
| // This code implements the HTML5 Structured Clone algorithm: |
| // http://www.whatwg.org/specs/web-apps/current-work/multipage/urls.html#safe-passing-of-structured-data |
| |
| // V8ObjectMap is a map from V8 objects to arbitrary values of type T. |
| // V8 objects (or handles to V8 objects) cannot be used as keys in ordinary wtf::HashMaps; |
| // this class should be used instead. GCObject must be a subtype of v8::Object. |
| // Suggested usage: |
| // V8ObjectMap<v8::Object, int> map; |
| // v8::Handle<v8::Object> obj = ...; |
| // map.set(obj, 42); |
| template<typename GCObject, typename T> |
| class V8ObjectMap { |
| public: |
| bool contains(const v8::Handle<GCObject>& handle) |
| { |
| return m_map.contains(*handle); |
| } |
| |
| bool tryGet(const v8::Handle<GCObject>& handle, T* valueOut) |
| { |
| typename HandleToT::iterator result = m_map.find(*handle); |
| if (result != m_map.end()) { |
| *valueOut = result->value; |
| return true; |
| } |
| return false; |
| } |
| |
| void set(const v8::Handle<GCObject>& handle, const T& value) |
| { |
| m_map.set(*handle, value); |
| } |
| |
| private: |
| // This implementation uses GetIdentityHash(), which sets a hidden property on the object containing |
| // a random integer (or returns the one that had been previously set). This ensures that the table |
| // never needs to be rebuilt across garbage collections at the expense of doing additional allocation |
| // and making more round trips into V8. Note that since GetIdentityHash() is defined only on |
| // v8::Objects, this V8ObjectMap cannot be used to map v8::Strings to T (because the public V8 API |
| // considers a v8::String to be a v8::Primitive). |
| |
| // If V8 exposes a way to get at the address of the object held by a handle, then we can produce |
| // an alternate implementation that does not need to do any V8-side allocation; however, it will |
| // need to rehash after every garbage collection because a key object may have been moved. |
| template<typename G> |
| struct V8HandlePtrHash { |
| static v8::Handle<G> unsafeHandleFromRawValue(const G* value) |
| { |
| const v8::Handle<G>* handle = reinterpret_cast<const v8::Handle<G>*>(&value); |
| return *handle; |
| } |
| |
| static unsigned hash(const G* key) |
| { |
| return static_cast<unsigned>(unsafeHandleFromRawValue(key)->GetIdentityHash()); |
| } |
| static bool equal(const G* a, const G* b) |
| { |
| return unsafeHandleFromRawValue(a) == unsafeHandleFromRawValue(b); |
| } |
| // For HashArg. |
| static const bool safeToCompareToEmptyOrDeleted = false; |
| }; |
| |
| typedef WTF::HashMap<GCObject*, T, V8HandlePtrHash<GCObject> > HandleToT; |
| HandleToT m_map; |
| }; |
| |
| typedef UChar BufferValueType; |
| |
| // Serialization format is a sequence of tags followed by zero or more data arguments. |
| // Tags always take exactly one byte. A serialized stream first begins with |
| // a complete VersionTag. If the stream does not begin with a VersionTag, we assume that |
| // the stream is in format 0. |
| |
| // This format is private to the implementation of SerializedScriptValue. Do not rely on it |
| // externally. It is safe to persist a SerializedScriptValue as a binary blob, but this |
| // code should always be used to interpret it. |
| |
| // WebCoreStrings are read as (length:uint32_t, string:UTF8[length]). |
| // RawStrings are read as (length:uint32_t, string:UTF8[length]). |
| // RawUCharStrings are read as (length:uint32_t, string:UChar[length/sizeof(UChar)]). |
| // RawFiles are read as (path:WebCoreString, url:WebCoreStrng, type:WebCoreString). |
| // There is a reference table that maps object references (uint32_t) to v8::Values. |
| // Tokens marked with (ref) are inserted into the reference table and given the next object reference ID after decoding. |
| // All tags except InvalidTag, PaddingTag, ReferenceCountTag, VersionTag, GenerateFreshObjectTag |
| // and GenerateFreshArrayTag push their results to the deserialization stack. |
| // There is also an 'open' stack that is used to resolve circular references. Objects or arrays may |
| // contain self-references. Before we begin to deserialize the contents of these values, they |
| // are first given object reference IDs (by GenerateFreshObjectTag/GenerateFreshArrayTag); |
| // these reference IDs are then used with ObjectReferenceTag to tie the recursive knot. |
| enum SerializationTag { |
| InvalidTag = '!', // Causes deserialization to fail. |
| PaddingTag = '\0', // Is ignored (but consumed). |
| UndefinedTag = '_', // -> <undefined> |
| NullTag = '0', // -> <null> |
| TrueTag = 'T', // -> <true> |
| FalseTag = 'F', // -> <false> |
| StringTag = 'S', // string:RawString -> string |
| StringUCharTag = 'c', // string:RawUCharString -> string |
| Int32Tag = 'I', // value:ZigZag-encoded int32 -> Integer |
| Uint32Tag = 'U', // value:uint32_t -> Integer |
| DateTag = 'D', // value:double -> Date (ref) |
| MessagePortTag = 'M', // index:int -> MessagePort. Fills the result with transferred MessagePort. |
| NumberTag = 'N', // value:double -> Number |
| BlobTag = 'b', // uuid:WebCoreString, type:WebCoreString, size:uint64_t -> Blob (ref) |
| BlobIndexTag = 'i', // index:int32_t -> Blob (ref) |
| FileTag = 'f', // file:RawFile -> File (ref) |
| FileIndexTag = 'e', // index:int32_t -> File (ref) |
| DOMFileSystemTag = 'd', // type:int32_t, name:WebCoreString, uuid:WebCoreString -> FileSystem (ref) |
| FileListTag = 'l', // length:uint32_t, files:RawFile[length] -> FileList (ref) |
| FileListIndexTag = 'L', // length:uint32_t, files:int32_t[length] -> FileList (ref) |
| ImageDataTag = '#', // width:uint32_t, height:uint32_t, pixelDataLength:uint32_t, data:byte[pixelDataLength] -> ImageData (ref) |
| ObjectTag = '{', // numProperties:uint32_t -> pops the last object from the open stack; |
| // fills it with the last numProperties name,value pairs pushed onto the deserialization stack |
| SparseArrayTag = '@', // numProperties:uint32_t, length:uint32_t -> pops the last object from the open stack; |
| // fills it with the last numProperties name,value pairs pushed onto the deserialization stack |
| DenseArrayTag = '$', // numProperties:uint32_t, length:uint32_t -> pops the last object from the open stack; |
| // fills it with the last length elements and numProperties name,value pairs pushed onto deserialization stack |
| RegExpTag = 'R', // pattern:RawString, flags:uint32_t -> RegExp (ref) |
| ArrayBufferTag = 'B', // byteLength:uint32_t, data:byte[byteLength] -> ArrayBuffer (ref) |
| ArrayBufferTransferTag = 't', // index:uint32_t -> ArrayBuffer. For ArrayBuffer transfer |
| ArrayBufferViewTag = 'V', // subtag:byte, byteOffset:uint32_t, byteLength:uint32_t -> ArrayBufferView (ref). Consumes an ArrayBuffer from the top of the deserialization stack. |
| CryptoKeyTag = 'K', // subtag:byte, props, usages:uint32_t, keyDataLength:uint32_t, keyData:byte[keyDataLength] |
| // If subtag=AesKeyTag: |
| // props = keyLengthBytes:uint32_t, algorithmId:uint32_t |
| // If subtag=HmacKeyTag: |
| // props = keyLengthBytes:uint32_t, hashId:uint32_t |
| // If subtag=RsaHashedKeyTag: |
| // props = algorithmId:uint32_t, type:uint32_t, modulusLengthBits:uint32_t, publicExponentLength:uint32_t, publicExponent:byte[publicExponentLength], hashId:uint32_t |
| ObjectReferenceTag = '^', // ref:uint32_t -> reference table[ref] |
| GenerateFreshObjectTag = 'o', // -> empty object allocated an object ID and pushed onto the open stack (ref) |
| GenerateFreshSparseArrayTag = 'a', // length:uint32_t -> empty array[length] allocated an object ID and pushed onto the open stack (ref) |
| GenerateFreshDenseArrayTag = 'A', // length:uint32_t -> empty array[length] allocated an object ID and pushed onto the open stack (ref) |
| ReferenceCountTag = '?', // refTableSize:uint32_t -> If the reference table is not refTableSize big, fails. |
| StringObjectTag = 's', // string:RawString -> new String(string) (ref) |
| NumberObjectTag = 'n', // value:double -> new Number(value) (ref) |
| TrueObjectTag = 'y', // new Boolean(true) (ref) |
| FalseObjectTag = 'x', // new Boolean(false) (ref) |
| VersionTag = 0xFF // version:uint32_t -> Uses this as the file version. |
| }; |
| |
| enum ArrayBufferViewSubTag { |
| ByteArrayTag = 'b', |
| UnsignedByteArrayTag = 'B', |
| UnsignedByteClampedArrayTag = 'C', |
| ShortArrayTag = 'w', |
| UnsignedShortArrayTag = 'W', |
| IntArrayTag = 'd', |
| UnsignedIntArrayTag = 'D', |
| FloatArrayTag = 'f', |
| DoubleArrayTag = 'F', |
| DataViewTag = '?' |
| }; |
| |
| enum CryptoKeySubTag { |
| AesKeyTag = 1, |
| HmacKeyTag = 2, |
| // ID 3 was used by RsaKeyTag, while still behind experimental flag. |
| RsaHashedKeyTag = 4, |
| // Maximum allowed value is 255 |
| }; |
| |
| enum AssymetricCryptoKeyType { |
| PublicKeyType = 1, |
| PrivateKeyType = 2, |
| // Maximum allowed value is 2^32-1 |
| }; |
| |
| enum CryptoKeyAlgorithmTag { |
| AesCbcTag = 1, |
| HmacTag = 2, |
| RsaSsaPkcs1v1_5Tag = 3, |
| // ID 4 was used by RsaEs, while still behind experimental flag. |
| Sha1Tag = 5, |
| Sha256Tag = 6, |
| Sha384Tag = 7, |
| Sha512Tag = 8, |
| AesGcmTag = 9, |
| RsaOaepTag = 10, |
| AesCtrTag = 11, |
| AesKwTag = 12, |
| RsaPssTag = 13, |
| // Maximum allowed value is 2^32-1 |
| }; |
| |
| enum CryptoKeyUsage { |
| // Extractability is not a "usage" in the WebCryptoKeyUsages sense, however |
| // it fits conveniently into this bitfield. |
| ExtractableUsage = 1 << 0, |
| |
| EncryptUsage = 1 << 1, |
| DecryptUsage = 1 << 2, |
| SignUsage = 1 << 3, |
| VerifyUsage = 1 << 4, |
| DeriveKeyUsage = 1 << 5, |
| WrapKeyUsage = 1 << 6, |
| UnwrapKeyUsage = 1 << 7, |
| DeriveBitsUsage = 1 << 8, |
| // Maximum allowed value is 1 << 31 |
| }; |
| |
| static bool shouldCheckForCycles(int depth) |
| { |
| ASSERT(depth >= 0); |
| // Since we are not required to spot the cycle as soon as it |
| // happens we can check for cycles only when the current depth |
| // is a power of two. |
| return !(depth & (depth - 1)); |
| } |
| |
| static const int maxDepth = 20000; |
| |
| // VarInt encoding constants. |
| static const int varIntShift = 7; |
| static const int varIntMask = (1 << varIntShift) - 1; |
| |
| // ZigZag encoding helps VarInt encoding stay small for negative |
| // numbers with small absolute values. |
| class ZigZag { |
| public: |
| static uint32_t encode(uint32_t value) |
| { |
| if (value & (1U << 31)) |
| value = ((~value) << 1) + 1; |
| else |
| value <<= 1; |
| return value; |
| } |
| |
| static uint32_t decode(uint32_t value) |
| { |
| if (value & 1) |
| value = ~(value >> 1); |
| else |
| value >>= 1; |
| return value; |
| } |
| |
| private: |
| ZigZag(); |
| }; |
| |
| // Writer is responsible for serializing primitive types and storing |
| // information used to reconstruct composite types. |
| class Writer { |
| WTF_MAKE_NONCOPYABLE(Writer); |
| public: |
| Writer() |
| : m_position(0) |
| { |
| } |
| |
| // Write functions for primitive types. |
| |
| void writeUndefined() { append(UndefinedTag); } |
| |
| void writeNull() { append(NullTag); } |
| |
| void writeTrue() { append(TrueTag); } |
| |
| void writeFalse() { append(FalseTag); } |
| |
| void writeBooleanObject(bool value) |
| { |
| append(value ? TrueObjectTag : FalseObjectTag); |
| } |
| |
| void writeOneByteString(v8::Handle<v8::String>& string) |
| { |
| int stringLength = string->Length(); |
| int utf8Length = string->Utf8Length(); |
| ASSERT(stringLength >= 0 && utf8Length >= 0); |
| |
| append(StringTag); |
| doWriteUint32(static_cast<uint32_t>(utf8Length)); |
| ensureSpace(utf8Length); |
| |
| // ASCII fast path. |
| if (stringLength == utf8Length) { |
| string->WriteOneByte(byteAt(m_position), 0, utf8Length, v8StringWriteOptions()); |
| } else { |
| char* buffer = reinterpret_cast<char*>(byteAt(m_position)); |
| string->WriteUtf8(buffer, utf8Length, 0, v8StringWriteOptions()); |
| } |
| m_position += utf8Length; |
| } |
| |
| void writeUCharString(v8::Handle<v8::String>& string) |
| { |
| int length = string->Length(); |
| ASSERT(length >= 0); |
| |
| int size = length * sizeof(UChar); |
| int bytes = bytesNeededToWireEncode(static_cast<uint32_t>(size)); |
| if ((m_position + 1 + bytes) & 1) |
| append(PaddingTag); |
| |
| append(StringUCharTag); |
| doWriteUint32(static_cast<uint32_t>(size)); |
| ensureSpace(size); |
| |
| ASSERT(!(m_position & 1)); |
| uint16_t* buffer = reinterpret_cast<uint16_t*>(byteAt(m_position)); |
| string->Write(buffer, 0, length, v8StringWriteOptions()); |
| m_position += size; |
| } |
| |
| void writeStringObject(const char* data, int length) |
| { |
| ASSERT(length >= 0); |
| append(StringObjectTag); |
| doWriteString(data, length); |
| } |
| |
| void writeWebCoreString(const String& string) |
| { |
| // Uses UTF8 encoding so we can read it back as either V8 or |
| // WebCore string. |
| append(StringTag); |
| doWriteWebCoreString(string); |
| } |
| |
| void writeVersion() |
| { |
| append(VersionTag); |
| doWriteUint32(SerializedScriptValue::wireFormatVersion); |
| } |
| |
| void writeInt32(int32_t value) |
| { |
| append(Int32Tag); |
| doWriteUint32(ZigZag::encode(static_cast<uint32_t>(value))); |
| } |
| |
| void writeUint32(uint32_t value) |
| { |
| append(Uint32Tag); |
| doWriteUint32(value); |
| } |
| |
| void writeDate(double numberValue) |
| { |
| append(DateTag); |
| doWriteNumber(numberValue); |
| } |
| |
| void writeNumber(double number) |
| { |
| append(NumberTag); |
| doWriteNumber(number); |
| } |
| |
| void writeNumberObject(double number) |
| { |
| append(NumberObjectTag); |
| doWriteNumber(number); |
| } |
| |
| void writeBlob(const String& uuid, const String& type, unsigned long long size) |
| { |
| append(BlobTag); |
| doWriteWebCoreString(uuid); |
| doWriteWebCoreString(type); |
| doWriteUint64(size); |
| } |
| |
| void writeBlobIndex(int blobIndex) |
| { |
| ASSERT(blobIndex >= 0); |
| append(BlobIndexTag); |
| doWriteUint32(blobIndex); |
| } |
| |
| void writeDOMFileSystem(int type, const String& name, const String& url) |
| { |
| append(DOMFileSystemTag); |
| doWriteUint32(type); |
| doWriteWebCoreString(name); |
| doWriteWebCoreString(url); |
| } |
| |
| void writeFile(const File& file) |
| { |
| append(FileTag); |
| doWriteFile(file); |
| } |
| |
| void writeFileIndex(int blobIndex) |
| { |
| append(FileIndexTag); |
| doWriteUint32(blobIndex); |
| } |
| |
| void writeFileList(const FileList& fileList) |
| { |
| append(FileListTag); |
| uint32_t length = fileList.length(); |
| doWriteUint32(length); |
| for (unsigned i = 0; i < length; ++i) |
| doWriteFile(*fileList.item(i)); |
| } |
| |
| void writeFileListIndex(const Vector<int>& blobIndices) |
| { |
| append(FileListIndexTag); |
| uint32_t length = blobIndices.size(); |
| doWriteUint32(length); |
| for (unsigned i = 0; i < length; ++i) |
| doWriteUint32(blobIndices[i]); |
| } |
| |
| bool writeCryptoKey(const blink::WebCryptoKey& key) |
| { |
| append(static_cast<uint8_t>(CryptoKeyTag)); |
| |
| switch (key.algorithm().paramsType()) { |
| case blink::WebCryptoKeyAlgorithmParamsTypeAes: |
| doWriteAesKey(key); |
| break; |
| case blink::WebCryptoKeyAlgorithmParamsTypeHmac: |
| doWriteHmacKey(key); |
| break; |
| case blink::WebCryptoKeyAlgorithmParamsTypeRsaHashed: |
| doWriteRsaHashedKey(key); |
| break; |
| case blink::WebCryptoKeyAlgorithmParamsTypeNone: |
| ASSERT_NOT_REACHED(); |
| return false; |
| } |
| |
| doWriteKeyUsages(key.usages(), key.extractable()); |
| |
| blink::WebVector<uint8_t> keyData; |
| if (!blink::Platform::current()->crypto()->serializeKeyForClone(key, keyData)) |
| return false; |
| |
| doWriteUint32(keyData.size()); |
| append(keyData.data(), keyData.size()); |
| return true; |
| } |
| |
| void writeArrayBuffer(const ArrayBuffer& arrayBuffer) |
| { |
| append(ArrayBufferTag); |
| doWriteArrayBuffer(arrayBuffer); |
| } |
| |
| void writeArrayBufferView(const ArrayBufferView& arrayBufferView) |
| { |
| append(ArrayBufferViewTag); |
| #if ENABLE(ASSERT) |
| const ArrayBuffer& arrayBuffer = *arrayBufferView.buffer(); |
| ASSERT(static_cast<const uint8_t*>(arrayBuffer.data()) + arrayBufferView.byteOffset() == |
| static_cast<const uint8_t*>(arrayBufferView.baseAddress())); |
| #endif |
| ArrayBufferView::ViewType type = arrayBufferView.type(); |
| |
| if (type == ArrayBufferView::TypeInt8) |
| append(ByteArrayTag); |
| else if (type == ArrayBufferView::TypeUint8Clamped) |
| append(UnsignedByteClampedArrayTag); |
| else if (type == ArrayBufferView::TypeUint8) |
| append(UnsignedByteArrayTag); |
| else if (type == ArrayBufferView::TypeInt16) |
| append(ShortArrayTag); |
| else if (type == ArrayBufferView::TypeUint16) |
| append(UnsignedShortArrayTag); |
| else if (type == ArrayBufferView::TypeInt32) |
| append(IntArrayTag); |
| else if (type == ArrayBufferView::TypeUint32) |
| append(UnsignedIntArrayTag); |
| else if (type == ArrayBufferView::TypeFloat32) |
| append(FloatArrayTag); |
| else if (type == ArrayBufferView::TypeFloat64) |
| append(DoubleArrayTag); |
| else if (type == ArrayBufferView::TypeDataView) |
| append(DataViewTag); |
| else |
| ASSERT_NOT_REACHED(); |
| doWriteUint32(arrayBufferView.byteOffset()); |
| doWriteUint32(arrayBufferView.byteLength()); |
| } |
| |
| void writeImageData(uint32_t width, uint32_t height, const uint8_t* pixelData, uint32_t pixelDataLength) |
| { |
| append(ImageDataTag); |
| doWriteUint32(width); |
| doWriteUint32(height); |
| doWriteUint32(pixelDataLength); |
| append(pixelData, pixelDataLength); |
| } |
| |
| void writeRegExp(v8::Local<v8::String> pattern, v8::RegExp::Flags flags) |
| { |
| append(RegExpTag); |
| v8::String::Utf8Value patternUtf8Value(pattern); |
| doWriteString(*patternUtf8Value, patternUtf8Value.length()); |
| doWriteUint32(static_cast<uint32_t>(flags)); |
| } |
| |
| void writeTransferredMessagePort(uint32_t index) |
| { |
| append(MessagePortTag); |
| doWriteUint32(index); |
| } |
| |
| void writeTransferredArrayBuffer(uint32_t index) |
| { |
| append(ArrayBufferTransferTag); |
| doWriteUint32(index); |
| } |
| |
| void writeObjectReference(uint32_t reference) |
| { |
| append(ObjectReferenceTag); |
| doWriteUint32(reference); |
| } |
| |
| void writeObject(uint32_t numProperties) |
| { |
| append(ObjectTag); |
| doWriteUint32(numProperties); |
| } |
| |
| void writeSparseArray(uint32_t numProperties, uint32_t length) |
| { |
| append(SparseArrayTag); |
| doWriteUint32(numProperties); |
| doWriteUint32(length); |
| } |
| |
| void writeDenseArray(uint32_t numProperties, uint32_t length) |
| { |
| append(DenseArrayTag); |
| doWriteUint32(numProperties); |
| doWriteUint32(length); |
| } |
| |
| String takeWireString() |
| { |
| COMPILE_ASSERT(sizeof(BufferValueType) == 2, BufferValueTypeIsTwoBytes); |
| fillHole(); |
| String data = String(m_buffer.data(), m_buffer.size()); |
| data.impl()->truncateAssumingIsolated((m_position + 1) / sizeof(BufferValueType)); |
| return data; |
| } |
| |
| void writeReferenceCount(uint32_t numberOfReferences) |
| { |
| append(ReferenceCountTag); |
| doWriteUint32(numberOfReferences); |
| } |
| |
| void writeGenerateFreshObject() |
| { |
| append(GenerateFreshObjectTag); |
| } |
| |
| void writeGenerateFreshSparseArray(uint32_t length) |
| { |
| append(GenerateFreshSparseArrayTag); |
| doWriteUint32(length); |
| } |
| |
| void writeGenerateFreshDenseArray(uint32_t length) |
| { |
| append(GenerateFreshDenseArrayTag); |
| doWriteUint32(length); |
| } |
| |
| private: |
| void doWriteFile(const File& file) |
| { |
| doWriteWebCoreString(file.hasBackingFile() ? file.path() : ""); |
| doWriteWebCoreString(file.name()); |
| doWriteWebCoreString(file.webkitRelativePath()); |
| doWriteWebCoreString(file.uuid()); |
| doWriteWebCoreString(file.type()); |
| |
| // FIXME don't use 1 byte to encode a flag. |
| if (file.hasValidSnapshotMetadata()) { |
| doWriteUint32(static_cast<uint8_t>(1)); |
| |
| long long size; |
| double lastModified; |
| file.captureSnapshot(size, lastModified); |
| doWriteUint64(static_cast<uint64_t>(size)); |
| doWriteNumber(lastModified); |
| } else { |
| doWriteUint32(static_cast<uint8_t>(0)); |
| } |
| |
| doWriteUint32(static_cast<uint8_t>((file.userVisibility() == File::IsUserVisible) ? 1 : 0)); |
| } |
| |
| void doWriteArrayBuffer(const ArrayBuffer& arrayBuffer) |
| { |
| uint32_t byteLength = arrayBuffer.byteLength(); |
| doWriteUint32(byteLength); |
| append(static_cast<const uint8_t*>(arrayBuffer.data()), byteLength); |
| } |
| |
| void doWriteString(const char* data, int length) |
| { |
| doWriteUint32(static_cast<uint32_t>(length)); |
| append(reinterpret_cast<const uint8_t*>(data), length); |
| } |
| |
| void doWriteWebCoreString(const String& string) |
| { |
| StringUTF8Adaptor stringUTF8(string); |
| doWriteString(stringUTF8.data(), stringUTF8.length()); |
| } |
| |
| void doWriteHmacKey(const blink::WebCryptoKey& key) |
| { |
| ASSERT(key.algorithm().paramsType() == blink::WebCryptoKeyAlgorithmParamsTypeHmac); |
| |
| append(static_cast<uint8_t>(HmacKeyTag)); |
| ASSERT(!(key.algorithm().hmacParams()->lengthBits() % 8)); |
| doWriteUint32(key.algorithm().hmacParams()->lengthBits() / 8); |
| doWriteAlgorithmId(key.algorithm().hmacParams()->hash().id()); |
| } |
| |
| void doWriteAesKey(const blink::WebCryptoKey& key) |
| { |
| ASSERT(key.algorithm().paramsType() == blink::WebCryptoKeyAlgorithmParamsTypeAes); |
| |
| append(static_cast<uint8_t>(AesKeyTag)); |
| doWriteAlgorithmId(key.algorithm().id()); |
| // Converting the key length from bits to bytes is lossless and makes |
| // it fit in 1 byte. |
| ASSERT(!(key.algorithm().aesParams()->lengthBits() % 8)); |
| doWriteUint32(key.algorithm().aesParams()->lengthBits() / 8); |
| } |
| |
| void doWriteRsaHashedKey(const blink::WebCryptoKey& key) |
| { |
| ASSERT(key.algorithm().rsaHashedParams()); |
| append(static_cast<uint8_t>(RsaHashedKeyTag)); |
| |
| doWriteAlgorithmId(key.algorithm().id()); |
| |
| switch (key.type()) { |
| case blink::WebCryptoKeyTypePublic: |
| doWriteUint32(PublicKeyType); |
| break; |
| case blink::WebCryptoKeyTypePrivate: |
| doWriteUint32(PrivateKeyType); |
| break; |
| case blink::WebCryptoKeyTypeSecret: |
| ASSERT_NOT_REACHED(); |
| } |
| |
| const blink::WebCryptoRsaHashedKeyAlgorithmParams* params = key.algorithm().rsaHashedParams(); |
| doWriteUint32(params->modulusLengthBits()); |
| doWriteUint32(params->publicExponent().size()); |
| append(params->publicExponent().data(), params->publicExponent().size()); |
| doWriteAlgorithmId(key.algorithm().rsaHashedParams()->hash().id()); |
| } |
| |
| void doWriteAlgorithmId(blink::WebCryptoAlgorithmId id) |
| { |
| switch (id) { |
| case blink::WebCryptoAlgorithmIdAesCbc: |
| return doWriteUint32(AesCbcTag); |
| case blink::WebCryptoAlgorithmIdHmac: |
| return doWriteUint32(HmacTag); |
| case blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5: |
| return doWriteUint32(RsaSsaPkcs1v1_5Tag); |
| case blink::WebCryptoAlgorithmIdSha1: |
| return doWriteUint32(Sha1Tag); |
| case blink::WebCryptoAlgorithmIdSha256: |
| return doWriteUint32(Sha256Tag); |
| case blink::WebCryptoAlgorithmIdSha384: |
| return doWriteUint32(Sha384Tag); |
| case blink::WebCryptoAlgorithmIdSha512: |
| return doWriteUint32(Sha512Tag); |
| case blink::WebCryptoAlgorithmIdAesGcm: |
| return doWriteUint32(AesGcmTag); |
| case blink::WebCryptoAlgorithmIdRsaOaep: |
| return doWriteUint32(RsaOaepTag); |
| case blink::WebCryptoAlgorithmIdAesCtr: |
| return doWriteUint32(AesCtrTag); |
| case blink::WebCryptoAlgorithmIdAesKw: |
| return doWriteUint32(AesKwTag); |
| case blink::WebCryptoAlgorithmIdRsaPss: |
| return doWriteUint32(RsaPssTag); |
| } |
| ASSERT_NOT_REACHED(); |
| } |
| |
| void doWriteKeyUsages(const blink::WebCryptoKeyUsageMask usages, bool extractable) |
| { |
| // Reminder to update this when adding new key usages. |
| COMPILE_ASSERT(blink::EndOfWebCryptoKeyUsage == (1 << 7) + 1, UpdateMe); |
| |
| uint32_t value = 0; |
| |
| if (extractable) |
| value |= ExtractableUsage; |
| |
| if (usages & blink::WebCryptoKeyUsageEncrypt) |
| value |= EncryptUsage; |
| if (usages & blink::WebCryptoKeyUsageDecrypt) |
| value |= DecryptUsage; |
| if (usages & blink::WebCryptoKeyUsageSign) |
| value |= SignUsage; |
| if (usages & blink::WebCryptoKeyUsageVerify) |
| value |= VerifyUsage; |
| if (usages & blink::WebCryptoKeyUsageDeriveKey) |
| value |= DeriveKeyUsage; |
| if (usages & blink::WebCryptoKeyUsageWrapKey) |
| value |= WrapKeyUsage; |
| if (usages & blink::WebCryptoKeyUsageUnwrapKey) |
| value |= UnwrapKeyUsage; |
| if (usages & blink::WebCryptoKeyUsageDeriveBits) |
| value |= DeriveBitsUsage; |
| |
| doWriteUint32(value); |
| } |
| |
| int bytesNeededToWireEncode(uint32_t value) |
| { |
| int bytes = 1; |
| while (true) { |
| value >>= varIntShift; |
| if (!value) |
| break; |
| ++bytes; |
| } |
| |
| return bytes; |
| } |
| |
| template<class T> |
| void doWriteUintHelper(T value) |
| { |
| while (true) { |
| uint8_t b = (value & varIntMask); |
| value >>= varIntShift; |
| if (!value) { |
| append(b); |
| break; |
| } |
| append(b | (1 << varIntShift)); |
| } |
| } |
| |
| void doWriteUint32(uint32_t value) |
| { |
| doWriteUintHelper(value); |
| } |
| |
| void doWriteUint64(uint64_t value) |
| { |
| doWriteUintHelper(value); |
| } |
| |
| void doWriteNumber(double number) |
| { |
| append(reinterpret_cast<uint8_t*>(&number), sizeof(number)); |
| } |
| |
| void append(SerializationTag tag) |
| { |
| append(static_cast<uint8_t>(tag)); |
| } |
| |
| void append(uint8_t b) |
| { |
| ensureSpace(1); |
| *byteAt(m_position++) = b; |
| } |
| |
| void append(const uint8_t* data, int length) |
| { |
| ensureSpace(length); |
| memcpy(byteAt(m_position), data, length); |
| m_position += length; |
| } |
| |
| void ensureSpace(unsigned extra) |
| { |
| COMPILE_ASSERT(sizeof(BufferValueType) == 2, BufferValueTypeIsTwoBytes); |
| m_buffer.resize((m_position + extra + 1) / sizeof(BufferValueType)); // "+ 1" to round up. |
| } |
| |
| void fillHole() |
| { |
| COMPILE_ASSERT(sizeof(BufferValueType) == 2, BufferValueTypeIsTwoBytes); |
| // If the writer is at odd position in the buffer, then one of |
| // the bytes in the last UChar is not initialized. |
| if (m_position % 2) |
| *byteAt(m_position) = static_cast<uint8_t>(PaddingTag); |
| } |
| |
| uint8_t* byteAt(int position) |
| { |
| return reinterpret_cast<uint8_t*>(m_buffer.data()) + position; |
| } |
| |
| int v8StringWriteOptions() |
| { |
| return v8::String::NO_NULL_TERMINATION; |
| } |
| |
| Vector<BufferValueType> m_buffer; |
| unsigned m_position; |
| }; |
| |
| static v8::Handle<v8::Object> toV8Object(MessagePort* impl, v8::Handle<v8::Object> creationContext, v8::Isolate* isolate) |
| { |
| if (!impl) |
| return v8::Handle<v8::Object>(); |
| v8::Handle<v8::Value> wrapper = toV8(impl, creationContext, isolate); |
| ASSERT(wrapper->IsObject()); |
| return wrapper.As<v8::Object>(); |
| } |
| |
| static v8::Handle<v8::ArrayBuffer> toV8Object(DOMArrayBuffer* impl, v8::Handle<v8::Object> creationContext, v8::Isolate* isolate) |
| { |
| if (!impl) |
| return v8::Handle<v8::ArrayBuffer>(); |
| v8::Handle<v8::Value> wrapper = toV8(impl, creationContext, isolate); |
| ASSERT(wrapper->IsArrayBuffer()); |
| return wrapper.As<v8::ArrayBuffer>(); |
| } |
| |
| class Serializer { |
| class StateBase; |
| public: |
| enum Status { |
| Success, |
| InputError, |
| DataCloneError, |
| JSException |
| }; |
| |
| Serializer(Writer& writer, MessagePortArray* messagePorts, ArrayBufferArray* arrayBuffers, WebBlobInfoArray* blobInfo, BlobDataHandleMap& blobDataHandles, v8::TryCatch& tryCatch, ScriptState* scriptState) |
| : m_scriptState(scriptState) |
| , m_writer(writer) |
| , m_tryCatch(tryCatch) |
| , m_depth(0) |
| , m_status(Success) |
| , m_nextObjectReference(0) |
| , m_blobInfo(blobInfo) |
| , m_blobDataHandles(blobDataHandles) |
| { |
| ASSERT(!tryCatch.HasCaught()); |
| v8::Handle<v8::Object> creationContext = m_scriptState->context()->Global(); |
| if (messagePorts) { |
| for (size_t i = 0; i < messagePorts->size(); i++) |
| m_transferredMessagePorts.set(toV8Object(messagePorts->at(i).get(), creationContext, isolate()), i); |
| } |
| if (arrayBuffers) { |
| for (size_t i = 0; i < arrayBuffers->size(); i++) { |
| v8::Handle<v8::Object> v8ArrayBuffer = toV8Object(arrayBuffers->at(i).get(), creationContext, isolate()); |
| // Coalesce multiple occurences of the same buffer to the first index. |
| if (!m_transferredArrayBuffers.contains(v8ArrayBuffer)) |
| m_transferredArrayBuffers.set(v8ArrayBuffer, i); |
| } |
| } |
| } |
| |
| v8::Isolate* isolate() { return m_scriptState->isolate(); } |
| |
| Status serialize(v8::Handle<v8::Value> value) |
| { |
| v8::HandleScope scope(isolate()); |
| m_writer.writeVersion(); |
| StateBase* state = doSerialize(value, 0); |
| while (state) |
| state = state->advance(*this); |
| return m_status; |
| } |
| |
| String errorMessage() { return m_errorMessage; } |
| |
| // Functions used by serialization states. |
| StateBase* doSerialize(v8::Handle<v8::Value>, StateBase* next); |
| |
| StateBase* doSerializeArrayBuffer(v8::Handle<v8::Value> arrayBuffer, StateBase* next) |
| { |
| return doSerialize(arrayBuffer, next); |
| } |
| |
| StateBase* checkException(StateBase* state) |
| { |
| return m_tryCatch.HasCaught() ? handleError(JSException, "", state) : 0; |
| } |
| |
| StateBase* writeObject(uint32_t numProperties, StateBase* state) |
| { |
| m_writer.writeObject(numProperties); |
| return pop(state); |
| } |
| |
| StateBase* writeSparseArray(uint32_t numProperties, uint32_t length, StateBase* state) |
| { |
| m_writer.writeSparseArray(numProperties, length); |
| return pop(state); |
| } |
| |
| StateBase* writeDenseArray(uint32_t numProperties, uint32_t length, StateBase* state) |
| { |
| m_writer.writeDenseArray(numProperties, length); |
| return pop(state); |
| } |
| |
| |
| private: |
| class StateBase { |
| WTF_MAKE_NONCOPYABLE(StateBase); |
| public: |
| virtual ~StateBase() { } |
| |
| // Link to the next state to form a stack. |
| StateBase* nextState() { return m_next; } |
| |
| // Composite object we're processing in this state. |
| v8::Handle<v8::Value> composite() { return m_composite; } |
| |
| // Serializes (a part of) the current composite and returns |
| // the next state to process or null when this is the final |
| // state. |
| virtual StateBase* advance(Serializer&) = 0; |
| |
| protected: |
| StateBase(v8::Handle<v8::Value> composite, StateBase* next) |
| : m_composite(composite) |
| , m_next(next) |
| { |
| } |
| |
| private: |
| v8::Handle<v8::Value> m_composite; |
| StateBase* m_next; |
| }; |
| |
| // Dummy state that is used to signal serialization errors. |
| class ErrorState final : public StateBase { |
| public: |
| ErrorState() |
| : StateBase(v8Undefined(), 0) |
| { |
| } |
| |
| virtual StateBase* advance(Serializer&) override |
| { |
| delete this; |
| return 0; |
| } |
| }; |
| |
| template <typename T> |
| class State : public StateBase { |
| public: |
| v8::Handle<T> composite() { return v8::Handle<T>::Cast(StateBase::composite()); } |
| |
| protected: |
| State(v8::Handle<T> composite, StateBase* next) |
| : StateBase(composite, next) |
| { |
| } |
| }; |
| |
| class AbstractObjectState : public State<v8::Object> { |
| public: |
| AbstractObjectState(v8::Handle<v8::Object> object, StateBase* next) |
| : State<v8::Object>(object, next) |
| , m_index(0) |
| , m_numSerializedProperties(0) |
| , m_nameDone(false) |
| { |
| } |
| |
| protected: |
| virtual StateBase* objectDone(unsigned numProperties, Serializer&) = 0; |
| |
| StateBase* serializeProperties(bool ignoreIndexed, Serializer& serializer) |
| { |
| while (m_index < m_propertyNames->Length()) { |
| if (!m_nameDone) { |
| v8::Local<v8::Value> propertyName = m_propertyNames->Get(m_index); |
| if (StateBase* newState = serializer.checkException(this)) |
| return newState; |
| if (propertyName.IsEmpty()) |
| return serializer.handleError(InputError, "Empty property names cannot be cloned.", this); |
| bool hasStringProperty = propertyName->IsString() && composite()->HasRealNamedProperty(propertyName.As<v8::String>()); |
| if (StateBase* newState = serializer.checkException(this)) |
| return newState; |
| bool hasIndexedProperty = !hasStringProperty && propertyName->IsUint32() && composite()->HasRealIndexedProperty(propertyName->Uint32Value()); |
| if (StateBase* newState = serializer.checkException(this)) |
| return newState; |
| if (hasStringProperty || (hasIndexedProperty && !ignoreIndexed)) { |
| m_propertyName = propertyName; |
| } else { |
| ++m_index; |
| continue; |
| } |
| } |
| ASSERT(!m_propertyName.IsEmpty()); |
| if (!m_nameDone) { |
| m_nameDone = true; |
| if (StateBase* newState = serializer.doSerialize(m_propertyName, this)) |
| return newState; |
| } |
| v8::Local<v8::Value> value = composite()->Get(m_propertyName); |
| if (StateBase* newState = serializer.checkException(this)) |
| return newState; |
| m_nameDone = false; |
| m_propertyName.Clear(); |
| ++m_index; |
| ++m_numSerializedProperties; |
| // If we return early here, it's either because we have pushed a new state onto the |
| // serialization state stack or because we have encountered an error (and in both cases |
| // we are unwinding the native stack). |
| if (StateBase* newState = serializer.doSerialize(value, this)) |
| return newState; |
| } |
| return objectDone(m_numSerializedProperties, serializer); |
| } |
| |
| v8::Local<v8::Array> m_propertyNames; |
| |
| private: |
| v8::Local<v8::Value> m_propertyName; |
| unsigned m_index; |
| unsigned m_numSerializedProperties; |
| bool m_nameDone; |
| }; |
| |
| class ObjectState final : public AbstractObjectState { |
| public: |
| ObjectState(v8::Handle<v8::Object> object, StateBase* next) |
| : AbstractObjectState(object, next) |
| { |
| } |
| |
| virtual StateBase* advance(Serializer& serializer) override |
| { |
| if (m_propertyNames.IsEmpty()) { |
| m_propertyNames = composite()->GetPropertyNames(); |
| if (StateBase* newState = serializer.checkException(this)) |
| return newState; |
| if (m_propertyNames.IsEmpty()) |
| return serializer.handleError(InputError, "Empty property names cannot be cloned.", nextState()); |
| } |
| return serializeProperties(false, serializer); |
| } |
| |
| protected: |
| virtual StateBase* objectDone(unsigned numProperties, Serializer& serializer) override |
| { |
| return serializer.writeObject(numProperties, this); |
| } |
| }; |
| |
| class DenseArrayState final : public AbstractObjectState { |
| public: |
| DenseArrayState(v8::Handle<v8::Array> array, v8::Handle<v8::Array> propertyNames, StateBase* next, v8::Isolate* isolate) |
| : AbstractObjectState(array, next) |
| , m_arrayIndex(0) |
| , m_arrayLength(array->Length()) |
| { |
| m_propertyNames = v8::Local<v8::Array>::New(isolate, propertyNames); |
| } |
| |
| virtual StateBase* advance(Serializer& serializer) override |
| { |
| while (m_arrayIndex < m_arrayLength) { |
| v8::Handle<v8::Value> value = composite().As<v8::Array>()->Get(m_arrayIndex); |
| m_arrayIndex++; |
| if (StateBase* newState = serializer.checkException(this)) |
| return newState; |
| if (StateBase* newState = serializer.doSerialize(value, this)) |
| return newState; |
| } |
| return serializeProperties(true, serializer); |
| } |
| |
| protected: |
| virtual StateBase* objectDone(unsigned numProperties, Serializer& serializer) override |
| { |
| return serializer.writeDenseArray(numProperties, m_arrayLength, this); |
| } |
| |
| private: |
| uint32_t m_arrayIndex; |
| uint32_t m_arrayLength; |
| }; |
| |
| class SparseArrayState final : public AbstractObjectState { |
| public: |
| SparseArrayState(v8::Handle<v8::Array> array, v8::Handle<v8::Array> propertyNames, StateBase* next, v8::Isolate* isolate) |
| : AbstractObjectState(array, next) |
| { |
| m_propertyNames = v8::Local<v8::Array>::New(isolate, propertyNames); |
| } |
| |
| virtual StateBase* advance(Serializer& serializer) override |
| { |
| return serializeProperties(false, serializer); |
| } |
| |
| protected: |
| virtual StateBase* objectDone(unsigned numProperties, Serializer& serializer) override |
| { |
| return serializer.writeSparseArray(numProperties, composite().As<v8::Array>()->Length(), this); |
| } |
| }; |
| |
| StateBase* push(StateBase* state) |
| { |
| ASSERT(state); |
| ++m_depth; |
| return checkComposite(state) ? state : handleError(InputError, "Value being cloned is either cyclic or too deeply nested.", state); |
| } |
| |
| StateBase* pop(StateBase* state) |
| { |
| ASSERT(state); |
| --m_depth; |
| StateBase* next = state->nextState(); |
| delete state; |
| return next; |
| } |
| |
| StateBase* handleError(Status errorStatus, const String& message, StateBase* state) |
| { |
| ASSERT(errorStatus != Success); |
| m_status = errorStatus; |
| m_errorMessage = message; |
| while (state) { |
| StateBase* tmp = state->nextState(); |
| delete state; |
| state = tmp; |
| } |
| return new ErrorState; |
| } |
| |
| bool checkComposite(StateBase* top) |
| { |
| ASSERT(top); |
| if (m_depth > maxDepth) |
| return false; |
| if (!shouldCheckForCycles(m_depth)) |
| return true; |
| v8::Handle<v8::Value> composite = top->composite(); |
| for (StateBase* state = top->nextState(); state; state = state->nextState()) { |
| if (state->composite() == composite) |
| return false; |
| } |
| return true; |
| } |
| |
| void writeString(v8::Handle<v8::Value> value) |
| { |
| v8::Handle<v8::String> string = value.As<v8::String>(); |
| if (!string->Length() || string->IsOneByte()) |
| m_writer.writeOneByteString(string); |
| else |
| m_writer.writeUCharString(string); |
| } |
| |
| void writeStringObject(v8::Handle<v8::Value> value) |
| { |
| v8::Handle<v8::StringObject> stringObject = value.As<v8::StringObject>(); |
| v8::String::Utf8Value stringValue(stringObject->ValueOf()); |
| m_writer.writeStringObject(*stringValue, stringValue.length()); |
| } |
| |
| void writeNumberObject(v8::Handle<v8::Value> value) |
| { |
| v8::Handle<v8::NumberObject> numberObject = value.As<v8::NumberObject>(); |
| m_writer.writeNumberObject(numberObject->ValueOf()); |
| } |
| |
| void writeBooleanObject(v8::Handle<v8::Value> value) |
| { |
| v8::Handle<v8::BooleanObject> booleanObject = value.As<v8::BooleanObject>(); |
| m_writer.writeBooleanObject(booleanObject->ValueOf()); |
| } |
| |
| StateBase* writeBlob(v8::Handle<v8::Value> value, StateBase* next) |
| { |
| Blob* blob = V8Blob::toImpl(value.As<v8::Object>()); |
| if (!blob) |
| return 0; |
| if (blob->hasBeenClosed()) |
| return handleError(DataCloneError, "A Blob object has been closed, and could therefore not be cloned.", next); |
| int blobIndex = -1; |
| m_blobDataHandles.set(blob->uuid(), blob->blobDataHandle()); |
| if (appendBlobInfo(blob->uuid(), blob->type(), blob->size(), &blobIndex)) |
| m_writer.writeBlobIndex(blobIndex); |
| else |
| m_writer.writeBlob(blob->uuid(), blob->type(), blob->size()); |
| return 0; |
| } |
| |
| StateBase* writeDOMFileSystem(v8::Handle<v8::Value> value, StateBase* next) |
| { |
| DOMFileSystem* fs = V8DOMFileSystem::toImpl(value.As<v8::Object>()); |
| if (!fs) |
| return 0; |
| if (!fs->clonable()) |
| return handleError(DataCloneError, "A FileSystem object could not be cloned.", next); |
| m_writer.writeDOMFileSystem(fs->type(), fs->name(), fs->rootURL().string()); |
| return 0; |
| } |
| |
| StateBase* writeFile(v8::Handle<v8::Value> value, StateBase* next) |
| { |
| File* file = V8File::toImpl(value.As<v8::Object>()); |
| if (!file) |
| return 0; |
| if (file->hasBeenClosed()) |
| return handleError(DataCloneError, "A File object has been closed, and could therefore not be cloned.", next); |
| int blobIndex = -1; |
| m_blobDataHandles.set(file->uuid(), file->blobDataHandle()); |
| if (appendFileInfo(file, &blobIndex)) { |
| ASSERT(blobIndex >= 0); |
| m_writer.writeFileIndex(blobIndex); |
| } else { |
| m_writer.writeFile(*file); |
| } |
| return 0; |
| } |
| |
| StateBase* writeFileList(v8::Handle<v8::Value> value, StateBase* next) |
| { |
| FileList* fileList = V8FileList::toImpl(value.As<v8::Object>()); |
| if (!fileList) |
| return 0; |
| unsigned length = fileList->length(); |
| Vector<int> blobIndices; |
| for (unsigned i = 0; i < length; ++i) { |
| int blobIndex = -1; |
| const File* file = fileList->item(i); |
| if (file->hasBeenClosed()) |
| return handleError(DataCloneError, "A File object has been closed, and could therefore not be cloned.", next); |
| m_blobDataHandles.set(file->uuid(), file->blobDataHandle()); |
| if (appendFileInfo(file, &blobIndex)) { |
| ASSERT(!i || blobIndex > 0); |
| ASSERT(blobIndex >= 0); |
| blobIndices.append(blobIndex); |
| } |
| } |
| if (!blobIndices.isEmpty()) |
| m_writer.writeFileListIndex(blobIndices); |
| else |
| m_writer.writeFileList(*fileList); |
| return 0; |
| } |
| |
| bool writeCryptoKey(v8::Handle<v8::Value> value) |
| { |
| CryptoKey* key = V8CryptoKey::toImpl(value.As<v8::Object>()); |
| if (!key) |
| return false; |
| return m_writer.writeCryptoKey(key->key()); |
| } |
| |
| void writeImageData(v8::Handle<v8::Value> value) |
| { |
| ImageData* imageData = V8ImageData::toImpl(value.As<v8::Object>()); |
| if (!imageData) |
| return; |
| Uint8ClampedArray* pixelArray = imageData->data(); |
| m_writer.writeImageData(imageData->width(), imageData->height(), pixelArray->data(), pixelArray->length()); |
| } |
| |
| void writeRegExp(v8::Handle<v8::Value> value) |
| { |
| v8::Handle<v8::RegExp> regExp = value.As<v8::RegExp>(); |
| m_writer.writeRegExp(regExp->GetSource(), regExp->GetFlags()); |
| } |
| |
| StateBase* writeAndGreyArrayBufferView(v8::Handle<v8::Object> object, StateBase* next) |
| { |
| ASSERT(!object.IsEmpty()); |
| DOMArrayBufferView* arrayBufferView = V8ArrayBufferView::toImpl(object); |
| if (!arrayBufferView) |
| return 0; |
| if (!arrayBufferView->buffer()) |
| return handleError(DataCloneError, "An ArrayBuffer could not be cloned.", next); |
| v8::Handle<v8::Value> underlyingBuffer = toV8(arrayBufferView->buffer(), m_scriptState->context()->Global(), isolate()); |
| if (underlyingBuffer.IsEmpty()) |
| return handleError(DataCloneError, "An ArrayBuffer could not be cloned.", next); |
| StateBase* stateOut = doSerializeArrayBuffer(underlyingBuffer, next); |
| if (stateOut) |
| return stateOut; |
| m_writer.writeArrayBufferView(*arrayBufferView->view()); |
| // This should be safe: we serialize something that we know to be a wrapper (see |
| // the toV8 call above), so the call to doSerializeArrayBuffer should neither |
| // cause the system stack to overflow nor should it have potential to reach |
| // this ArrayBufferView again. |
| // |
| // We do need to grey the underlying buffer before we grey its view, however; |
| // ArrayBuffers may be shared, so they need to be given reference IDs, and an |
| // ArrayBufferView cannot be constructed without a corresponding ArrayBuffer |
| // (or without an additional tag that would allow us to do two-stage construction |
| // like we do for Objects and Arrays). |
| greyObject(object); |
| return 0; |
| } |
| |
| StateBase* writeArrayBuffer(v8::Handle<v8::Value> value, StateBase* next) |
| { |
| DOMArrayBuffer* arrayBuffer = V8ArrayBuffer::toImpl(value.As<v8::Object>()); |
| if (!arrayBuffer) |
| return 0; |
| if (arrayBuffer->isNeutered()) |
| return handleError(DataCloneError, "An ArrayBuffer is neutered and could not be cloned.", next); |
| ASSERT(!m_transferredArrayBuffers.contains(value.As<v8::Object>())); |
| m_writer.writeArrayBuffer(*arrayBuffer->buffer()); |
| return 0; |
| } |
| |
| StateBase* writeTransferredArrayBuffer(v8::Handle<v8::Value> value, uint32_t index, StateBase* next) |
| { |
| DOMArrayBuffer* arrayBuffer = V8ArrayBuffer::toImpl(value.As<v8::Object>()); |
| if (!arrayBuffer) |
| return 0; |
| if (arrayBuffer->isNeutered()) |
| return handleError(DataCloneError, "An ArrayBuffer is neutered and could not be cloned.", next); |
| m_writer.writeTransferredArrayBuffer(index); |
| return 0; |
| } |
| |
| static bool shouldSerializeDensely(uint32_t length, uint32_t propertyCount) |
| { |
| // Let K be the cost of serializing all property values that are there |
| // Cost of serializing sparsely: 5*propertyCount + K (5 bytes per uint32_t key) |
| // Cost of serializing densely: K + 1*(length - propertyCount) (1 byte for all properties that are not there) |
| // so densely is better than sparsly whenever 6*propertyCount > length |
| return 6 * propertyCount >= length; |
| } |
| |
| StateBase* startArrayState(v8::Handle<v8::Array> array, StateBase* next) |
| { |
| v8::Handle<v8::Array> propertyNames = array->GetPropertyNames(); |
| if (StateBase* newState = checkException(next)) |
| return newState; |
| uint32_t length = array->Length(); |
| |
| if (shouldSerializeDensely(length, propertyNames->Length())) { |
| m_writer.writeGenerateFreshDenseArray(length); |
| return push(new DenseArrayState(array, propertyNames, next, isolate())); |
| } |
| |
| m_writer.writeGenerateFreshSparseArray(length); |
| return push(new SparseArrayState(array, propertyNames, next, isolate())); |
| } |
| |
| StateBase* startObjectState(v8::Handle<v8::Object> object, StateBase* next) |
| { |
| m_writer.writeGenerateFreshObject(); |
| // FIXME: check not a wrapper |
| return push(new ObjectState(object, next)); |
| } |
| |
| // Marks object as having been visited by the serializer and assigns it a unique object reference ID. |
| // An object may only be greyed once. |
| void greyObject(const v8::Handle<v8::Object>& object) |
| { |
| ASSERT(!m_objectPool.contains(object)); |
| uint32_t objectReference = m_nextObjectReference++; |
| m_objectPool.set(object, objectReference); |
| } |
| |
| bool appendBlobInfo(const String& uuid, const String& type, unsigned long long size, int* index) |
| { |
| if (!m_blobInfo) |
| return false; |
| *index = m_blobInfo->size(); |
| m_blobInfo->append(WebBlobInfo(uuid, type, size)); |
| return true; |
| } |
| |
| bool appendFileInfo(const File* file, int* index) |
| { |
| if (!m_blobInfo) |
| return false; |
| |
| long long size = -1; |
| double lastModified = invalidFileTime(); |
| file->captureSnapshot(size, lastModified); |
| *index = m_blobInfo->size(); |
| m_blobInfo->append(WebBlobInfo(file->uuid(), file->path(), file->name(), file->type(), lastModified, size)); |
| return true; |
| } |
| |
| RefPtr<ScriptState> m_scriptState; |
| Writer& m_writer; |
| v8::TryCatch& m_tryCatch; |
| int m_depth; |
| Status m_status; |
| String m_errorMessage; |
| typedef V8ObjectMap<v8::Object, uint32_t> ObjectPool; |
| ObjectPool m_objectPool; |
| ObjectPool m_transferredMessagePorts; |
| ObjectPool m_transferredArrayBuffers; |
| uint32_t m_nextObjectReference; |
| WebBlobInfoArray* m_blobInfo; |
| BlobDataHandleMap& m_blobDataHandles; |
| }; |
| |
| // Returns true if the provided object is to be considered a 'host object', as used in the |
| // HTML5 structured clone algorithm. |
| static bool isHostObject(v8::Handle<v8::Object> object) |
| { |
| // If the object has any internal fields, then we won't be able to serialize or deserialize |
| // them; conveniently, this is also a quick way to detect DOM wrapper objects, because |
| // the mechanism for these relies on data stored in these fields. We should |
| // catch external array data as a special case. |
| return object->InternalFieldCount() || object->HasIndexedPropertiesInExternalArrayData(); |
| } |
| |
| Serializer::StateBase* Serializer::doSerialize(v8::Handle<v8::Value> value, StateBase* next) |
| { |
| m_writer.writeReferenceCount(m_nextObjectReference); |
| uint32_t objectReference; |
| uint32_t arrayBufferIndex; |
| if ((value->IsObject() || value->IsDate() || value->IsRegExp()) |
| && m_objectPool.tryGet(value.As<v8::Object>(), &objectReference)) { |
| // Note that IsObject() also detects wrappers (eg, it will catch the things |
| // that we grey and write below). |
| ASSERT(!value->IsString()); |
| m_writer.writeObjectReference(objectReference); |
| } else if (value.IsEmpty()) { |
| return handleError(InputError, "The empty property name cannot be cloned.", next); |
| } else if (value->IsUndefined()) { |
| m_writer.writeUndefined(); |
| } else if (value->IsNull()) { |
| m_writer.writeNull(); |
| } else if (value->IsTrue()) { |
| m_writer.writeTrue(); |
| } else if (value->IsFalse()) { |
| m_writer.writeFalse(); |
| } else if (value->IsInt32()) { |
| m_writer.writeInt32(value->Int32Value()); |
| } else if (value->IsUint32()) { |
| m_writer.writeUint32(value->Uint32Value()); |
| } else if (value->IsNumber()) { |
| m_writer.writeNumber(value.As<v8::Number>()->Value()); |
| } else if (V8ArrayBufferView::hasInstance(value, isolate())) { |
| return writeAndGreyArrayBufferView(value.As<v8::Object>(), next); |
| } else if (value->IsString()) { |
| writeString(value); |
| } else if (V8MessagePort::hasInstance(value, isolate())) { |
| uint32_t messagePortIndex; |
| if (m_transferredMessagePorts.tryGet(value.As<v8::Object>(), &messagePortIndex)) { |
| m_writer.writeTransferredMessagePort(messagePortIndex); |
| } else { |
| return handleError(DataCloneError, "A MessagePort could not be cloned.", next); |
| } |
| } else if (V8ArrayBuffer::hasInstance(value, isolate()) && m_transferredArrayBuffers.tryGet(value.As<v8::Object>(), &arrayBufferIndex)) { |
| return writeTransferredArrayBuffer(value, arrayBufferIndex, next); |
| } else { |
| v8::Handle<v8::Object> jsObject = value.As<v8::Object>(); |
| if (jsObject.IsEmpty()) |
| return handleError(DataCloneError, "An object could not be cloned.", next); |
| greyObject(jsObject); |
| if (value->IsDate()) { |
| m_writer.writeDate(value->NumberValue()); |
| } else if (value->IsStringObject()) { |
| writeStringObject(value); |
| } else if (value->IsNumberObject()) { |
| writeNumberObject(value); |
| } else if (value->IsBooleanObject()) { |
| writeBooleanObject(value); |
| } else if (value->IsArray()) { |
| return startArrayState(value.As<v8::Array>(), next); |
| } else if (V8File::hasInstance(value, isolate())) { |
| return writeFile(value, next); |
| } else if (V8Blob::hasInstance(value, isolate())) { |
| return writeBlob(value, next); |
| } else if (V8DOMFileSystem::hasInstance(value, isolate())) { |
| return writeDOMFileSystem(value, next); |
| } else if (V8FileList::hasInstance(value, isolate())) { |
| return writeFileList(value, next); |
| } else if (V8CryptoKey::hasInstance(value, isolate())) { |
| if (!writeCryptoKey(value)) |
| return handleError(DataCloneError, "Couldn't serialize key data", next); |
| } else if (V8ImageData::hasInstance(value, isolate())) { |
| writeImageData(value); |
| } else if (value->IsRegExp()) { |
| writeRegExp(value); |
| } else if (V8ArrayBuffer::hasInstance(value, isolate())) { |
| return writeArrayBuffer(value, next); |
| } else if (value->IsObject()) { |
| if (isHostObject(jsObject) || jsObject->IsCallable() || value->IsNativeError()) |
| return handleError(DataCloneError, "An object could not be cloned.", next); |
| return startObjectState(jsObject, next); |
| } else { |
| return handleError(DataCloneError, "A value could not be cloned.", next); |
| } |
| } |
| return 0; |
| } |
| |
| // Interface used by Reader to create objects of composite types. |
| class CompositeCreator { |
| STACK_ALLOCATED(); |
| public: |
| virtual ~CompositeCreator() { } |
| |
| virtual bool consumeTopOfStack(v8::Handle<v8::Value>*) = 0; |
| virtual uint32_t objectReferenceCount() = 0; |
| virtual void pushObjectReference(const v8::Handle<v8::Value>&) = 0; |
| virtual bool tryGetObjectFromObjectReference(uint32_t reference, v8::Handle<v8::Value>*) = 0; |
| virtual bool tryGetTransferredMessagePort(uint32_t index, v8::Handle<v8::Value>*) = 0; |
| virtual bool tryGetTransferredArrayBuffer(uint32_t index, v8::Handle<v8::Value>*) = 0; |
| virtual bool newSparseArray(uint32_t length) = 0; |
| virtual bool newDenseArray(uint32_t length) = 0; |
| virtual bool newObject() = 0; |
| virtual bool completeObject(uint32_t numProperties, v8::Handle<v8::Value>*) = 0; |
| virtual bool completeSparseArray(uint32_t numProperties, uint32_t length, v8::Handle<v8::Value>*) = 0; |
| virtual bool completeDenseArray(uint32_t numProperties, uint32_t length, v8::Handle<v8::Value>*) = 0; |
| }; |
| |
| // Reader is responsible for deserializing primitive types and |
| // restoring information about saved objects of composite types. |
| class Reader { |
| public: |
| Reader(const uint8_t* buffer, int length, const WebBlobInfoArray* blobInfo, BlobDataHandleMap& blobDataHandles, ScriptState* scriptState) |
| : m_scriptState(scriptState) |
| , m_buffer(buffer) |
| , m_length(length) |
| , m_position(0) |
| , m_version(0) |
| , m_blobInfo(blobInfo) |
| , m_blobDataHandles(blobDataHandles) |
| { |
| ASSERT(!(reinterpret_cast<size_t>(buffer) & 1)); |
| ASSERT(length >= 0); |
| } |
| |
| bool isEof() const { return m_position >= m_length; } |
| |
| ScriptState* scriptState() const { return m_scriptState.get(); } |
| |
| private: |
| v8::Isolate* isolate() const { return m_scriptState->isolate(); } |
| |
| public: |
| bool read(v8::Handle<v8::Value>* value, CompositeCreator& creator) |
| { |
| SerializationTag tag; |
| if (!readTag(&tag)) |
| return false; |
| switch (tag) { |
| case ReferenceCountTag: { |
| if (!m_version) |
| return false; |
| uint32_t referenceTableSize; |
| if (!doReadUint32(&referenceTableSize)) |
| return false; |
| // If this test fails, then the serializer and deserializer disagree about the assignment |
| // of object reference IDs. On the deserialization side, this means there are too many or too few |
| // calls to pushObjectReference. |
| if (referenceTableSize != creator.objectReferenceCount()) |
| return false; |
| return true; |
| } |
| case InvalidTag: |
| return false; |
| case PaddingTag: |
| return true; |
| case UndefinedTag: |
| *value = v8::Undefined(isolate()); |
| break; |
| case NullTag: |
| *value = v8::Null(isolate()); |
| break; |
| case TrueTag: |
| *value = v8Boolean(true, isolate()); |
| break; |
| case FalseTag: |
| *value = v8Boolean(false, isolate()); |
| break; |
| case TrueObjectTag: |
| *value = v8::BooleanObject::New(true); |
| creator.pushObjectReference(*value); |
| break; |
| case FalseObjectTag: |
| *value = v8::BooleanObject::New(false); |
| creator.pushObjectReference(*value); |
| break; |
| case StringTag: |
| if (!readString(value)) |
| return false; |
| break; |
| case StringUCharTag: |
| if (!readUCharString(value)) |
| return false; |
| break; |
| case StringObjectTag: |
| if (!readStringObject(value)) |
| return false; |
| creator.pushObjectReference(*value); |
| break; |
| case Int32Tag: |
| if (!readInt32(value)) |
| return false; |
| break; |
| case Uint32Tag: |
| if (!readUint32(value)) |
| return false; |
| break; |
| case DateTag: |
| if (!readDate(value)) |
| return false; |
| creator.pushObjectReference(*value); |
| break; |
| case NumberTag: |
| if (!readNumber(value)) |
| return false; |
| break; |
| case NumberObjectTag: |
| if (!readNumberObject(value)) |
| return false; |
| creator.pushObjectReference(*value); |
| break; |
| case BlobTag: |
| case BlobIndexTag: |
| if (!readBlob(value, tag == BlobIndexTag)) |
| return false; |
| creator.pushObjectReference(*value); |
| break; |
| case FileTag: |
| case FileIndexTag: |
| if (!readFile(value, tag == FileIndexTag)) |
| return false; |
| creator.pushObjectReference(*value); |
| break; |
| case DOMFileSystemTag: |
| if (!readDOMFileSystem(value)) |
| return false; |
| creator.pushObjectReference(*value); |
| break; |
| case FileListTag: |
| case FileListIndexTag: |
| if (!readFileList(value, tag == FileListIndexTag)) |
| return false; |
| creator.pushObjectReference(*value); |
| break; |
| case CryptoKeyTag: |
| if (!readCryptoKey(value)) |
| return false; |
| creator.pushObjectReference(*value); |
| break; |
| case ImageDataTag: |
| if (!readImageData(value)) |
| return false; |
| creator.pushObjectReference(*value); |
| break; |
| |
| case RegExpTag: |
| if (!readRegExp(value)) |
| return false; |
| creator.pushObjectReference(*value); |
| break; |
| case ObjectTag: { |
| uint32_t numProperties; |
| if (!doReadUint32(&numProperties)) |
| return false; |
| if (!creator.completeObject(numProperties, value)) |
| return false; |
| break; |
| } |
| case SparseArrayTag: { |
| uint32_t numProperties; |
| uint32_t length; |
| if (!doReadUint32(&numProperties)) |
| return false; |
| if (!doReadUint32(&length)) |
| return false; |
| if (!creator.completeSparseArray(numProperties, length, value)) |
| return false; |
| break; |
| } |
| case DenseArrayTag: { |
| uint32_t numProperties; |
| uint32_t length; |
| if (!doReadUint32(&numProperties)) |
| return false; |
| if (!doReadUint32(&length)) |
| return false; |
| if (!creator.completeDenseArray(numProperties, length, value)) |
| return false; |
| break; |
| } |
| case ArrayBufferViewTag: { |
| if (!m_version) |
| return false; |
| if (!readArrayBufferView(value, creator)) |
| return false; |
| creator.pushObjectReference(*value); |
| break; |
| } |
| case ArrayBufferTag: { |
| if (!m_version) |
| return false; |
| if (!readArrayBuffer(value)) |
| return false; |
| creator.pushObjectReference(*value); |
| break; |
| } |
| case GenerateFreshObjectTag: { |
| if (!m_version) |
| return false; |
| if (!creator.newObject()) |
| return false; |
| return true; |
| } |
| case GenerateFreshSparseArrayTag: { |
| if (!m_version) |
| return false; |
| uint32_t length; |
| if (!doReadUint32(&length)) |
| return false; |
| if (!creator.newSparseArray(length)) |
| return false; |
| return true; |
| } |
| case GenerateFreshDenseArrayTag: { |
| if (!m_version) |
| return false; |
| uint32_t length; |
| if (!doReadUint32(&length)) |
| return false; |
| if (!creator.newDenseArray(length)) |
| return false; |
| return true; |
| } |
| case MessagePortTag: { |
| if (!m_version) |
| return false; |
| uint32_t index; |
| if (!doReadUint32(&index)) |
| return false; |
| if (!creator.tryGetTransferredMessagePort(index, value)) |
| return false; |
| break; |
| } |
| case ArrayBufferTransferTag: { |
| if (!m_version) |
| return false; |
| uint32_t index; |
| if (!doReadUint32(&index)) |
| return false; |
| if (!creator.tryGetTransferredArrayBuffer(index, value)) |
| return false; |
| break; |
| } |
| case ObjectReferenceTag: { |
| if (!m_version) |
| return false; |
| uint32_t reference; |
| if (!doReadUint32(&reference)) |
| return false; |
| if (!creator.tryGetObjectFromObjectReference(reference, value)) |
| return false; |
| break; |
| } |
| default: |
| return false; |
| } |
| return !value->IsEmpty(); |
| } |
| |
| bool readVersion(uint32_t& version) |
| { |
| SerializationTag tag; |
| if (!readTag(&tag)) { |
| // This is a nullary buffer. We're still version 0. |
| version = 0; |
| return true; |
| } |
| if (tag != VersionTag) { |
| // Versions of the format past 0 start with the version tag. |
| version = 0; |
| // Put back the tag. |
| undoReadTag(); |
| return true; |
| } |
| // Version-bearing messages are obligated to finish the version tag. |
| return doReadUint32(&version); |
| } |
| |
| void setVersion(uint32_t version) |
| { |
| m_version = version; |
| } |
| |
| private: |
| bool readTag(SerializationTag* tag) |
| { |
| if (m_position >= m_length) |
| return false; |
| *tag = static_cast<SerializationTag>(m_buffer[m_position++]); |
| return true; |
| } |
| |
| void undoReadTag() |
| { |
| if (m_position > 0) |
| --m_position; |
| } |
| |
| bool readArrayBufferViewSubTag(ArrayBufferViewSubTag* tag) |
| { |
| if (m_position >= m_length) |
| return false; |
| *tag = static_cast<ArrayBufferViewSubTag>(m_buffer[m_position++]); |
| return true; |
| } |
| |
| bool readString(v8::Handle<v8::Value>* value) |
| { |
| uint32_t length; |
| if (!doReadUint32(&length)) |
| return false; |
| if (m_position + length > m_length) |
| return false; |
| *value = v8::String::NewFromUtf8(isolate(), reinterpret_cast<const char*>(m_buffer + m_position), v8::String::kNormalString, length); |
| m_position += length; |
| return true; |
| } |
| |
| bool readUCharString(v8::Handle<v8::Value>* value) |
| { |
| uint32_t length; |
| if (!doReadUint32(&length) || (length & 1)) |
| return false; |
| if (m_position + length > m_length) |
| return false; |
| ASSERT(!(m_position & 1)); |
| *value = v8::String::NewFromTwoByte(isolate(), reinterpret_cast<const uint16_t*>(m_buffer + m_position), v8::String::kNormalString, length / sizeof(UChar)); |
| m_position += length; |
| return true; |
| } |
| |
| bool readStringObject(v8::Handle<v8::Value>* value) |
| { |
| v8::Handle<v8::Value> stringValue; |
| if (!readString(&stringValue) || !stringValue->IsString()) |
| return false; |
| *value = v8::StringObject::New(stringValue.As<v8::String>()); |
| return true; |
| } |
| |
| bool readWebCoreString(String* string) |
| { |
| uint32_t length; |
| if (!doReadUint32(&length)) |
| return false; |
| if (m_position + length > m_length) |
| return false; |
| *string = String::fromUTF8(reinterpret_cast<const char*>(m_buffer + m_position), length); |
| m_position += length; |
| return true; |
| } |
| |
| bool readInt32(v8::Handle<v8::Value>* value) |
| { |
| uint32_t rawValue; |
| if (!doReadUint32(&rawValue)) |
| return false; |
| *value = v8::Integer::New(isolate(), static_cast<int32_t>(ZigZag::decode(rawValue))); |
| return true; |
| } |
| |
| bool readUint32(v8::Handle<v8::Value>* value) |
| { |
| uint32_t rawValue; |
| if (!doReadUint32(&rawValue)) |
| return false; |
| *value = v8::Integer::NewFromUnsigned(isolate(), rawValue); |
| return true; |
| } |
| |
| bool readDate(v8::Handle<v8::Value>* value) |
| { |
| double numberValue; |
| if (!doReadNumber(&numberValue)) |
| return false; |
| *value = v8DateOrNaN(numberValue, isolate()); |
| return true; |
| } |
| |
| bool readNumber(v8::Handle<v8::Value>* value) |
| { |
| double number; |
| if (!doReadNumber(&number)) |
| return false; |
| *value = v8::Number::New(isolate(), number); |
| return true; |
| } |
| |
| bool readNumberObject(v8::Handle<v8::Value>* value) |
| { |
| double number; |
| if (!doReadNumber(&number)) |
| return false; |
| *value = v8::NumberObject::New(isolate(), number); |
| return true; |
| } |
| |
| bool readImageData(v8::Handle<v8::Value>* value) |
| { |
| uint32_t width; |
| uint32_t height; |
| uint32_t pixelDataLength; |
| if (!doReadUint32(&width)) |
| return false; |
| if (!doReadUint32(&height)) |
| return false; |
| if (!doReadUint32(&pixelDataLength)) |
| return false; |
| if (m_position + pixelDataLength > m_length) |
| return false; |
| RefPtrWillBeRawPtr<ImageData> imageData = ImageData::create(IntSize(width, height)); |
| Uint8ClampedArray* pixelArray = imageData->data(); |
| ASSERT(pixelArray); |
| ASSERT(pixelArray->length() >= pixelDataLength); |
| memcpy(pixelArray->data(), m_buffer + m_position, pixelDataLength); |
| m_position += pixelDataLength; |
| *value = toV8(imageData.release(), m_scriptState->context()->Global(), isolate()); |
| return true; |
| } |
| |
| PassRefPtr<ArrayBuffer> doReadArrayBuffer() |
| { |
| uint32_t byteLength; |
| if (!doReadUint32(&byteLength)) |
| return nullptr; |
| if (m_position + byteLength > m_length) |
| return nullptr; |
| const void* bufferStart = m_buffer + m_position; |
| m_position += byteLength; |
| return ArrayBuffer::create(bufferStart, byteLength); |
| } |
| |
| bool readArrayBuffer(v8::Handle<v8::Value>* value) |
| { |
| RefPtr<ArrayBuffer> arrayBuffer = doReadArrayBuffer(); |
| if (!arrayBuffer) |
| return false; |
| *value = toV8(DOMArrayBuffer::create(arrayBuffer.release()), m_scriptState->context()->Global(), isolate()); |
| return true; |
| } |
| |
| bool readArrayBufferView(v8::Handle<v8::Value>* value, CompositeCreator& creator) |
| { |
| ArrayBufferViewSubTag subTag; |
| uint32_t byteOffset; |
| uint32_t byteLength; |
| RefPtr<DOMArrayBuffer> arrayBuffer; |
| v8::Handle<v8::Value> arrayBufferV8Value; |
| if (!readArrayBufferViewSubTag(&subTag)) |
| return false; |
| if (!doReadUint32(&byteOffset)) |
| return false; |
| if (!doReadUint32(&byteLength)) |
| return false; |
| if (!creator.consumeTopOfStack(&arrayBufferV8Value)) |
| return false; |
| if (arrayBufferV8Value.IsEmpty()) |
| return false; |
| arrayBuffer = V8ArrayBuffer::toImpl(arrayBufferV8Value.As<v8::Object>()); |
| if (!arrayBuffer) |
| return false; |
| |
| v8::Handle<v8::Object> creationContext = m_scriptState->context()->Global(); |
| switch (subTag) { |
| case ByteArrayTag: |
| *value = toV8(DOMInt8Array::create(arrayBuffer.release(), byteOffset, byteLength), creationContext, isolate()); |
| break; |
| case UnsignedByteArrayTag: |
| *value = toV8(DOMUint8Array::create(arrayBuffer.release(), byteOffset, byteLength), creationContext, isolate()); |
| break; |
| case UnsignedByteClampedArrayTag: |
| *value = toV8(DOMUint8ClampedArray::create(arrayBuffer.release(), byteOffset, byteLength), creationContext, isolate()); |
| break; |
| case ShortArrayTag: { |
| uint32_t shortLength = byteLength / sizeof(int16_t); |
| if (shortLength * sizeof(int16_t) != byteLength) |
| return false; |
| *value = toV8(DOMInt16Array::create(arrayBuffer.release(), byteOffset, shortLength), creationContext, isolate()); |
| break; |
| } |
| case UnsignedShortArrayTag: { |
| uint32_t shortLength = byteLength / sizeof(uint16_t); |
| if (shortLength * sizeof(uint16_t) != byteLength) |
| return false; |
| *value = toV8(DOMUint16Array::create(arrayBuffer.release(), byteOffset, shortLength), creationContext, isolate()); |
| break; |
| } |
| case IntArrayTag: { |
| uint32_t intLength = byteLength / sizeof(int32_t); |
| if (intLength * sizeof(int32_t) != byteLength) |
| return false; |
| *value = toV8(DOMInt32Array::create(arrayBuffer.release(), byteOffset, intLength), creationContext, isolate()); |
| break; |
| } |
| case UnsignedIntArrayTag: { |
| uint32_t intLength = byteLength / sizeof(uint32_t); |
| if (intLength * sizeof(uint32_t) != byteLength) |
| return false; |
| *value = toV8(DOMUint32Array::create(arrayBuffer.release(), byteOffset, intLength), creationContext, isolate()); |
| break; |
| } |
| case FloatArrayTag: { |
| uint32_t floatLength = byteLength / sizeof(float); |
| if (floatLength * sizeof(float) != byteLength) |
| return false; |
| *value = toV8(DOMFloat32Array::create(arrayBuffer.release(), byteOffset, floatLength), creationContext, isolate()); |
| break; |
| } |
| case DoubleArrayTag: { |
| uint32_t floatLength = byteLength / sizeof(double); |
| if (floatLength * sizeof(double) != byteLength) |
| return false; |
| *value = toV8(DOMFloat64Array::create(arrayBuffer.release(), byteOffset, floatLength), creationContext, isolate()); |
| break; |
| } |
| case DataViewTag: |
| *value = toV8(DOMDataView::create(arrayBuffer.release(), byteOffset, byteLength), creationContext, isolate()); |
| break; |
| default: |
| return false; |
| } |
| // The various *Array::create() methods will return null if the range the view expects is |
| // mismatched with the range the buffer can provide or if the byte offset is not aligned |
| // to the size of the element type. |
| return !value->IsEmpty(); |
| } |
| |
| bool readRegExp(v8::Handle<v8::Value>* value) |
| { |
| v8::Handle<v8::Value> pattern; |
| if (!readString(&pattern)) |
| return false; |
| uint32_t flags; |
| if (!doReadUint32(&flags)) |
| return false; |
| *value = v8::RegExp::New(pattern.As<v8::String>(), static_cast<v8::RegExp::Flags>(flags)); |
| return true; |
| } |
| |
| bool readBlob(v8::Handle<v8::Value>* value, bool isIndexed) |
| { |
| if (m_version < 3) |
| return false; |
| Blob* blob = nullptr; |
| if (isIndexed) { |
| if (m_version < 6) |
| return false; |
| ASSERT(m_blobInfo); |
| uint32_t index; |
| if (!doReadUint32(&index) || index >= m_blobInfo->size()) |
| return false; |
| const blink::WebBlobInfo& info = (*m_blobInfo)[index]; |
| blob = Blob::create(getOrCreateBlobDataHandle(info.uuid(), info.type(), info.size())); |
| } else { |
| ASSERT(!m_blobInfo); |
| String uuid; |
| String type; |
| uint64_t size; |
| ASSERT(!m_blobInfo); |
| if (!readWebCoreString(&uuid)) |
| return false; |
| if (!readWebCoreString(&type)) |
| return false; |
| if (!doReadUint64(&size)) |
| return false; |
| blob = Blob::create(getOrCreateBlobDataHandle(uuid, type, size)); |
| } |
| *value = toV8(blob, m_scriptState->context()->Global(), isolate()); |
| return true; |
| } |
| |
| bool readDOMFileSystem(v8::Handle<v8::Value>* value) |
| { |
| uint32_t type; |
| String name; |
| String url; |
| if (!doReadUint32(&type)) |
| return false; |
| if (!readWebCoreString(&name)) |
| return false; |
| if (!readWebCoreString(&url)) |
| return false; |
| DOMFileSystem* fs = DOMFileSystem::create(m_scriptState->executionContext(), name, static_cast<blink::FileSystemType>(type), KURL(ParsedURLString, url)); |
| *value = toV8(fs, m_scriptState->context()->Global(), isolate()); |
| return true; |
| } |
| |
| bool readFile(v8::Handle<v8::Value>* value, bool isIndexed) |
| { |
| File* file = nullptr; |
| if (isIndexed) { |
| if (m_version < 6) |
| return false; |
| file = readFileIndexHelper(); |
| } else { |
| file = readFileHelper(); |
| } |
| if (!file) |
| return false; |
| *value = toV8(file, m_scriptState->context()->Global(), isolate()); |
| return true; |
| } |
| |
| bool readFileList(v8::Handle<v8::Value>* value, bool isIndexed) |
| { |
| if (m_version < 3) |
| return false; |
| uint32_t length; |
| if (!doReadUint32(&length)) |
| return false; |
| FileList* fileList = FileList::create(); |
| for (unsigned i = 0; i < length; ++i) { |
| File* file = nullptr; |
| if (isIndexed) { |
| if (m_version < 6) |
| return false; |
| file = readFileIndexHelper(); |
| } else { |
| file = readFileHelper(); |
| } |
| if (!file) |
| return false; |
| fileList->append(file); |
| } |
| *value = toV8(fileList, m_scriptState->context()->Global(), isolate()); |
| return true; |
| } |
| |
| bool readCryptoKey(v8::Handle<v8::Value>* value) |
| { |
| uint32_t rawKeyType; |
| if (!doReadUint32(&rawKeyType)) |
| return false; |
| |
| blink::WebCryptoKeyAlgorithm algorithm; |
| blink::WebCryptoKeyType type = blink::WebCryptoKeyTypeSecret; |
| |
| switch (static_cast<CryptoKeySubTag>(rawKeyType)) { |
| case AesKeyTag: |
| if (!doReadAesKey(algorithm, type)) |
| return false; |
| break; |
| case HmacKeyTag: |
| if (!doReadHmacKey(algorithm, type)) |
| return false; |
| break; |
| case RsaHashedKeyTag: |
| if (!doReadRsaHashedKey(algorithm, type)) |
| return false; |
| break; |
| default: |
| return false; |
| } |
| |
| blink::WebCryptoKeyUsageMask usages; |
| bool extractable; |
| if (!doReadKeyUsages(usages, extractable)) |
| return false; |
| |
| uint32_t keyDataLength; |
| if (!doReadUint32(&keyDataLength)) |
| return false; |
| |
| if (m_position + keyDataLength > m_length) |
| return false; |
| |
| const uint8_t* keyData = m_buffer + m_position; |
| m_position += keyDataLength; |
| |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| if (!blink::Platform::current()->crypto()->deserializeKeyForClone( |
| algorithm, type, extractable, usages, keyData, keyDataLength, key)) { |
| return false; |
| } |
| |
| *value = toV8(CryptoKey::create(key), m_scriptState->context()->Global(), isolate()); |
| return true; |
| } |
| |
| File* readFileHelper() |
| { |
| if (m_version < 3) |
| return nullptr; |
| ASSERT(!m_blobInfo); |
| String path; |
| String name; |
| String relativePath; |
| String uuid; |
| String type; |
| uint32_t hasSnapshot = 0; |
| uint64_t size = 0; |
| double lastModified = 0; |
| if (!readWebCoreString(&path)) |
| return nullptr; |
| if (m_version >= 4 && !readWebCoreString(&name)) |
| return nullptr; |
| if (m_version >= 4 && !readWebCoreString(&relativePath)) |
| return nullptr; |
| if (!readWebCoreString(&uuid)) |
| return nullptr; |
| if (!readWebCoreString(&type)) |
| return nullptr; |
| if (m_version >= 4 && !doReadUint32(&hasSnapshot)) |
| return nullptr; |
| if (hasSnapshot) { |
| if (!doReadUint64(&size)) |
| return nullptr; |
| if (!doReadNumber(&lastModified)) |
| return nullptr; |
| } |
| uint32_t isUserVisible = 1; |
| if (m_version >= 7 && !doReadUint32(&isUserVisible)) |
| return nullptr; |
| const File::UserVisibility userVisibility = (isUserVisible > 0) ? File::IsUserVisible : File::IsNotUserVisible; |
| return File::createFromSerialization(path, name, relativePath, userVisibility, hasSnapshot > 0, size, lastModified, getOrCreateBlobDataHandle(uuid, type)); |
| } |
| |
| File* readFileIndexHelper() |
| { |
| if (m_version < 3) |
| return nullptr; |
| ASSERT(m_blobInfo); |
| uint32_t index; |
| if (!doReadUint32(&index) || index >= m_blobInfo->size()) |
| return nullptr; |
| const WebBlobInfo& info = (*m_blobInfo)[index]; |
| return File::createFromIndexedSerialization(info.filePath(), info.fileName(), info.size(), info.lastModified(), getOrCreateBlobDataHandle(info.uuid(), info.type(), info.size())); |
| } |
| |
| template<class T> |
| bool doReadUintHelper(T* value) |
| { |
| *value = 0; |
| uint8_t currentByte; |
| int shift = 0; |
| do { |
| if (m_position >= m_length) |
| return false; |
| currentByte = m_buffer[m_position++]; |
| *value |= ((currentByte & varIntMask) << shift); |
| shift += varIntShift; |
| } while (currentByte & (1 << varIntShift)); |
| return true; |
| } |
| |
| bool doReadUint32(uint32_t* value) |
| { |
| return doReadUintHelper(value); |
| } |
| |
| bool doReadUint64(uint64_t* value) |
| { |
| return doReadUintHelper(value); |
| } |
| |
| bool doReadNumber(double* number) |
| { |
| if (m_position + sizeof(double) > m_length) |
| return false; |
| uint8_t* numberAsByteArray = reinterpret_cast<uint8_t*>(number); |
| for (unsigned i = 0; i < sizeof(double); ++i) |
| numberAsByteArray[i] = m_buffer[m_position++]; |
| return true; |
| } |
| |
| PassRefPtr<BlobDataHandle> getOrCreateBlobDataHandle(const String& uuid, const String& type, long long size = -1) |
| { |
| // The containing ssv may have a BDH for this uuid if this ssv is just being |
| // passed from main to worker thread (for example). We use those values when creating |
| // the new blob instead of cons'ing up a new BDH. |
| // |
| // FIXME: Maybe we should require that it work that way where the ssv must have a BDH for any |
| // blobs it comes across during deserialization. Would require callers to explicitly populate |
| // the collection of BDH's for blobs to work, which would encourage lifetimes to be considered |
| // when passing ssv's around cross process. At present, we get 'lucky' in some cases because |
| // the blob in the src process happens to still exist at the time the dest process is deserializing. |
| // For example in sharedWorker.postMessage(...). |
| BlobDataHandleMap::const_iterator it = m_blobDataHandles.find(uuid); |
| if (it != m_blobDataHandles.end()) { |
| // make assertions about type and size? |
| return it->value; |
| } |
| return BlobDataHandle::create(uuid, type, size); |
| } |
| |
| bool doReadHmacKey(blink::WebCryptoKeyAlgorithm& algorithm, blink::WebCryptoKeyType& type) |
| { |
| uint32_t lengthBytes; |
| if (!doReadUint32(&lengthBytes)) |
| return false; |
| blink::WebCryptoAlgorithmId hash; |
| if (!doReadAlgorithmId(hash)) |
| return false; |
| algorithm = blink::WebCryptoKeyAlgorithm::createHmac(hash, lengthBytes * 8); |
| type = blink::WebCryptoKeyTypeSecret; |
| return !algorithm.isNull(); |
| } |
| |
| bool doReadAesKey(blink::WebCryptoKeyAlgorithm& algorithm, blink::WebCryptoKeyType& type) |
| { |
| blink::WebCryptoAlgorithmId id; |
| if (!doReadAlgorithmId(id)) |
| return false; |
| uint32_t lengthBytes; |
| if (!doReadUint32(&lengthBytes)) |
| return false; |
| algorithm = blink::WebCryptoKeyAlgorithm::createAes(id, lengthBytes * 8); |
| type = blink::WebCryptoKeyTypeSecret; |
| return !algorithm.isNull(); |
| } |
| |
| bool doReadRsaHashedKey(blink::WebCryptoKeyAlgorithm& algorithm, blink::WebCryptoKeyType& type) |
| { |
| blink::WebCryptoAlgorithmId id; |
| if (!doReadAlgorithmId(id)) |
| return false; |
| |
| uint32_t rawType; |
| if (!doReadUint32(&rawType)) |
| return false; |
| |
| switch (static_cast<AssymetricCryptoKeyType>(rawType)) { |
| case PublicKeyType: |
| type = blink::WebCryptoKeyTypePublic; |
| break; |
| case PrivateKeyType: |
| type = blink::WebCryptoKeyTypePrivate; |
| break; |
| default: |
| return false; |
| } |
| |
| uint32_t modulusLengthBits; |
| if (!doReadUint32(&modulusLengthBits)) |
| return false; |
| |
| uint32_t publicExponentSize; |
| if (!doReadUint32(&publicExponentSize)) |
| return false; |
| |
| if (m_position + publicExponentSize > m_length) |
| return false; |
| |
| const uint8_t* publicExponent = m_buffer + m_position; |
| m_position += publicExponentSize; |
| |
| blink::WebCryptoAlgorithmId hash; |
| if (!doReadAlgorithmId(hash)) |
| return false; |
| algorithm = blink::WebCryptoKeyAlgorithm::createRsaHashed(id, modulusLengthBits, publicExponent, publicExponentSize, hash); |
| |
| return !algorithm.isNull(); |
| } |
| |
| bool doReadAlgorithmId(blink::WebCryptoAlgorithmId& id) |
| { |
| uint32_t rawId; |
| if (!doReadUint32(&rawId)) |
| return false; |
| |
| switch (static_cast<CryptoKeyAlgorithmTag>(rawId)) { |
| case AesCbcTag: |
| id = blink::WebCryptoAlgorithmIdAesCbc; |
| return true; |
| case HmacTag: |
| id = blink::WebCryptoAlgorithmIdHmac; |
| return true; |
| case RsaSsaPkcs1v1_5Tag: |
| id = blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5; |
| return true; |
| case Sha1Tag: |
| id = blink::WebCryptoAlgorithmIdSha1; |
| return true; |
| case Sha256Tag: |
| id = blink::WebCryptoAlgorithmIdSha256; |
| return true; |
| case Sha384Tag: |
| id = blink::WebCryptoAlgorithmIdSha384; |
| return true; |
| case Sha512Tag: |
| id = blink::WebCryptoAlgorithmIdSha512; |
| return true; |
| case AesGcmTag: |
| id = blink::WebCryptoAlgorithmIdAesGcm; |
| return true; |
| case RsaOaepTag: |
| id = blink::WebCryptoAlgorithmIdRsaOaep; |
| return true; |
| case AesCtrTag: |
| id = blink::WebCryptoAlgorithmIdAesCtr; |
| return true; |
| case AesKwTag: |
| id = blink::WebCryptoAlgorithmIdAesKw; |
| return true; |
| case RsaPssTag: |
| id = blink::WebCryptoAlgorithmIdRsaPss; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool doReadKeyUsages(blink::WebCryptoKeyUsageMask& usages, bool& extractable) |
| { |
| // Reminder to update this when adding new key usages. |
| COMPILE_ASSERT(blink::EndOfWebCryptoKeyUsage == (1 << 7) + 1, UpdateMe); |
| const uint32_t allPossibleUsages = ExtractableUsage | EncryptUsage | DecryptUsage | SignUsage | VerifyUsage | DeriveKeyUsage | WrapKeyUsage | UnwrapKeyUsage | DeriveBitsUsage; |
| |
| uint32_t rawUsages; |
| if (!doReadUint32(&rawUsages)) |
| return false; |
| |
| // Make sure it doesn't contain an unrecognized usage value. |
| if (rawUsages & ~allPossibleUsages) |
| return false; |
| |
| usages = 0; |
| |
| extractable = rawUsages & ExtractableUsage; |
| |
| if (rawUsages & EncryptUsage) |
| usages |= blink::WebCryptoKeyUsageEncrypt; |
| if (rawUsages & DecryptUsage) |
| usages |= blink::WebCryptoKeyUsageDecrypt; |
| if (rawUsages & SignUsage) |
| usages |= blink::WebCryptoKeyUsageSign; |
| if (rawUsages & VerifyUsage) |
| usages |= blink::WebCryptoKeyUsageVerify; |
| if (rawUsages & DeriveKeyUsage) |
| usages |= blink::WebCryptoKeyUsageDeriveKey; |
| if (rawUsages & WrapKeyUsage) |
| usages |= blink::WebCryptoKeyUsageWrapKey; |
| if (rawUsages & UnwrapKeyUsage) |
| usages |= blink::WebCryptoKeyUsageUnwrapKey; |
| if (rawUsages & DeriveBitsUsage) |
| usages |= blink::WebCryptoKeyUsageDeriveBits; |
| |
| return true; |
| } |
| |
| RefPtr<ScriptState> m_scriptState; |
| const uint8_t* m_buffer; |
| const unsigned m_length; |
| unsigned m_position; |
| uint32_t m_version; |
| const WebBlobInfoArray* m_blobInfo; |
| const BlobDataHandleMap& m_blobDataHandles; |
| }; |
| |
| |
| typedef Vector<WTF::ArrayBufferContents, 1> ArrayBufferContentsArray; |
| |
| class Deserializer final : public CompositeCreator { |
| public: |
| Deserializer(Reader& reader, MessagePortArray* messagePorts, ArrayBufferContentsArray* arrayBufferContents) |
| : m_reader(reader) |
| , m_transferredMessagePorts(messagePorts) |
| , m_arrayBufferContents(arrayBufferContents) |
| , m_arrayBuffers(arrayBufferContents ? arrayBufferContents->size() : 0) |
| , m_version(0) |
| { |
| } |
| |
| v8::Handle<v8::Value> deserialize() |
| { |
| v8::Isolate* isolate = m_reader.scriptState()->isolate(); |
| if (!m_reader.readVersion(m_version) || m_version > SerializedScriptValue::wireFormatVersion) |
| return v8::Null(isolate); |
| m_reader.setVersion(m_version); |
| v8::EscapableHandleScope scope(isolate); |
| while (!m_reader.isEof()) { |
| if (!doDeserialize()) |
| return v8::Null(isolate); |
| } |
| if (stackDepth() != 1 || m_openCompositeReferenceStack.size()) |
| return v8::Null(isolate); |
| v8::Handle<v8::Value> result = scope.Escape(element(0)); |
| return result; |
| } |
| |
| virtual bool newSparseArray(uint32_t) override |
| { |
| v8::Local<v8::Array> array = v8::Array::New(m_reader.scriptState()->isolate(), 0); |
| openComposite(array); |
| return true; |
| } |
| |
| virtual bool newDenseArray(uint32_t length) override |
| { |
| v8::Local<v8::Array> array = v8::Array::New(m_reader.scriptState()->isolate(), length); |
| openComposite(array); |
| return true; |
| } |
| |
| virtual bool consumeTopOfStack(v8::Handle<v8::Value>* object) override |
| { |
| if (stackDepth() < 1) |
| return false; |
| *object = element(stackDepth() - 1); |
| pop(1); |
| return true; |
| } |
| |
| virtual bool newObject() override |
| { |
| v8::Local<v8::Object> object = v8::Object::New(m_reader.scriptState()->isolate()); |
| if (object.IsEmpty()) |
| return false; |
| openComposite(object); |
| return true; |
| } |
| |
| virtual bool completeObject(uint32_t numProperties, v8::Handle<v8::Value>* value) override |
| { |
| v8::Local<v8::Object> object; |
| if (m_version > 0) { |
| v8::Local<v8::Value> composite; |
| if (!closeComposite(&composite)) |
| return false; |
| object = composite.As<v8::Object>(); |
| } else { |
| object = v8::Object::New(m_reader.scriptState()->isolate()); |
| } |
| if (object.IsEmpty()) |
| return false; |
| return initializeObject(object, numProperties, value); |
| } |
| |
| virtual bool completeSparseArray(uint32_t numProperties, uint32_t length, v8::Handle<v8::Value>* value) override |
| { |
| v8::Local<v8::Array> array; |
| if (m_version > 0) { |
| v8::Local<v8::Value> composite; |
| if (!closeComposite(&composite)) |
| return false; |
| array = composite.As<v8::Array>(); |
| } else { |
| array = v8::Array::New(m_reader.scriptState()->isolate()); |
| } |
| if (array.IsEmpty()) |
| return false; |
| return initializeObject(array, numProperties, value); |
| } |
| |
| virtual bool completeDenseArray(uint32_t numProperties, uint32_t length, v8::Handle<v8::Value>* value) override |
| { |
| v8::Local<v8::Array> array; |
| if (m_version > 0) { |
| v8::Local<v8::Value> composite; |
| if (!closeComposite(&composite)) |
| return false; |
| array = composite.As<v8::Array>(); |
| } |
| if (array.IsEmpty()) |
| return false; |
| if (!initializeObject(array, numProperties, value)) |
| return false; |
| if (length > stackDepth()) |
| return false; |
| for (unsigned i = 0, stackPos = stackDepth() - length; i < length; i++, stackPos++) { |
| v8::Local<v8::Value> elem = element(stackPos); |
| if (!elem->IsUndefined()) |
| array->Set(i, elem); |
| } |
| pop(length); |
| return true; |
| } |
| |
| virtual void pushObjectReference(const v8::Handle<v8::Value>& object) override |
| { |
| m_objectPool.append(object); |
| } |
| |
| virtual bool tryGetTransferredMessagePort(uint32_t index, v8::Handle<v8::Value>* object) override |
| { |
| if (!m_transferredMessagePorts) |
| return false; |
| if (index >= m_transferredMessagePorts->size()) |
| return false; |
| v8::Handle<v8::Object> creationContext = m_reader.scriptState()->context()->Global(); |
| *object = toV8(m_transferredMessagePorts->at(index).get(), creationContext, m_reader.scriptState()->isolate()); |
| return true; |
| } |
| |
| virtual bool tryGetTransferredArrayBuffer(uint32_t index, v8::Handle<v8::Value>* object) override |
| { |
| if (!m_arrayBufferContents) |
| return false; |
| if (index >= m_arrayBuffers.size()) |
| return false; |
| v8::Handle<v8::Object> result = m_arrayBuffers.at(index); |
| if (result.IsEmpty()) { |
| RefPtr<DOMArrayBuffer> buffer = DOMArrayBuffer::create(m_arrayBufferContents->at(index)); |
| v8::Isolate* isolate = m_reader.scriptState()->isolate(); |
| v8::Handle<v8::Object> creationContext = m_reader.scriptState()->context()->Global(); |
| result = toV8Object(buffer.get(), creationContext, isolate); |
| m_arrayBuffers[index] = result; |
| } |
| *object = result; |
| return true; |
| } |
| |
| virtual bool tryGetObjectFromObjectReference(uint32_t reference, v8::Handle<v8::Value>* object) override |
| { |
| if (reference >= m_objectPool.size()) |
| return false; |
| *object = m_objectPool[reference]; |
| return object; |
| } |
| |
| virtual uint32_t objectReferenceCount() override |
| { |
| return m_objectPool.size(); |
| } |
| |
| private: |
| bool initializeObject(v8::Handle<v8::Object> object, uint32_t numProperties, v8::Handle<v8::Value>* value) |
| { |
| unsigned length = 2 * numProperties; |
| if (length > stackDepth()) |
| return false; |
| for (unsigned i = stackDepth() - length; i < stackDepth(); i += 2) { |
| v8::Local<v8::Value> propertyName = element(i); |
| v8::Local<v8::Value> propertyValue = element(i + 1); |
| object->Set(propertyName, propertyValue); |
| } |
| pop(length); |
| *value = object; |
| return true; |
| } |
| |
| bool doDeserialize() |
| { |
| v8::Local<v8::Value> value; |
| if (!m_reader.read(&value, *this)) |
| return false; |
| if (!value.IsEmpty()) |
| push(value); |
| return true; |
| } |
| |
| void push(v8::Local<v8::Value> value) { m_stack.append(value); } |
| |
| void pop(unsigned length) |
| { |
| ASSERT(length <= m_stack.size()); |
| m_stack.shrink(m_stack.size() - length); |
| } |
| |
| unsigned stackDepth() const { return m_stack.size(); } |
| |
| v8::Local<v8::Value> element(unsigned index) |
| { |
| ASSERT_WITH_SECURITY_IMPLICATION(index < m_stack.size()); |
| return m_stack[index]; |
| } |
| |
| void openComposite(const v8::Local<v8::Value>& object) |
| { |
| uint32_t newObjectReference = m_objectPool.size(); |
| m_openCompositeReferenceStack.append(newObjectReference); |
| m_objectPool.append(object); |
| } |
| |
| bool closeComposite(v8::Handle<v8::Value>* object) |
| { |
| if (!m_openCompositeReferenceStack.size()) |
| return false; |
| uint32_t objectReference = m_openCompositeReferenceStack[m_openCompositeReferenceStack.size() - 1]; |
| m_openCompositeReferenceStack.shrink(m_openCompositeReferenceStack.size() - 1); |
| if (objectReference >= m_objectPool.size()) |
| return false; |
| *object = m_objectPool[objectReference]; |
| return true; |
| } |
| |
| Reader& m_reader; |
| Vector<v8::Local<v8::Value> > m_stack; |
| Vector<v8::Handle<v8::Value> > m_objectPool; |
| Vector<uint32_t> m_openCompositeReferenceStack; |
| RawPtrWillBeMember<MessagePortArray> m_transferredMessagePorts; |
| ArrayBufferContentsArray* m_arrayBufferContents; |
| Vector<v8::Handle<v8::Object> > m_arrayBuffers; |
| uint32_t m_version; |
| }; |
| |
| } // namespace |
| |
| PassRefPtr<SerializedScriptValue> SerializedScriptValue::create(v8::Handle<v8::Value> value, MessagePortArray* messagePorts, ArrayBufferArray* arrayBuffers, ExceptionState& exceptionState, v8::Isolate* isolate) |
| { |
| return adoptRef(new SerializedScriptValue(value, messagePorts, arrayBuffers, 0, exceptionState, isolate)); |
| } |
| |
| PassRefPtr<SerializedScriptValue> SerializedScriptValue::createAndSwallowExceptions(v8::Isolate* isolate, v8::Handle<v8::Value> value) |
| { |
| TrackExceptionState exceptionState; |
| return adoptRef(new SerializedScriptValue(value, 0, 0, 0, exceptionState, isolate)); |
| } |
| |
| PassRefPtr<SerializedScriptValue> SerializedScriptValue::create(const ScriptValue& value, WebBlobInfoArray* blobInfo, ExceptionState& exceptionState, v8::Isolate* isolate) |
| { |
| ASSERT(isolate->InContext()); |
| return adoptRef(new SerializedScriptValue(value.v8Value(), 0, 0, blobInfo, exceptionState, isolate)); |
| } |
| |
| PassRefPtr<SerializedScriptValue> SerializedScriptValue::createFromWire(const String& data) |
| { |
| return adoptRef(new SerializedScriptValue(data)); |
| } |
| |
| PassRefPtr<SerializedScriptValue> SerializedScriptValue::createFromWireBytes(const Vector<uint8_t>& data) |
| { |
| // Decode wire data from big endian to host byte order. |
| ASSERT(!(data.size() % sizeof(UChar))); |
| size_t length = data.size() / sizeof(UChar); |
| StringBuffer<UChar> buffer(length); |
| const UChar* src = reinterpret_cast<const UChar*>(data.data()); |
| UChar* dst = buffer.characters(); |
| for (size_t i = 0; i < length; i++) |
| dst[i] = ntohs(src[i]); |
| |
| return createFromWire(String::adopt(buffer)); |
| } |
| |
| PassRefPtr<SerializedScriptValue> SerializedScriptValue::create(const String& data) |
| { |
| return create(data, v8::Isolate::GetCurrent()); |
| } |
| |
| PassRefPtr<SerializedScriptValue> SerializedScriptValue::create(const String& data, v8::Isolate* isolate) |
| { |
| Writer writer; |
| writer.writeWebCoreString(data); |
| String wireData = writer.takeWireString(); |
| return adoptRef(new SerializedScriptValue(wireData)); |
| } |
| |
| PassRefPtr<SerializedScriptValue> SerializedScriptValue::create() |
| { |
| return adoptRef(new SerializedScriptValue()); |
| } |
| |
| PassRefPtr<SerializedScriptValue> SerializedScriptValue::nullValue() |
| { |
| Writer writer; |
| writer.writeNull(); |
| String wireData = writer.takeWireString(); |
| return adoptRef(new SerializedScriptValue(wireData)); |
| } |
| |
| // Convert serialized string to big endian wire data. |
| void SerializedScriptValue::toWireBytes(Vector<char>& result) const |
| { |
| ASSERT(result.isEmpty()); |
| size_t length = m_data.length(); |
| result.resize(length * sizeof(UChar)); |
| UChar* dst = reinterpret_cast<UChar*>(result.data()); |
| |
| if (m_data.is8Bit()) { |
| const LChar* src = m_data.characters8(); |
| for (size_t i = 0; i < length; i++) |
| dst[i] = htons(static_cast<UChar>(src[i])); |
| } else { |
| const UChar* src = m_data.characters16(); |
| for (size_t i = 0; i < length; i++) |
| dst[i] = htons(src[i]); |
| } |
| } |
| |
| SerializedScriptValue::SerializedScriptValue() |
| : m_externallyAllocatedMemory(0) |
| { |
| } |
| |
| static void neuterArrayBufferInAllWorlds(DOMArrayBuffer* object) |
| { |
| v8::Isolate* isolate = v8::Isolate::GetCurrent(); |
| if (isMainThread()) { |
| Vector<RefPtr<DOMWrapperWorld> > worlds; |
| DOMWrapperWorld::allWorldsInMainThread(worlds); |
| for (size_t i = 0; i < worlds.size(); i++) { |
| v8::Handle<v8::Object> wrapper = worlds[i]->domDataStore().get<V8ArrayBuffer>(object, isolate); |
| if (!wrapper.IsEmpty()) { |
| ASSERT(wrapper->IsArrayBuffer()); |
| v8::Handle<v8::ArrayBuffer>::Cast(wrapper)->Neuter(); |
| } |
| } |
| } else { |
| v8::Handle<v8::Object> wrapper = DOMWrapperWorld::current(isolate).domDataStore().get<V8ArrayBuffer>(object, isolate); |
| if (!wrapper.IsEmpty()) { |
| ASSERT(wrapper->IsArrayBuffer()); |
| v8::Handle<v8::ArrayBuffer>::Cast(wrapper)->Neuter(); |
| } |
| } |
| } |
| |
| PassOwnPtr<SerializedScriptValue::ArrayBufferContentsArray> SerializedScriptValue::transferArrayBuffers(v8::Isolate* isolate, ArrayBufferArray& arrayBuffers, ExceptionState& exceptionState) |
| { |
| ASSERT(arrayBuffers.size()); |
| |
| for (size_t i = 0; i < arrayBuffers.size(); i++) { |
| if (arrayBuffers[i]->isNeutered()) { |
| exceptionState.throwDOMException(DataCloneError, "ArrayBuffer at index " + String::number(i) + " is already neutered."); |
| return nullptr; |
| } |
| } |
| |
| OwnPtr<ArrayBufferContentsArray> contents = adoptPtr(new ArrayBufferContentsArray(arrayBuffers.size())); |
| |
| HashSet<DOMArrayBuffer*> visited; |
| for (size_t i = 0; i < arrayBuffers.size(); i++) { |
| if (visited.contains(arrayBuffers[i].get())) |
| continue; |
| visited.add(arrayBuffers[i].get()); |
| |
| bool result = arrayBuffers[i]->transfer(contents->at(i)); |
| if (!result) { |
| exceptionState.throwDOMException(DataCloneError, "ArrayBuffer at index " + String::number(i) + " could not be transferred."); |
| return nullptr; |
| } |
| |
| neuterArrayBufferInAllWorlds(arrayBuffers[i].get()); |
| } |
| return contents.release(); |
| } |
| |
| SerializedScriptValue::SerializedScriptValue(v8::Handle<v8::Value> value, MessagePortArray* messagePorts, ArrayBufferArray* arrayBuffers, WebBlobInfoArray* blobInfo, ExceptionState& exceptionState, v8::Isolate* isolate) |
| : m_externallyAllocatedMemory(0) |
| { |
| Writer writer; |
| Serializer::Status status; |
| String errorMessage; |
| { |
| v8::TryCatch tryCatch; |
| Serializer serializer(writer, messagePorts, arrayBuffers, blobInfo, m_blobDataHandles, tryCatch, ScriptState::current(isolate)); |
| status = serializer.serialize(value); |
| if (status == Serializer::JSException) { |
| // If there was a JS exception thrown, re-throw it. |
| exceptionState.rethrowV8Exception(tryCatch.Exception()); |
| return; |
| } |
| errorMessage = serializer.errorMessage(); |
| } |
| switch (status) { |
| case Serializer::InputError: |
| case Serializer::DataCloneError: |
| exceptionState.throwDOMException(DataCloneError, errorMessage); |
| return; |
| case Serializer::Success: |
| m_data = writer.takeWireString(); |
| ASSERT(m_data.impl()->hasOneRef()); |
| if (arrayBuffers && arrayBuffers->size()) |
| m_arrayBufferContentsArray = transferArrayBuffers(isolate, *arrayBuffers, exceptionState); |
| return; |
| case Serializer::JSException: |
| ASSERT_NOT_REACHED(); |
| break; |
| } |
| ASSERT_NOT_REACHED(); |
| } |
| |
| SerializedScriptValue::SerializedScriptValue(const String& wireData) |
| : m_externallyAllocatedMemory(0) |
| { |
| m_data = wireData.isolatedCopy(); |
| } |
| |
| v8::Handle<v8::Value> SerializedScriptValue::deserialize(MessagePortArray* messagePorts) |
| { |
| return deserialize(v8::Isolate::GetCurrent(), messagePorts, 0); |
| } |
| |
| v8::Handle<v8::Value> SerializedScriptValue::deserialize(v8::Isolate* isolate, MessagePortArray* messagePorts, const WebBlobInfoArray* blobInfo) |
| { |
| if (!m_data.impl()) |
| return v8::Null(isolate); |
| COMPILE_ASSERT(sizeof(BufferValueType) == 2, BufferValueTypeIsTwoBytes); |
| m_data.ensure16Bit(); |
| // FIXME: SerializedScriptValue shouldn't use String for its underlying |
| // storage. Instead, it should use SharedBuffer or Vector<uint8_t>. The |
| // information stored in m_data isn't even encoded in UTF-16. Instead, |
| // unicode characters are encoded as UTF-8 with two code units per UChar. |
| Reader reader(reinterpret_cast<const uint8_t*>(m_data.impl()->characters16()), 2 * m_data.length(), blobInfo, m_blobDataHandles, ScriptState::current(isolate)); |
| Deserializer deserializer(reader, messagePorts, m_arrayBufferContentsArray.get()); |
| |
| // deserialize() can run arbitrary script (e.g., setters), which could result in |this| being destroyed. |
| // Holding a RefPtr ensures we are alive (along with our internal data) throughout the operation. |
| RefPtr<SerializedScriptValue> protect(this); |
| return deserializer.deserialize(); |
| } |
| |
| bool SerializedScriptValue::extractTransferables(v8::Isolate* isolate, v8::Local<v8::Value> value, int argumentIndex, MessagePortArray& ports, ArrayBufferArray& arrayBuffers, ExceptionState& exceptionState) |
| { |
| if (isUndefinedOrNull(value)) { |
| ports.resize(0); |
| arrayBuffers.resize(0); |
| return true; |
| } |
| |
| uint32_t length = 0; |
| if (value->IsArray()) { |
| v8::Local<v8::Array> array = v8::Local<v8::Array>::Cast(value); |
| length = array->Length(); |
| } else if (!toV8Sequence(value, length, isolate, exceptionState)) { |
| if (!exceptionState.hadException()) |
| exceptionState.throwTypeError(ExceptionMessages::notAnArrayTypeArgumentOrValue(argumentIndex + 1)); |
| return false; |
| } |
| |
| v8::Local<v8::Object> transferrables = v8::Local<v8::Object>::Cast(value); |
| |
| // Validate the passed array of transferrables. |
| for (unsigned i = 0; i < length; ++i) { |
| v8::Local<v8::Value> transferrable = transferrables->Get(i); |
| // Validation of non-null objects, per HTML5 spec 10.3.3. |
| if (isUndefinedOrNull(transferrable)) { |
| exceptionState.throwDOMException(DataCloneError, "Value at index " + String::number(i) + " is an untransferable " + (transferrable->IsUndefined() ? "'undefined'" : "'null'") + " value."); |
| return false; |
| } |
| // Validation of Objects implementing an interface, per WebIDL spec 4.1.15. |
| if (V8MessagePort::hasInstance(transferrable, isolate)) { |
| RefPtrWillBeRawPtr<MessagePort> port = V8MessagePort::toImpl(v8::Handle<v8::Object>::Cast(transferrable)); |
| // Check for duplicate MessagePorts. |
| if (ports.contains(port)) { |
| exceptionState.throwDOMException(DataCloneError, "Message port at index " + String::number(i) + " is a duplicate of an earlier port."); |
| return false; |
| } |
| ports.append(port.release()); |
| } else if (V8ArrayBuffer::hasInstance(transferrable, isolate)) { |
| RefPtr<DOMArrayBuffer> arrayBuffer = V8ArrayBuffer::toImpl(v8::Handle<v8::Object>::Cast(transferrable)); |
| if (arrayBuffers.contains(arrayBuffer)) { |
| exceptionState.throwDOMException(DataCloneError, "ArrayBuffer at index " + String::number(i) + " is a duplicate of an earlier ArrayBuffer."); |
| return false; |
| } |
| arrayBuffers.append(arrayBuffer.release()); |
| } else { |
| exceptionState.throwDOMException(DataCloneError, "Value at index " + String::number(i) + " does not have a transferable type."); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| void SerializedScriptValue::registerMemoryAllocatedWithCurrentScriptContext() |
| { |
| if (m_externallyAllocatedMemory) |
| return; |
| m_externallyAllocatedMemory = static_cast<intptr_t>(m_data.length()); |
| v8::Isolate::GetCurrent()->AdjustAmountOfExternalAllocatedMemory(m_externallyAllocatedMemory); |
| } |
| |
| SerializedScriptValue::~SerializedScriptValue() |
| { |
| // If the allocated memory was not registered before, then this class is likely |
| // used in a context other then Worker's onmessage environment and the presence of |
| // current v8 context is not guaranteed. Avoid calling v8 then. |
| if (m_externallyAllocatedMemory) { |
| ASSERT(v8::Isolate::GetCurrent()); |
| v8::Isolate::GetCurrent()->AdjustAmountOfExternalAllocatedMemory(-m_externallyAllocatedMemory); |
| } |
| } |
| |
| } // namespace blink |