| /* |
| * 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 "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.h" |
| |
| #include <algorithm> |
| #include <memory> |
| |
| #include "base/numerics/checked_math.h" |
| #include "base/sys_byteorder.h" |
| #include "third_party/blink/public/web/web_serialized_script_value_version.h" |
| #include "third_party/blink/renderer/bindings/core/v8/idl_types.h" |
| #include "third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h" |
| #include "third_party/blink/renderer/bindings/core/v8/serialization/serialization_tag.h" |
| #include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value_factory.h" |
| #include "third_party/blink/renderer/bindings/core/v8/serialization/transferables.h" |
| #include "third_party/blink/renderer/bindings/core/v8/serialization/unpacked_serialized_script_value.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_array_buffer.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_image_bitmap.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_message_port.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_mojo_handle.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_offscreen_canvas.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_readable_stream.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_shared_array_buffer.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_transform_stream.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_writable_stream.h" |
| #include "third_party/blink/renderer/core/execution_context/execution_context.h" |
| #include "third_party/blink/renderer/core/imagebitmap/image_bitmap.h" |
| #include "third_party/blink/renderer/core/messaging/message_port.h" |
| #include "third_party/blink/renderer/core/streams/readable_stream.h" |
| #include "third_party/blink/renderer/core/streams/transform_stream.h" |
| #include "third_party/blink/renderer/core/streams/writable_stream.h" |
| #include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h" |
| #include "third_party/blink/renderer/core/typed_arrays/dom_shared_array_buffer.h" |
| #include "third_party/blink/renderer/platform/bindings/dom_data_store.h" |
| #include "third_party/blink/renderer/platform/bindings/dom_wrapper_world.h" |
| #include "third_party/blink/renderer/platform/bindings/exception_state.h" |
| #include "third_party/blink/renderer/platform/bindings/script_state.h" |
| #include "third_party/blink/renderer/platform/blob/blob_data.h" |
| #include "third_party/blink/renderer/platform/heap/handle.h" |
| #include "third_party/blink/renderer/platform/runtime_enabled_features.h" |
| #include "third_party/blink/renderer/platform/wtf/assertions.h" |
| #include "third_party/blink/renderer/platform/wtf/shared_buffer.h" |
| #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_buffer.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_hash.h" |
| #include "third_party/blink/renderer/platform/wtf/vector.h" |
| #include "third_party/blink/renderer/platform/wtf/wtf_size_t.h" |
| |
| namespace blink { |
| |
| scoped_refptr<SerializedScriptValue> SerializedScriptValue::Serialize( |
| v8::Isolate* isolate, |
| v8::Local<v8::Value> value, |
| const SerializeOptions& options, |
| ExceptionState& exception) { |
| return SerializedScriptValueFactory::Instance().Create(isolate, value, |
| options, exception); |
| } |
| |
| scoped_refptr<SerializedScriptValue> |
| SerializedScriptValue::SerializeAndSwallowExceptions( |
| v8::Isolate* isolate, |
| v8::Local<v8::Value> value) { |
| DummyExceptionStateForTesting exception_state; |
| scoped_refptr<SerializedScriptValue> serialized = |
| Serialize(isolate, value, SerializeOptions(), exception_state); |
| if (exception_state.HadException()) |
| return NullValue(); |
| return serialized; |
| } |
| |
| scoped_refptr<SerializedScriptValue> SerializedScriptValue::Create() { |
| return base::AdoptRef(new SerializedScriptValue); |
| } |
| |
| scoped_refptr<SerializedScriptValue> SerializedScriptValue::Create( |
| const String& data) { |
| base::CheckedNumeric<size_t> data_buffer_size = data.length(); |
| data_buffer_size *= 2; |
| if (!data_buffer_size.IsValid()) |
| return Create(); |
| |
| DataBufferPtr data_buffer = AllocateBuffer(data_buffer_size.ValueOrDie()); |
| data.CopyTo(reinterpret_cast<UChar*>(data_buffer.get()), 0, data.length()); |
| |
| return base::AdoptRef(new SerializedScriptValue( |
| std::move(data_buffer), data_buffer_size.ValueOrDie())); |
| } |
| |
| // Versions 16 and below (prior to April 2017) used ntohs() to byte-swap SSV |
| // data when converting it to the wire format. This was a historical accient. |
| // |
| // As IndexedDB stores SSVs to disk indefinitely, we still need to keep around |
| // the code needed to deserialize the old format. |
| inline static bool IsByteSwappedWiredData(const uint8_t* data, size_t length) { |
| // TODO(pwnall): Return false early if we're on big-endian hardware. Chromium |
| // doesn't currently support big-endian hardware, and there's no header |
| // exposing endianness to Blink yet. ARCH_CPU_LITTLE_ENDIAN seems promising, |
| // but Blink is not currently allowed to include files from build/. |
| |
| // The first SSV version without byte-swapping has two envelopes (Blink, V8), |
| // each of which is at least 2 bytes long. |
| if (length < 4) |
| return true; |
| |
| // This code handles the following cases: |
| // |
| // v0 (byte-swapped) - [d, t, ...], t = tag byte, d = first data byte |
| // v1-16 (byte-swapped) - [v, 0xFF, ...], v = version (1 <= v <= 16) |
| // v17+ - [0xFF, v, ...], v = first byte of version varint |
| |
| if (data[0] == kVersionTag) { |
| // The only case where byte-swapped data can have 0xFF in byte zero is |
| // version 0. This can only happen if byte one is a tag (supported in |
| // version 0) that takes in extra data, and the first byte of extra data is |
| // 0xFF. There are 13 such tags, listed below. These tags cannot be used as |
| // version numbers in the Blink-side SSV envelope. |
| // |
| // 35 - 0x23 - # - ImageDataTag |
| // 64 - 0x40 - @ - SparseArrayTag |
| // 68 - 0x44 - D - DateTag |
| // 73 - 0x49 - I - Int32Tag |
| // 78 - 0x4E - N - NumberTag |
| // 82 - 0x52 - R - RegExpTag |
| // 83 - 0x53 - S - StringTag |
| // 85 - 0x55 - U - Uint32Tag |
| // 91 - 0x5B - [ - ArrayTag |
| // 98 - 0x62 - b - BlobTag |
| // 102 - 0x66 - f - FileTag |
| // 108 - 0x6C - l - FileListTag |
| // 123 - 0x7B - { - ObjectTag |
| // |
| // Why we care about version 0: |
| // |
| // IndexedDB stores values using the SSV format. Currently, IndexedDB does |
| // not do any sort of migration, so a value written with a SSV version will |
| // be stored with that version until it is removed via an update or delete. |
| // |
| // IndexedDB was shipped in Chrome 11, which was released on April 27, 2011. |
| // SSV version 1 was added in WebKit r91698, which was shipped in Chrome 14, |
| // which was released on September 16, 2011. |
| static_assert( |
| SerializedScriptValue::kWireFormatVersion != 35 && |
| SerializedScriptValue::kWireFormatVersion != 64 && |
| SerializedScriptValue::kWireFormatVersion != 68 && |
| SerializedScriptValue::kWireFormatVersion != 73 && |
| SerializedScriptValue::kWireFormatVersion != 78 && |
| SerializedScriptValue::kWireFormatVersion != 82 && |
| SerializedScriptValue::kWireFormatVersion != 83 && |
| SerializedScriptValue::kWireFormatVersion != 85 && |
| SerializedScriptValue::kWireFormatVersion != 91 && |
| SerializedScriptValue::kWireFormatVersion != 98 && |
| SerializedScriptValue::kWireFormatVersion != 102 && |
| SerializedScriptValue::kWireFormatVersion != 108 && |
| SerializedScriptValue::kWireFormatVersion != 123, |
| "Using a burned version will prevent us from reading SSV version 0"); |
| |
| // Fast path until the Blink-side SSV envelope reaches version 35. |
| if (SerializedScriptValue::kWireFormatVersion < 35) { |
| if (data[1] < 35) |
| return false; |
| |
| // TODO(pwnall): Add UMA metric here. |
| return true; |
| } |
| |
| // Slower path that would kick in after version 35, assuming we don't remove |
| // support for SSV version 0 by then. |
| static constexpr uint8_t version0Tags[] = {35, 64, 68, 73, 78, 82, 83, |
| 85, 91, 98, 102, 108, 123}; |
| return std::find(std::begin(version0Tags), std::end(version0Tags), |
| data[1]) != std::end(version0Tags); |
| } |
| |
| if (data[1] == kVersionTag) { |
| // The last SSV format that used byte-swapping was version 16. The version |
| // number is stored (before byte-swapping) after a serialization tag, which |
| // is 0xFF. |
| return data[0] != kVersionTag; |
| } |
| |
| // If kVersionTag isn't in any of the first two bytes, this is SSV version 0, |
| // which was byte-swapped. |
| return true; |
| } |
| |
| static void SwapWiredDataIfNeeded(uint8_t* buffer, size_t buffer_size) { |
| if (buffer_size % sizeof(UChar)) |
| return; |
| |
| if (!IsByteSwappedWiredData(buffer, buffer_size)) |
| return; |
| |
| UChar* uchars = reinterpret_cast<UChar*>(buffer); |
| size_t uchars_size = buffer_size / sizeof(UChar); |
| |
| for (size_t i = 0; i < uchars_size; ++i) |
| uchars[i] = base::NetToHost16(uchars[i]); |
| } |
| |
| scoped_refptr<SerializedScriptValue> SerializedScriptValue::Create( |
| const char* data, |
| size_t length) { |
| if (!data) |
| return Create(); |
| |
| DataBufferPtr data_buffer = AllocateBuffer(length); |
| std::copy(data, data + length, data_buffer.get()); |
| SwapWiredDataIfNeeded(data_buffer.get(), length); |
| |
| return base::AdoptRef( |
| new SerializedScriptValue(std::move(data_buffer), length)); |
| } |
| |
| scoped_refptr<SerializedScriptValue> SerializedScriptValue::Create( |
| scoped_refptr<const SharedBuffer> buffer) { |
| if (!buffer) |
| return Create(); |
| |
| DataBufferPtr data_buffer = AllocateBuffer(buffer->size()); |
| size_t offset = 0; |
| for (const auto& span : *buffer) { |
| std::copy(span.data(), span.data() + span.size(), |
| data_buffer.get() + offset); |
| offset += span.size(); |
| } |
| SwapWiredDataIfNeeded(data_buffer.get(), buffer->size()); |
| |
| return base::AdoptRef( |
| new SerializedScriptValue(std::move(data_buffer), buffer->size())); |
| } |
| |
| SerializedScriptValue::SerializedScriptValue() |
| : has_registered_external_allocation_(false) {} |
| |
| SerializedScriptValue::SerializedScriptValue(DataBufferPtr data, |
| size_t data_size) |
| : data_buffer_(std::move(data)), |
| data_buffer_size_(data_size), |
| has_registered_external_allocation_(false) {} |
| |
| void SerializedScriptValue::SetImageBitmapContentsArray( |
| ImageBitmapContentsArray contents) { |
| image_bitmap_contents_array_ = std::move(contents); |
| } |
| |
| SerializedScriptValue::DataBufferPtr SerializedScriptValue::AllocateBuffer( |
| size_t buffer_size) { |
| return DataBufferPtr(static_cast<uint8_t*>(WTF::Partitions::BufferMalloc( |
| buffer_size, "SerializedScriptValue buffer"))); |
| } |
| |
| SerializedScriptValue::~SerializedScriptValue() { |
| // If the allocated memory was not registered before, then this class is |
| // likely used in a context other than Worker's onmessage environment and the |
| // presence of current v8 context is not guaranteed. Avoid calling v8 then. |
| if (has_registered_external_allocation_) { |
| DCHECK(v8::Isolate::GetCurrent()); |
| v8::Isolate::GetCurrent()->AdjustAmountOfExternalAllocatedMemory( |
| -static_cast<int64_t>(DataLengthInBytes())); |
| } |
| } |
| |
| scoped_refptr<SerializedScriptValue> SerializedScriptValue::NullValue() { |
| // The format here may fall a bit out of date, because we support |
| // deserializing SSVs written by old browser versions. |
| static const uint8_t kNullData[] = {0xFF, 17, 0xFF, 13, '0', 0x00}; |
| return Create(reinterpret_cast<const char*>(kNullData), sizeof(kNullData)); |
| } |
| |
| scoped_refptr<SerializedScriptValue> SerializedScriptValue::UndefinedValue() { |
| // The format here may fall a bit out of date, because we support |
| // deserializing SSVs written by old browser versions. |
| static const uint8_t kUndefinedData[] = {0xFF, 17, 0xFF, 13, '_', 0x00}; |
| return Create(reinterpret_cast<const char*>(kUndefinedData), |
| sizeof(kUndefinedData)); |
| } |
| |
| String SerializedScriptValue::ToWireString() const { |
| // Add the padding '\0', but don't put it in |data_buffer_|. |
| // This requires direct use of uninitialized strings, though. |
| UChar* destination; |
| wtf_size_t string_size_bytes = |
| SafeCast<wtf_size_t>((data_buffer_size_ + 1) & ~1); |
| String wire_string = |
| String::CreateUninitialized(string_size_bytes / 2, destination); |
| memcpy(destination, data_buffer_.get(), data_buffer_size_); |
| if (string_size_bytes > data_buffer_size_) |
| reinterpret_cast<char*>(destination)[string_size_bytes - 1] = '\0'; |
| return wire_string; |
| } |
| |
| SerializedScriptValue::ImageBitmapContentsArray |
| SerializedScriptValue::TransferImageBitmapContents( |
| v8::Isolate* isolate, |
| const ImageBitmapArray& image_bitmaps, |
| ExceptionState& exception_state) { |
| ImageBitmapContentsArray contents; |
| |
| if (!image_bitmaps.size()) |
| return contents; |
| |
| for (wtf_size_t i = 0; i < image_bitmaps.size(); ++i) { |
| if (image_bitmaps[i]->IsNeutered()) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kDataCloneError, |
| "ImageBitmap at index " + |
| String::Number(i) + |
| " is already detached."); |
| return contents; |
| } |
| } |
| |
| HeapHashSet<Member<ImageBitmap>> visited; |
| for (wtf_size_t i = 0; i < image_bitmaps.size(); ++i) { |
| if (visited.Contains(image_bitmaps[i])) |
| continue; |
| visited.insert(image_bitmaps[i]); |
| contents.push_back(image_bitmaps[i]->Transfer()); |
| } |
| return contents; |
| } |
| |
| void SerializedScriptValue::TransferImageBitmaps( |
| v8::Isolate* isolate, |
| const ImageBitmapArray& image_bitmaps, |
| ExceptionState& exception_state) { |
| image_bitmap_contents_array_ = |
| TransferImageBitmapContents(isolate, image_bitmaps, exception_state); |
| } |
| |
| void SerializedScriptValue::TransferOffscreenCanvas( |
| v8::Isolate* isolate, |
| const OffscreenCanvasArray& offscreen_canvases, |
| ExceptionState& exception_state) { |
| if (!offscreen_canvases.size()) |
| return; |
| |
| HeapHashSet<Member<OffscreenCanvas>> visited; |
| for (wtf_size_t i = 0; i < offscreen_canvases.size(); i++) { |
| if (visited.Contains(offscreen_canvases[i].Get())) |
| continue; |
| if (offscreen_canvases[i]->IsNeutered()) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kDataCloneError, |
| "OffscreenCanvas at index " + |
| String::Number(i) + |
| " is already detached."); |
| return; |
| } |
| if (offscreen_canvases[i]->RenderingContext()) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kDataCloneError, |
| "OffscreenCanvas at index " + |
| String::Number(i) + |
| " has an associated context."); |
| return; |
| } |
| visited.insert(offscreen_canvases[i].Get()); |
| offscreen_canvases[i].Get()->SetNeutered(); |
| offscreen_canvases[i].Get()->RecordTransfer(); |
| } |
| } |
| |
| void SerializedScriptValue::TransferReadableStreams( |
| ScriptState* script_state, |
| const ReadableStreamArray& readable_streams, |
| ExceptionState& exception_state) { |
| auto* execution_context = ExecutionContext::From(script_state); |
| for (ReadableStream* readable_stream : readable_streams) { |
| TransferReadableStream(script_state, execution_context, readable_stream, |
| exception_state); |
| if (exception_state.HadException()) |
| return; |
| } |
| } |
| |
| void SerializedScriptValue::TransferReadableStream( |
| ScriptState* script_state, |
| ExecutionContext* execution_context, |
| ReadableStream* readable_stream, |
| ExceptionState& exception_state) { |
| MessagePort* local_port = AddStreamChannel(execution_context); |
| readable_stream->Serialize(script_state, local_port, exception_state); |
| if (exception_state.HadException()) |
| return; |
| // The last element is added by the above `AddStreamChannel()` call. |
| streams_.back().readable_optimizer = |
| readable_stream->TakeTransferringOptimizer(); |
| } |
| |
| void SerializedScriptValue::TransferWritableStreams( |
| ScriptState* script_state, |
| const WritableStreamArray& writable_streams, |
| ExceptionState& exception_state) { |
| auto* execution_context = ExecutionContext::From(script_state); |
| for (WritableStream* writable_stream : writable_streams) { |
| TransferWritableStream(script_state, execution_context, writable_stream, |
| exception_state); |
| if (exception_state.HadException()) |
| return; |
| } |
| } |
| |
| void SerializedScriptValue::TransferWritableStream( |
| ScriptState* script_state, |
| ExecutionContext* execution_context, |
| WritableStream* writable_stream, |
| ExceptionState& exception_state) { |
| MessagePort* local_port = AddStreamChannel(execution_context); |
| writable_stream->Serialize(script_state, local_port, exception_state); |
| if (exception_state.HadException()) |
| return; |
| // The last element is added by the above `AddStreamChannel()` call. |
| streams_.back().writable_optimizer = |
| writable_stream->TakeTransferringOptimizer(); |
| } |
| |
| void SerializedScriptValue::TransferTransformStreams( |
| ScriptState* script_state, |
| const TransformStreamArray& transform_streams, |
| ExceptionState& exception_state) { |
| auto* execution_context = ExecutionContext::From(script_state); |
| for (TransformStream* transform_stream : transform_streams) { |
| TransferReadableStream(script_state, execution_context, |
| transform_stream->Readable(), exception_state); |
| if (exception_state.HadException()) |
| return; |
| TransferWritableStream(script_state, execution_context, |
| transform_stream->Writable(), exception_state); |
| if (exception_state.HadException()) |
| return; |
| } |
| } |
| |
| // Creates an entangled pair of channels. Adds one end to |streams_| as |
| // a MessagePortChannel, and returns the other end as a MessagePort. |
| MessagePort* SerializedScriptValue::AddStreamChannel( |
| ExecutionContext* execution_context) { |
| // Used for both https://streams.spec.whatwg.org/#rs-transfer and |
| // https://streams.spec.whatwg.org/#ws-transfer. |
| // 2. Let port1 be a new MessagePort in the current Realm. |
| // 3. Let port2 be a new MessagePort in the current Realm. |
| MessagePortDescriptorPair pipe; |
| auto* local_port = MakeGarbageCollected<MessagePort>(*execution_context); |
| |
| // 4. Entangle port1 and port2. |
| local_port->Entangle(pipe.TakePort0()); |
| |
| // 9. Set dataHolder.[[port]] to ! StructuredSerializeWithTransfer(port2, |
| // « port2 »). |
| streams_.push_back(Stream(pipe.TakePort1())); |
| return local_port; |
| } |
| |
| void SerializedScriptValue::TransferArrayBuffers( |
| v8::Isolate* isolate, |
| const ArrayBufferArray& array_buffers, |
| ExceptionState& exception_state) { |
| array_buffer_contents_array_ = |
| TransferArrayBufferContents(isolate, array_buffers, exception_state); |
| } |
| |
| void SerializedScriptValue::CloneSharedArrayBuffers( |
| SharedArrayBufferArray& array_buffers) { |
| if (!array_buffers.size()) |
| return; |
| |
| HeapHashSet<Member<DOMArrayBufferBase>> visited; |
| shared_array_buffers_contents_.Grow(array_buffers.size()); |
| wtf_size_t i = 0; |
| for (auto* it = array_buffers.begin(); it != array_buffers.end(); ++it) { |
| DOMSharedArrayBuffer* shared_array_buffer = *it; |
| if (visited.Contains(shared_array_buffer)) |
| continue; |
| visited.insert(shared_array_buffer); |
| shared_array_buffer->ShareContentsWith(shared_array_buffers_contents_[i]); |
| i++; |
| } |
| } |
| |
| v8::Local<v8::Value> SerializedScriptValue::Deserialize( |
| v8::Isolate* isolate, |
| const DeserializeOptions& options) { |
| return SerializedScriptValueFactory::Instance().Deserialize(this, isolate, |
| options); |
| } |
| |
| // static |
| UnpackedSerializedScriptValue* SerializedScriptValue::Unpack( |
| scoped_refptr<SerializedScriptValue> value) { |
| if (!value) |
| return nullptr; |
| #if DCHECK_IS_ON() |
| DCHECK(!value->was_unpacked_); |
| value->was_unpacked_ = true; |
| #endif |
| return MakeGarbageCollected<UnpackedSerializedScriptValue>(std::move(value)); |
| } |
| |
| bool SerializedScriptValue::HasPackedContents() const { |
| return !array_buffer_contents_array_.IsEmpty() || |
| !shared_array_buffers_contents_.IsEmpty() || |
| !image_bitmap_contents_array_.IsEmpty(); |
| } |
| |
| bool SerializedScriptValue::ExtractTransferables( |
| v8::Isolate* isolate, |
| v8::Local<v8::Value> value, |
| int argument_index, |
| Transferables& transferables, |
| ExceptionState& exception_state) { |
| if (value.IsEmpty() || value->IsUndefined()) |
| return true; |
| |
| const HeapVector<ScriptValue>& transferable_array = |
| NativeValueTraits<IDLSequence<ScriptValue>>::NativeValue(isolate, value, |
| exception_state); |
| if (exception_state.HadException()) |
| return false; |
| |
| return ExtractTransferables(isolate, transferable_array, transferables, |
| exception_state); |
| } |
| |
| bool SerializedScriptValue::ExtractTransferables( |
| v8::Isolate* isolate, |
| const HeapVector<ScriptValue>& object_sequence, |
| Transferables& transferables, |
| ExceptionState& exception_state) { |
| // Validate the passed array of transferables. |
| wtf_size_t i = 0; |
| bool transferable_streams_enabled = |
| RuntimeEnabledFeatures::TransferableStreamsEnabled( |
| CurrentExecutionContext(isolate)); |
| for (const auto& script_value : object_sequence) { |
| v8::Local<v8::Value> transferable_object = script_value.V8Value(); |
| // Validation of non-null objects, per HTML5 spec 10.3.3. |
| if (IsUndefinedOrNull(transferable_object)) { |
| exception_state.ThrowTypeError( |
| "Value at index " + String::Number(i) + " is an untransferable " + |
| (transferable_object->IsUndefined() ? "'undefined'" : "'null'") + |
| " value."); |
| return false; |
| } |
| // Validation of Objects implementing an interface, per WebIDL spec 4.1.15. |
| if (V8MessagePort::HasInstance(transferable_object, isolate)) { |
| MessagePort* port = V8MessagePort::ToImpl( |
| v8::Local<v8::Object>::Cast(transferable_object)); |
| // Check for duplicate MessagePorts. |
| if (transferables.message_ports.Contains(port)) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kDataCloneError, |
| "Message port at index " + String::Number(i) + |
| " is a duplicate of an earlier port."); |
| return false; |
| } |
| transferables.message_ports.push_back(port); |
| } else if (V8MojoHandle::HasInstance(transferable_object, isolate)) { |
| MojoHandle* handle = V8MojoHandle::ToImpl( |
| v8::Local<v8::Object>::Cast(transferable_object)); |
| // Check for duplicate MojoHandles. |
| if (transferables.mojo_handles.Contains(handle)) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kDataCloneError, |
| "Mojo handle at index " + String::Number(i) + |
| " is a duplicate of an earlier handle."); |
| return false; |
| } |
| transferables.mojo_handles.push_back(handle); |
| } else if (transferable_object->IsArrayBuffer()) { |
| DOMArrayBuffer* array_buffer = V8ArrayBuffer::ToImpl( |
| v8::Local<v8::Object>::Cast(transferable_object)); |
| if (transferables.array_buffers.Contains(array_buffer)) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kDataCloneError, |
| "ArrayBuffer at index " + String::Number(i) + |
| " is a duplicate of an earlier ArrayBuffer."); |
| return false; |
| } |
| transferables.array_buffers.push_back(array_buffer); |
| } else if (transferable_object->IsSharedArrayBuffer()) { |
| DOMSharedArrayBuffer* shared_array_buffer = V8SharedArrayBuffer::ToImpl( |
| v8::Local<v8::Object>::Cast(transferable_object)); |
| if (transferables.array_buffers.Contains(shared_array_buffer)) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kDataCloneError, |
| "SharedArrayBuffer at index " + String::Number(i) + |
| " is a duplicate of an earlier SharedArrayBuffer."); |
| return false; |
| } |
| transferables.array_buffers.push_back(shared_array_buffer); |
| } else if (V8ImageBitmap::HasInstance(transferable_object, isolate)) { |
| ImageBitmap* image_bitmap = V8ImageBitmap::ToImpl( |
| v8::Local<v8::Object>::Cast(transferable_object)); |
| if (transferables.image_bitmaps.Contains(image_bitmap)) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kDataCloneError, |
| "ImageBitmap at index " + String::Number(i) + |
| " is a duplicate of an earlier ImageBitmap."); |
| return false; |
| } |
| transferables.image_bitmaps.push_back(image_bitmap); |
| } else if (V8OffscreenCanvas::HasInstance(transferable_object, isolate)) { |
| OffscreenCanvas* offscreen_canvas = V8OffscreenCanvas::ToImpl( |
| v8::Local<v8::Object>::Cast(transferable_object)); |
| if (transferables.offscreen_canvases.Contains(offscreen_canvas)) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kDataCloneError, |
| "OffscreenCanvas at index " + String::Number(i) + |
| " is a duplicate of an earlier OffscreenCanvas."); |
| return false; |
| } |
| transferables.offscreen_canvases.push_back(offscreen_canvas); |
| } else if (transferable_streams_enabled && |
| V8ReadableStream::HasInstance(transferable_object, isolate)) { |
| ReadableStream* stream = V8ReadableStream::ToImpl( |
| v8::Local<v8::Object>::Cast(transferable_object)); |
| if (transferables.readable_streams.Contains(stream)) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kDataCloneError, |
| "ReadableStream at index " + String::Number(i) + |
| " is a duplicate of an earlier ReadableStream."); |
| return false; |
| } |
| transferables.readable_streams.push_back(stream); |
| } else if (transferable_streams_enabled && |
| V8WritableStream::HasInstance(transferable_object, isolate)) { |
| WritableStream* stream = V8WritableStream::ToImpl( |
| v8::Local<v8::Object>::Cast(transferable_object)); |
| if (transferables.writable_streams.Contains(stream)) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kDataCloneError, |
| "WritableStream at index " + String::Number(i) + |
| " is a duplicate of an earlier WritableStream."); |
| return false; |
| } |
| transferables.writable_streams.push_back(stream); |
| } else if (transferable_streams_enabled && |
| V8TransformStream::HasInstance(transferable_object, isolate)) { |
| TransformStream* stream = V8TransformStream::ToImpl( |
| v8::Local<v8::Object>::Cast(transferable_object)); |
| if (transferables.transform_streams.Contains(stream)) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kDataCloneError, |
| "TransformStream at index " + String::Number(i) + |
| " is a duplicate of an earlier TransformStream."); |
| return false; |
| } |
| transferables.transform_streams.push_back(stream); |
| } else { |
| exception_state.ThrowTypeError("Value at index " + String::Number(i) + |
| " does not have a transferable type."); |
| return false; |
| } |
| i++; |
| } |
| return true; |
| } |
| |
| ArrayBufferArray SerializedScriptValue::ExtractNonSharedArrayBuffers( |
| Transferables& transferables) { |
| ArrayBufferArray& array_buffers = transferables.array_buffers; |
| ArrayBufferArray result; |
| // Partition array_buffers into [shared..., non_shared...], maintaining |
| // relative ordering of elements with the same predicate value. |
| auto* non_shared_begin = |
| std::stable_partition(array_buffers.begin(), array_buffers.end(), |
| [](Member<DOMArrayBufferBase>& array_buffer) { |
| return array_buffer->IsShared(); |
| }); |
| // Copy the non-shared array buffers into result, and remove them from |
| // array_buffers. |
| result.AppendRange(non_shared_begin, array_buffers.end()); |
| array_buffers.EraseAt( |
| static_cast<wtf_size_t>(non_shared_begin - array_buffers.begin()), |
| static_cast<wtf_size_t>(array_buffers.end() - non_shared_begin)); |
| return result; |
| } |
| |
| SerializedScriptValue::ArrayBufferContentsArray |
| SerializedScriptValue::TransferArrayBufferContents( |
| v8::Isolate* isolate, |
| const ArrayBufferArray& array_buffers, |
| ExceptionState& exception_state) { |
| ArrayBufferContentsArray contents; |
| |
| if (!array_buffers.size()) |
| return ArrayBufferContentsArray(); |
| |
| for (auto* it = array_buffers.begin(); it != array_buffers.end(); ++it) { |
| DOMArrayBufferBase* array_buffer = *it; |
| if (array_buffer->IsDetached()) { |
| wtf_size_t index = |
| static_cast<wtf_size_t>(std::distance(array_buffers.begin(), it)); |
| exception_state.ThrowDOMException(DOMExceptionCode::kDataCloneError, |
| "ArrayBuffer at index " + |
| String::Number(index) + |
| " is already detached."); |
| return ArrayBufferContentsArray(); |
| } |
| } |
| |
| contents.Grow(array_buffers.size()); |
| HeapHashSet<Member<DOMArrayBufferBase>> visited; |
| // The scope object to promptly free the backing store to avoid memory |
| // regressions. |
| // TODO(bikineev): Revisit after young generation is there. |
| struct PromptlyFreeSet { |
| // The void* is to avoid blink-gc-plugin error. |
| void* buffer; |
| ~PromptlyFreeSet() { |
| static_cast<HeapHashSet<Member<DOMArrayBufferBase>>*>(buffer)->clear(); |
| } |
| } promptly_free_array_buffers{&visited}; |
| for (auto* it = array_buffers.begin(); it != array_buffers.end(); ++it) { |
| DOMArrayBufferBase* array_buffer_base = *it; |
| if (visited.Contains(array_buffer_base)) |
| continue; |
| visited.insert(array_buffer_base); |
| |
| wtf_size_t index = |
| static_cast<wtf_size_t>(std::distance(array_buffers.begin(), it)); |
| if (array_buffer_base->IsShared()) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kDataCloneError, |
| "SharedArrayBuffer at index " + |
| String::Number(index) + |
| " is not transferable."); |
| return ArrayBufferContentsArray(); |
| } else { |
| DOMArrayBuffer* array_buffer = |
| static_cast<DOMArrayBuffer*>(array_buffer_base); |
| |
| if (!array_buffer->Transfer(isolate, contents.at(index))) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kDataCloneError, |
| "ArrayBuffer at index " + |
| String::Number(index) + |
| " could not be transferred."); |
| return ArrayBufferContentsArray(); |
| } |
| } |
| } |
| return contents; |
| } |
| |
| void SerializedScriptValue:: |
| UnregisterMemoryAllocatedWithCurrentScriptContext() { |
| if (has_registered_external_allocation_) { |
| v8::Isolate::GetCurrent()->AdjustAmountOfExternalAllocatedMemory( |
| -static_cast<int64_t>(DataLengthInBytes())); |
| has_registered_external_allocation_ = false; |
| } |
| } |
| |
| void SerializedScriptValue::RegisterMemoryAllocatedWithCurrentScriptContext() { |
| if (has_registered_external_allocation_) |
| return; |
| |
| has_registered_external_allocation_ = true; |
| int64_t diff = static_cast<int64_t>(DataLengthInBytes()); |
| DCHECK_GE(diff, 0); |
| v8::Isolate::GetCurrent()->AdjustAmountOfExternalAllocatedMemory(diff); |
| } |
| |
| // This ensures that the version number published in |
| // WebSerializedScriptValueVersion.h matches the serializer's understanding. |
| // TODO(jbroman): Fix this to also account for the V8-side version. See |
| // https://crbug.com/704293. |
| static_assert(kSerializedScriptValueVersion == |
| SerializedScriptValue::kWireFormatVersion, |
| "Update WebSerializedScriptValueVersion.h."); |
| |
| bool SerializedScriptValue::IsOriginCheckRequired() const { |
| return file_system_access_tokens_.size() > 0; |
| } |
| |
| } // namespace blink |