| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "third_party/blink/renderer/bindings/core/v8/serialization/v8_script_value_deserializer.h" |
| |
| #include <limits> |
| |
| #include "base/numerics/checked_math.h" |
| #include "base/optional.h" |
| #include "base/time/time.h" |
| #include "third_party/blink/public/platform/web_blob_info.h" |
| #include "third_party/blink/renderer/bindings/core/v8/serialization/unpacked_serialized_script_value.h" |
| #include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_dom_point_init.h" |
| #include "third_party/blink/renderer/core/dom/dom_exception.h" |
| #include "third_party/blink/renderer/core/execution_context/execution_context.h" |
| #include "third_party/blink/renderer/core/fileapi/blob.h" |
| #include "third_party/blink/renderer/core/fileapi/file.h" |
| #include "third_party/blink/renderer/core/fileapi/file_list.h" |
| #include "third_party/blink/renderer/core/geometry/dom_matrix.h" |
| #include "third_party/blink/renderer/core/geometry/dom_matrix_read_only.h" |
| #include "third_party/blink/renderer/core/geometry/dom_point.h" |
| #include "third_party/blink/renderer/core/geometry/dom_point_read_only.h" |
| #include "third_party/blink/renderer/core/geometry/dom_quad.h" |
| #include "third_party/blink/renderer/core/geometry/dom_rect.h" |
| #include "third_party/blink/renderer/core/geometry/dom_rect_read_only.h" |
| #include "third_party/blink/renderer/core/html/canvas/image_data.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/mojo/mojo_handle.h" |
| #include "third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.h" |
| #include "third_party/blink/renderer/core/streams/readable_stream.h" |
| #include "third_party/blink/renderer/core/streams/readable_stream_transferring_optimizer.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/file_metadata.h" |
| #include "third_party/blink/renderer/platform/heap/heap.h" |
| #include "third_party/blink/renderer/platform/runtime_enabled_features.h" |
| #include "third_party/blink/renderer/platform/wtf/date_math.h" |
| #include "third_party/skia/include/core/SkFilterQuality.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| // The "Blink-side" serialization version, which defines how Blink will behave |
| // during the serialization process. The serialization format has two |
| // "envelopes": an outer one controlled by Blink and an inner one by V8. |
| // |
| // They are formatted as follows: |
| // [version tag] [Blink version] [version tag] [v8 version] ... |
| // |
| // Before version 16, there was only a single envelope and the version number |
| // for both parts was always equal. |
| // |
| // See also V8ScriptValueDeserializer.cpp. |
| const uint32_t kMinVersionForSeparateEnvelope = 16; |
| |
| // Returns the number of bytes consumed reading the Blink version envelope, and |
| // sets |*version| to the version. If no Blink envelope was detected, zero is |
| // returned. |
| size_t ReadVersionEnvelope(SerializedScriptValue* serialized_script_value, |
| uint32_t* out_version) { |
| const uint8_t* raw_data = serialized_script_value->Data(); |
| const size_t length = serialized_script_value->DataLengthInBytes(); |
| if (!length || raw_data[0] != kVersionTag) |
| return 0; |
| |
| // Read a 32-bit unsigned integer from varint encoding. |
| uint32_t version = 0; |
| size_t i = 1; |
| unsigned shift = 0; |
| bool has_another_byte; |
| do { |
| if (i >= length) |
| return 0; |
| uint8_t byte = raw_data[i]; |
| if (LIKELY(shift < 32)) { |
| version |= static_cast<uint32_t>(byte & 0x7f) << shift; |
| shift += 7; |
| } |
| has_another_byte = byte & 0x80; |
| i++; |
| } while (has_another_byte); |
| |
| // If the version in the envelope is too low, this was not a Blink version |
| // envelope. |
| if (version < kMinVersionForSeparateEnvelope) |
| return 0; |
| |
| // Otherwise, we did read the envelope. Hurray! |
| *out_version = version; |
| return i; |
| } |
| |
| MessagePort* CreateEntangledPort(ScriptState* script_state, |
| const MessagePortChannel& channel) { |
| MessagePort* const port = |
| MakeGarbageCollected<MessagePort>(*ExecutionContext::From(script_state)); |
| port->Entangle(channel); |
| return port; |
| } |
| |
| } // namespace |
| |
| V8ScriptValueDeserializer::V8ScriptValueDeserializer( |
| ScriptState* script_state, |
| UnpackedSerializedScriptValue* unpacked_value, |
| const Options& options) |
| : V8ScriptValueDeserializer(script_state, |
| unpacked_value, |
| unpacked_value->Value(), |
| options) {} |
| |
| V8ScriptValueDeserializer::V8ScriptValueDeserializer( |
| ScriptState* script_state, |
| scoped_refptr<SerializedScriptValue> value, |
| const Options& options) |
| : V8ScriptValueDeserializer(script_state, |
| nullptr, |
| std::move(value), |
| options) { |
| DCHECK(!serialized_script_value_->HasPackedContents()) |
| << "If the provided SerializedScriptValue could contain packed contents " |
| "due to transfer, then it must be unpacked before deserialization. " |
| "See SerializedScriptValue::Unpack."; |
| } |
| |
| V8ScriptValueDeserializer::V8ScriptValueDeserializer( |
| ScriptState* script_state, |
| UnpackedSerializedScriptValue* unpacked_value, |
| scoped_refptr<SerializedScriptValue> value, |
| const Options& options) |
| : script_state_(script_state), |
| unpacked_value_(unpacked_value), |
| serialized_script_value_(value), |
| deserializer_(script_state_->GetIsolate(), |
| serialized_script_value_->Data(), |
| serialized_script_value_->DataLengthInBytes(), |
| this), |
| transferred_message_ports_(options.message_ports), |
| blob_info_array_(options.blob_info) { |
| deserializer_.SetSupportsLegacyWireFormat(true); |
| } |
| |
| v8::Local<v8::Value> V8ScriptValueDeserializer::Deserialize() { |
| #if DCHECK_IS_ON() |
| DCHECK(!deserialize_invoked_); |
| deserialize_invoked_ = true; |
| #endif |
| |
| v8::Isolate* isolate = script_state_->GetIsolate(); |
| v8::EscapableHandleScope scope(isolate); |
| v8::TryCatch try_catch(isolate); |
| v8::Local<v8::Context> context = script_state_->GetContext(); |
| |
| size_t version_envelope_size = |
| ReadVersionEnvelope(serialized_script_value_.get(), &version_); |
| if (version_envelope_size) { |
| const void* blink_envelope; |
| bool read_envelope = ReadRawBytes(version_envelope_size, &blink_envelope); |
| DCHECK(read_envelope); |
| DCHECK_GE(version_, kMinVersionForSeparateEnvelope); |
| } else { |
| DCHECK_EQ(version_, 0u); |
| } |
| |
| bool read_header; |
| if (!deserializer_.ReadHeader(context).To(&read_header)) |
| return v8::Null(isolate); |
| DCHECK(read_header); |
| |
| // If there was no Blink envelope earlier, Blink shares the wire format |
| // version from the V8 header. |
| if (!version_) |
| version_ = deserializer_.GetWireFormatVersion(); |
| |
| // Prepare to transfer the provided transferables. |
| Transfer(); |
| |
| v8::Local<v8::Value> value; |
| if (!deserializer_.ReadValue(context).ToLocal(&value)) |
| return v8::Null(isolate); |
| return scope.Escape(value); |
| } |
| |
| void V8ScriptValueDeserializer::Transfer() { |
| if (TransferableStreamsEnabled()) { |
| // TODO(ricea): Make ExtendableMessageEvent store an |
| // UnpackedSerializedScriptValue like MessageEvent does, and then this |
| // special case won't be necessary. |
| streams_ = std::move(serialized_script_value_->GetStreams()); |
| } |
| |
| // There's nothing else to transfer if the deserializer was not given an |
| // unpacked value. |
| if (!unpacked_value_) |
| return; |
| |
| v8::Isolate* isolate = script_state_->GetIsolate(); |
| v8::Local<v8::Context> context = script_state_->GetContext(); |
| v8::Local<v8::Object> creation_context = context->Global(); |
| |
| // Transfer array buffers. |
| const auto& array_buffers = unpacked_value_->ArrayBuffers(); |
| for (unsigned i = 0; i < array_buffers.size(); i++) { |
| DOMArrayBufferBase* array_buffer = array_buffers.at(i); |
| v8::Local<v8::Value> wrapper = |
| ToV8(array_buffer, creation_context, isolate); |
| if (array_buffer->IsShared()) { |
| // Crash if we are receiving a SharedArrayBuffer and this isn't allowed. |
| auto* execution_context = ExecutionContext::From(script_state_); |
| CHECK(execution_context->SharedArrayBufferTransferAllowed()); |
| |
| DCHECK(wrapper->IsSharedArrayBuffer()); |
| deserializer_.TransferSharedArrayBuffer( |
| i, v8::Local<v8::SharedArrayBuffer>::Cast(wrapper)); |
| } else { |
| DCHECK(wrapper->IsArrayBuffer()); |
| deserializer_.TransferArrayBuffer( |
| i, v8::Local<v8::ArrayBuffer>::Cast(wrapper)); |
| } |
| } |
| } |
| |
| bool V8ScriptValueDeserializer::ReadUTF8String(String* string) { |
| uint32_t utf8_length = 0; |
| const void* utf8_data = nullptr; |
| if (!ReadUint32(&utf8_length) || !ReadRawBytes(utf8_length, &utf8_data)) |
| return false; |
| *string = |
| String::FromUTF8(reinterpret_cast<const LChar*>(utf8_data), utf8_length); |
| |
| // Decoding must have failed; this encoding does not distinguish between null |
| // and empty strings. |
| return !string->IsNull(); |
| } |
| |
| ScriptWrappable* V8ScriptValueDeserializer::ReadDOMObject( |
| SerializationTag tag, |
| ExceptionState& exception_state) { |
| switch (tag) { |
| case kBlobTag: { |
| if (Version() < 3) |
| return nullptr; |
| String uuid, type; |
| uint64_t size; |
| if (!ReadUTF8String(&uuid) || !ReadUTF8String(&type) || |
| !ReadUint64(&size)) |
| return nullptr; |
| auto blob_handle = GetOrCreateBlobDataHandle(uuid, type, size); |
| if (!blob_handle) |
| return nullptr; |
| return MakeGarbageCollected<Blob>(std::move(blob_handle)); |
| } |
| case kBlobIndexTag: { |
| if (Version() < 6 || !blob_info_array_) |
| return nullptr; |
| uint32_t index = 0; |
| if (!ReadUint32(&index) || index >= blob_info_array_->size()) |
| return nullptr; |
| const WebBlobInfo& info = (*blob_info_array_)[index]; |
| auto blob_handle = info.GetBlobHandle(); |
| if (!blob_handle) { |
| blob_handle = |
| GetOrCreateBlobDataHandle(info.Uuid(), info.GetType(), info.size()); |
| } |
| if (!blob_handle) |
| return nullptr; |
| return MakeGarbageCollected<Blob>(std::move(blob_handle)); |
| } |
| case kFileTag: |
| return ReadFile(); |
| case kFileIndexTag: |
| return ReadFileIndex(); |
| case kFileListTag: { |
| // This does not presently deduplicate a File object and its entry in a |
| // FileList, which is non-standard behavior. |
| uint32_t length; |
| if (!ReadUint32(&length)) |
| return nullptr; |
| auto* file_list = MakeGarbageCollected<FileList>(); |
| for (uint32_t i = 0; i < length; i++) { |
| if (File* file = ReadFile()) |
| file_list->Append(file); |
| else |
| return nullptr; |
| } |
| return file_list; |
| } |
| case kFileListIndexTag: { |
| // This does not presently deduplicate a File object and its entry in a |
| // FileList, which is non-standard behavior. |
| uint32_t length; |
| if (!ReadUint32(&length)) |
| return nullptr; |
| auto* file_list = MakeGarbageCollected<FileList>(); |
| for (uint32_t i = 0; i < length; i++) { |
| if (File* file = ReadFileIndex()) |
| file_list->Append(file); |
| else |
| return nullptr; |
| } |
| return file_list; |
| } |
| case kImageBitmapTag: { |
| SerializedColorSpace canvas_color_space = SerializedColorSpace::kSRGB; |
| SerializedPixelFormat canvas_pixel_format = |
| SerializedPixelFormat::kNative8_LegacyObsolete; |
| SerializedOpacityMode canvas_opacity_mode = |
| SerializedOpacityMode::kOpaque; |
| uint32_t origin_clean = 0, is_premultiplied = 0, width = 0, height = 0, |
| byte_length = 0; |
| const void* pixels = nullptr; |
| if (Version() >= 18) { |
| // read the list of key pair values for color settings, etc. |
| bool is_done = false; |
| do { |
| ImageSerializationTag image_tag; |
| if (!ReadUint32Enum<ImageSerializationTag>(&image_tag)) |
| return nullptr; |
| switch (image_tag) { |
| case ImageSerializationTag::kEndTag: |
| is_done = true; |
| break; |
| case ImageSerializationTag::kCanvasColorSpaceTag: |
| if (!ReadUint32Enum<SerializedColorSpace>(&canvas_color_space)) |
| return nullptr; |
| break; |
| case ImageSerializationTag::kCanvasPixelFormatTag: |
| if (!ReadUint32Enum<SerializedPixelFormat>(&canvas_pixel_format)) |
| return nullptr; |
| break; |
| case ImageSerializationTag::kCanvasOpacityModeTag: |
| if (!ReadUint32Enum<SerializedOpacityMode>(&canvas_opacity_mode)) |
| return nullptr; |
| break; |
| case ImageSerializationTag::kOriginCleanTag: |
| if (!ReadUint32(&origin_clean) || origin_clean > 1) |
| return nullptr; |
| break; |
| case ImageSerializationTag::kIsPremultipliedTag: |
| if (!ReadUint32(&is_premultiplied) || is_premultiplied > 1) |
| return nullptr; |
| break; |
| case ImageSerializationTag::kImageDataStorageFormatTag: |
| // Does not apply to ImageBitmap. |
| return nullptr; |
| } |
| } while (!is_done); |
| } else if (!ReadUint32(&origin_clean) || origin_clean > 1 || |
| !ReadUint32(&is_premultiplied) || is_premultiplied > 1) { |
| return nullptr; |
| } |
| if (!ReadUint32(&width) || !ReadUint32(&height) || |
| !ReadUint32(&byte_length) || !ReadRawBytes(byte_length, &pixels)) |
| return nullptr; |
| SkImageInfo info = |
| SerializedImageBitmapSettings(canvas_color_space, canvas_pixel_format, |
| canvas_opacity_mode, is_premultiplied) |
| .GetSkImageInfo(width, height); |
| base::CheckedNumeric<uint32_t> computed_byte_length = |
| info.computeMinByteSize(); |
| if (!computed_byte_length.IsValid() || |
| computed_byte_length.ValueOrDie() != byte_length) |
| return nullptr; |
| if (!origin_clean) { |
| // Non-origin-clean ImageBitmap serialization/deserialization have |
| // been deprecated. |
| return nullptr; |
| } |
| SkPixmap pixmap(info, pixels, info.minRowBytes()); |
| return MakeGarbageCollected<ImageBitmap>(pixmap, origin_clean); |
| } |
| case kImageBitmapTransferTag: { |
| uint32_t index = 0; |
| if (!unpacked_value_) |
| return nullptr; |
| const auto& transferred_image_bitmaps = unpacked_value_->ImageBitmaps(); |
| if (!ReadUint32(&index) || index >= transferred_image_bitmaps.size()) |
| return nullptr; |
| return transferred_image_bitmaps[index].Get(); |
| } |
| case kImageDataTag: { |
| SerializedColorSpace canvas_color_space = SerializedColorSpace::kSRGB; |
| SerializedImageDataStorageFormat image_data_storage_format = |
| SerializedImageDataStorageFormat::kUint8Clamped; |
| uint32_t width = 0, height = 0; |
| const void* pixels = nullptr; |
| if (Version() >= 18) { |
| bool is_done = false; |
| do { |
| ImageSerializationTag image_tag; |
| if (!ReadUint32Enum<ImageSerializationTag>(&image_tag)) |
| return nullptr; |
| switch (image_tag) { |
| case ImageSerializationTag::kEndTag: |
| is_done = true; |
| break; |
| case ImageSerializationTag::kCanvasColorSpaceTag: |
| if (!ReadUint32Enum<SerializedColorSpace>(&canvas_color_space)) |
| return nullptr; |
| break; |
| case ImageSerializationTag::kImageDataStorageFormatTag: |
| if (!ReadUint32Enum<SerializedImageDataStorageFormat>( |
| &image_data_storage_format)) |
| return nullptr; |
| break; |
| case ImageSerializationTag::kCanvasPixelFormatTag: |
| case ImageSerializationTag::kOriginCleanTag: |
| case ImageSerializationTag::kIsPremultipliedTag: |
| case ImageSerializationTag::kCanvasOpacityModeTag: |
| // Does not apply to ImageData. |
| return nullptr; |
| } |
| } while (!is_done); |
| } |
| |
| uint64_t byte_length_64 = 0; |
| size_t byte_length = 0; |
| if (!ReadUint32(&width) || !ReadUint32(&height) || |
| !ReadUint64(&byte_length_64) || |
| !base::MakeCheckedNum(byte_length_64).AssignIfValid(&byte_length) || |
| !ReadRawBytes(byte_length, &pixels)) { |
| return nullptr; |
| } |
| |
| SerializedImageDataSettings settings(canvas_color_space, |
| image_data_storage_format); |
| base::CheckedNumeric<size_t> computed_byte_length = width; |
| computed_byte_length *= height; |
| computed_byte_length *= |
| ImageData::StorageFormatBytesPerPixel(settings.GetStorageFormat()); |
| if (!computed_byte_length.IsValid() || |
| computed_byte_length.ValueOrDie() != byte_length) |
| return nullptr; |
| ImageData* image_data = ImageData::ValidateAndCreate( |
| width, height, base::nullopt, settings.GetImageDataSettings(), |
| exception_state); |
| if (!image_data) |
| return nullptr; |
| SkPixmap image_data_pixmap = image_data->GetSkPixmap(); |
| DCHECK_EQ(image_data_pixmap.computeByteSize(), byte_length); |
| memcpy(image_data_pixmap.writable_addr(), pixels, byte_length); |
| return image_data; |
| } |
| case kDOMPointTag: { |
| double x = 0, y = 0, z = 0, w = 1; |
| if (!ReadDouble(&x) || !ReadDouble(&y) || !ReadDouble(&z) || |
| !ReadDouble(&w)) |
| return nullptr; |
| return DOMPoint::Create(x, y, z, w); |
| } |
| case kDOMPointReadOnlyTag: { |
| double x = 0, y = 0, z = 0, w = 1; |
| if (!ReadDouble(&x) || !ReadDouble(&y) || !ReadDouble(&z) || |
| !ReadDouble(&w)) |
| return nullptr; |
| return DOMPointReadOnly::Create(x, y, z, w); |
| } |
| case kDOMRectTag: { |
| double x = 0, y = 0, width = 0, height = 0; |
| if (!ReadDouble(&x) || !ReadDouble(&y) || !ReadDouble(&width) || |
| !ReadDouble(&height)) |
| return nullptr; |
| return DOMRect::Create(x, y, width, height); |
| } |
| case kDOMRectReadOnlyTag: { |
| return ReadDOMRectReadOnly(); |
| } |
| case kDOMQuadTag: { |
| DOMPointInit* point_inits[4]; |
| for (int i = 0; i < 4; ++i) { |
| auto* init = DOMPointInit::Create(); |
| double x = 0, y = 0, z = 0, w = 0; |
| if (!ReadDouble(&x) || !ReadDouble(&y) || !ReadDouble(&z) || |
| !ReadDouble(&w)) |
| return nullptr; |
| init->setX(x); |
| init->setY(y); |
| init->setZ(z); |
| init->setW(w); |
| point_inits[i] = init; |
| } |
| return DOMQuad::Create(point_inits[0], point_inits[1], point_inits[2], |
| point_inits[3]); |
| } |
| case kDOMMatrix2DTag: { |
| double values[6]; |
| for (double& d : values) { |
| if (!ReadDouble(&d)) |
| return nullptr; |
| } |
| return DOMMatrix::CreateForSerialization(values, base::size(values)); |
| } |
| case kDOMMatrix2DReadOnlyTag: { |
| double values[6]; |
| for (double& d : values) { |
| if (!ReadDouble(&d)) |
| return nullptr; |
| } |
| return DOMMatrixReadOnly::CreateForSerialization(values, |
| base::size(values)); |
| } |
| case kDOMMatrixTag: { |
| double values[16]; |
| for (double& d : values) { |
| if (!ReadDouble(&d)) |
| return nullptr; |
| } |
| return DOMMatrix::CreateForSerialization(values, base::size(values)); |
| } |
| case kDOMMatrixReadOnlyTag: { |
| double values[16]; |
| for (double& d : values) { |
| if (!ReadDouble(&d)) |
| return nullptr; |
| } |
| return DOMMatrixReadOnly::CreateForSerialization(values, |
| base::size(values)); |
| } |
| case kMessagePortTag: { |
| uint32_t index = 0; |
| if (!ReadUint32(&index) || !transferred_message_ports_ || |
| index >= transferred_message_ports_->size()) |
| return nullptr; |
| return (*transferred_message_ports_)[index].Get(); |
| } |
| case kMojoHandleTag: { |
| uint32_t index = 0; |
| if (!RuntimeEnabledFeatures::MojoJSEnabled() || !ReadUint32(&index) || |
| index >= serialized_script_value_->MojoHandles().size()) { |
| return nullptr; |
| } |
| return MakeGarbageCollected<MojoHandle>( |
| std::move(serialized_script_value_->MojoHandles()[index])); |
| } |
| case kOffscreenCanvasTransferTag: { |
| uint32_t width = 0, height = 0, canvas_id = 0, client_id = 0, sink_id = 0, |
| filter_quality = 0; |
| if (!ReadUint32(&width) || !ReadUint32(&height) || |
| !ReadUint32(&canvas_id) || !ReadUint32(&client_id) || |
| !ReadUint32(&sink_id) || !ReadUint32(&filter_quality)) |
| return nullptr; |
| OffscreenCanvas* canvas = OffscreenCanvas::Create( |
| ExecutionContext::From(GetScriptState()), width, height); |
| canvas->SetPlaceholderCanvasId(canvas_id); |
| canvas->SetFrameSinkId(client_id, sink_id); |
| if (filter_quality == 0) |
| canvas->SetFilterQuality(kNone_SkFilterQuality); |
| else |
| canvas->SetFilterQuality(kLow_SkFilterQuality); |
| return canvas; |
| } |
| case kReadableStreamTransferTag: { |
| if (!TransferableStreamsEnabled()) |
| return nullptr; |
| uint32_t index = 0; |
| if (!ReadUint32(&index) || index >= streams_.size()) { |
| return nullptr; |
| } |
| return ReadableStream::Deserialize( |
| script_state_, |
| CreateEntangledPort(GetScriptState(), streams_[index].channel), |
| std::move(streams_[index].readable_optimizer), exception_state); |
| } |
| case kWritableStreamTransferTag: { |
| if (!TransferableStreamsEnabled()) |
| return nullptr; |
| uint32_t index = 0; |
| if (!ReadUint32(&index) || index >= streams_.size()) { |
| return nullptr; |
| } |
| return WritableStream::Deserialize( |
| script_state_, |
| CreateEntangledPort(GetScriptState(), streams_[index].channel), |
| std::move(streams_[index].writable_optimizer), exception_state); |
| } |
| case kTransformStreamTransferTag: { |
| if (!TransferableStreamsEnabled()) |
| return nullptr; |
| uint32_t index = 0; |
| if (!ReadUint32(&index) || |
| index == std::numeric_limits<decltype(index)>::max() || |
| index + 1 >= streams_.size()) { |
| return nullptr; |
| } |
| MessagePort* const port_for_readable = |
| CreateEntangledPort(GetScriptState(), streams_[index].channel); |
| MessagePort* const port_for_writable = |
| CreateEntangledPort(GetScriptState(), streams_[index + 1].channel); |
| |
| // https://streams.spec.whatwg.org/#ts-transfer |
| // 1. Let readableRecord be ! |
| // StructuredDeserializeWithTransfer(dataHolder.[[readable]], the |
| // current Realm). |
| ReadableStream* readable = |
| ReadableStream::Deserialize(script_state_, port_for_readable, |
| /*optimizer=*/nullptr, exception_state); |
| if (!readable) |
| return nullptr; |
| |
| // 2. Let writableRecord be ! |
| // StructuredDeserializeWithTransfer(dataHolder.[[writable]], the |
| // current Realm). |
| WritableStream* writable = |
| WritableStream::Deserialize(script_state_, port_for_writable, |
| /*optimizer=*/nullptr, exception_state); |
| if (!writable) |
| return nullptr; |
| |
| // 3. Set value.[[readable]] to readableRecord.[[Deserialized]]. |
| // 4. Set value.[[writable]] to writableRecord.[[Deserialized]]. |
| // 5. Set value.[[backpressure]], value.[[backpressureChangePromise]], and |
| // value.[[controller]] to undefined. |
| return MakeGarbageCollected<TransformStream>(readable, writable); |
| } |
| case kDOMExceptionTag: { |
| // See the serialization side for |stack_unused|. |
| String name, message, stack_unused; |
| if (!ReadUTF8String(&name) || !ReadUTF8String(&message) || |
| !ReadUTF8String(&stack_unused)) { |
| return nullptr; |
| } |
| // DOMException::Create takes its arguments in the opposite order. |
| return DOMException::Create(message, name); |
| } |
| default: |
| break; |
| } |
| return nullptr; |
| } |
| |
| File* V8ScriptValueDeserializer::ReadFile() { |
| if (Version() < 3) |
| return nullptr; |
| String path, name, relative_path, uuid, type; |
| uint32_t has_snapshot = 0; |
| uint64_t size = 0; |
| double last_modified_ms = 0; |
| if (!ReadUTF8String(&path) || (Version() >= 4 && !ReadUTF8String(&name)) || |
| (Version() >= 4 && !ReadUTF8String(&relative_path)) || |
| !ReadUTF8String(&uuid) || !ReadUTF8String(&type) || |
| (Version() >= 4 && !ReadUint32(&has_snapshot))) |
| return nullptr; |
| if (has_snapshot) { |
| if (!ReadUint64(&size) || !ReadDouble(&last_modified_ms)) |
| return nullptr; |
| if (Version() < 8) |
| last_modified_ms *= kMsPerSecond; |
| } |
| uint32_t is_user_visible = 1; |
| if (Version() >= 7 && !ReadUint32(&is_user_visible)) |
| return nullptr; |
| const File::UserVisibility user_visibility = |
| is_user_visible ? File::kIsUserVisible : File::kIsNotUserVisible; |
| const uint64_t kSizeForDataHandle = static_cast<uint64_t>(-1); |
| auto blob_handle = GetOrCreateBlobDataHandle(uuid, type, kSizeForDataHandle); |
| if (!blob_handle) |
| return nullptr; |
| base::Optional<base::Time> last_modified; |
| if (has_snapshot && std::isfinite(last_modified_ms)) |
| last_modified = base::Time::FromJsTime(last_modified_ms); |
| return File::CreateFromSerialization(path, name, relative_path, |
| user_visibility, has_snapshot, size, |
| last_modified, std::move(blob_handle)); |
| } |
| |
| File* V8ScriptValueDeserializer::ReadFileIndex() { |
| if (Version() < 6 || !blob_info_array_) |
| return nullptr; |
| uint32_t index; |
| if (!ReadUint32(&index) || index >= blob_info_array_->size()) |
| return nullptr; |
| const WebBlobInfo& info = (*blob_info_array_)[index]; |
| auto blob_handle = info.GetBlobHandle(); |
| if (!blob_handle) { |
| blob_handle = |
| GetOrCreateBlobDataHandle(info.Uuid(), info.GetType(), info.size()); |
| } |
| if (!blob_handle) |
| return nullptr; |
| return File::CreateFromIndexedSerialization(info.FileName(), info.size(), |
| info.LastModified(), blob_handle); |
| } |
| |
| DOMRectReadOnly* V8ScriptValueDeserializer::ReadDOMRectReadOnly() { |
| double x = 0, y = 0, width = 0, height = 0; |
| if (!ReadDouble(&x) || !ReadDouble(&y) || !ReadDouble(&width) || |
| !ReadDouble(&height)) |
| return nullptr; |
| return DOMRectReadOnly::Create(x, y, width, height); |
| } |
| |
| scoped_refptr<BlobDataHandle> |
| V8ScriptValueDeserializer::GetOrCreateBlobDataHandle(const String& uuid, |
| const String& type, |
| uint64_t size) { |
| // 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& handles = serialized_script_value_->BlobDataHandles(); |
| BlobDataHandleMap::const_iterator it = handles.find(uuid); |
| if (it != handles.end()) |
| return it->value; |
| // Creating a BlobDataHandle from an empty string will get this renderer |
| // killed, so since we're parsing untrusted data (from possibly another |
| // process/renderer) return null instead. |
| if (uuid.IsEmpty()) |
| return nullptr; |
| return BlobDataHandle::Create(uuid, type, size); |
| } |
| |
| v8::MaybeLocal<v8::Object> V8ScriptValueDeserializer::ReadHostObject( |
| v8::Isolate* isolate) { |
| DCHECK_EQ(isolate, script_state_->GetIsolate()); |
| ExceptionState exception_state(isolate, ExceptionState::kUnknownContext, |
| nullptr, nullptr); |
| ScriptWrappable* wrappable = nullptr; |
| SerializationTag tag = kVersionTag; |
| if (ReadTag(&tag)) { |
| wrappable = ReadDOMObject(tag, exception_state); |
| if (exception_state.HadException()) |
| return v8::MaybeLocal<v8::Object>(); |
| } |
| if (!wrappable) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kDataCloneError, |
| "Unable to deserialize cloned data."); |
| return v8::MaybeLocal<v8::Object>(); |
| } |
| v8::Local<v8::Object> creation_context = |
| script_state_->GetContext()->Global(); |
| v8::Local<v8::Value> wrapper = ToV8(wrappable, creation_context, isolate); |
| DCHECK(wrapper->IsObject()); |
| return wrapper.As<v8::Object>(); |
| } |
| |
| v8::MaybeLocal<v8::WasmModuleObject> |
| V8ScriptValueDeserializer::GetWasmModuleFromId(v8::Isolate* isolate, |
| uint32_t id) { |
| if (id < serialized_script_value_->WasmModules().size()) { |
| ExecutionContext* execution_context = ExecutionContext::From(script_state_); |
| DCHECK(serialized_script_value_->origin()); |
| UseCounter::Count(execution_context, WebFeature::kWasmModuleSharing); |
| if (!serialized_script_value_->origin()->IsSameOriginWith( |
| execution_context->GetSecurityOrigin())) { |
| UseCounter::Count(execution_context, |
| WebFeature::kCrossOriginWasmModuleSharing); |
| } |
| return v8::WasmModuleObject::FromCompiledModule( |
| isolate, serialized_script_value_->WasmModules()[id]); |
| } |
| CHECK(serialized_script_value_->WasmModules().IsEmpty()); |
| return v8::MaybeLocal<v8::WasmModuleObject>(); |
| } |
| |
| v8::MaybeLocal<v8::SharedArrayBuffer> |
| V8ScriptValueDeserializer::GetSharedArrayBufferFromId(v8::Isolate* isolate, |
| uint32_t id) { |
| auto& shared_array_buffers_contents = |
| serialized_script_value_->SharedArrayBuffersContents(); |
| if (id < shared_array_buffers_contents.size()) { |
| ArrayBufferContents& contents = shared_array_buffers_contents.at(id); |
| DOMSharedArrayBuffer* shared_array_buffer = |
| DOMSharedArrayBuffer::Create(contents); |
| v8::Local<v8::Object> creation_context = |
| script_state_->GetContext()->Global(); |
| v8::Local<v8::Value> wrapper = |
| ToV8(shared_array_buffer, creation_context, isolate); |
| DCHECK(wrapper->IsSharedArrayBuffer()); |
| return v8::Local<v8::SharedArrayBuffer>::Cast(wrapper); |
| } |
| ExceptionState exception_state(isolate, ExceptionState::kUnknownContext, |
| nullptr, nullptr); |
| exception_state.ThrowDOMException(DOMExceptionCode::kDataCloneError, |
| "Unable to deserialize SharedArrayBuffer."); |
| // If the id does not map to a valid index, it is expected that the |
| // SerializedScriptValue emptied its shared ArrayBufferContents when crossing |
| // a process boundary. |
| CHECK(shared_array_buffers_contents.IsEmpty()); |
| return v8::MaybeLocal<v8::SharedArrayBuffer>(); |
| } |
| |
| bool V8ScriptValueDeserializer::TransferableStreamsEnabled() const { |
| return RuntimeEnabledFeatures::TransferableStreamsEnabled( |
| ExecutionContext::From(script_state_)); |
| } |
| |
| } // namespace blink |