| // 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_serializer.h" |
| |
| #include "base/auto_reset.h" |
| #include "third_party/blink/public/mojom/web_feature/web_feature.mojom-blink.h" |
| #include "third_party/blink/public/platform/web_blob_info.h" |
| #include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_blob.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_dom_exception.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_dom_matrix.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_dom_matrix_read_only.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_dom_point.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_dom_point_read_only.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_dom_quad.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_dom_rect.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_dom_rect_read_only.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_file.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_file_list.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_image_bitmap.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_image_data.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_throw_dom_exception.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/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/mojo/mojo_handle.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_base.h" |
| #include "third_party/blink/renderer/platform/file_metadata.h" |
| #include "third_party/blink/renderer/platform/instrumentation/use_counter.h" |
| #include "third_party/blink/renderer/platform/runtime_enabled_features.h" |
| #include "third_party/blink/renderer/platform/wtf/allocator/partitions.h" |
| #include "third_party/blink/renderer/platform/wtf/date_math.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h" |
| |
| namespace blink { |
| |
| // The "Blink-side" serialization version, which defines how Blink will behave |
| // during the serialization process, is in |
| // SerializedScriptValue::wireFormatVersion. 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. |
| // |
| // This version number must be incremented whenever any incompatible changes are |
| // made to how Blink writes data. Purely V8-side changes do not require an |
| // adjustment to this value. |
| |
| V8ScriptValueSerializer::V8ScriptValueSerializer(ScriptState* script_state, |
| const Options& options) |
| : script_state_(script_state), |
| serialized_script_value_(SerializedScriptValue::Create()), |
| serializer_(script_state_->GetIsolate(), this), |
| transferables_(options.transferables), |
| blob_info_array_(options.blob_info), |
| wasm_policy_(options.wasm_policy), |
| for_storage_(options.for_storage == SerializedScriptValue::kForStorage) {} |
| |
| scoped_refptr<SerializedScriptValue> V8ScriptValueSerializer::Serialize( |
| v8::Local<v8::Value> value, |
| ExceptionState& exception_state) { |
| #if DCHECK_IS_ON() |
| DCHECK(!serialize_invoked_); |
| serialize_invoked_ = true; |
| #endif |
| DCHECK(serialized_script_value_); |
| base::AutoReset<const ExceptionState*> reset(&exception_state_, |
| &exception_state); |
| |
| // Prepare to transfer the provided transferables. |
| PrepareTransfer(exception_state); |
| if (exception_state.HadException()) |
| return nullptr; |
| |
| // Write out the file header. |
| WriteTag(kVersionTag); |
| WriteUint32(SerializedScriptValue::kWireFormatVersion); |
| serializer_.WriteHeader(); |
| |
| // Serialize the value and handle errors. |
| v8::TryCatch try_catch(script_state_->GetIsolate()); |
| bool wrote_value; |
| if (!serializer_.WriteValue(script_state_->GetContext(), value) |
| .To(&wrote_value)) { |
| DCHECK(try_catch.HasCaught()); |
| exception_state.RethrowV8Exception(try_catch.Exception()); |
| return nullptr; |
| } |
| DCHECK(wrote_value); |
| |
| // Finalize the transfer (e.g. detaching array buffers). |
| FinalizeTransfer(exception_state); |
| if (exception_state.HadException()) |
| return nullptr; |
| |
| if (shared_array_buffers_.size()) { |
| auto* execution_context = ExecutionContext::From(script_state_); |
| if (!execution_context->CheckSharedArrayBufferTransferAllowedAndReport()) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kDataCloneError, |
| "SharedArrayBuffer transfer requires self.crossOriginIsolated."); |
| return nullptr; |
| } |
| } |
| |
| serialized_script_value_->CloneSharedArrayBuffers(shared_array_buffers_); |
| |
| // Finalize the results. |
| std::pair<uint8_t*, size_t> buffer = serializer_.Release(); |
| serialized_script_value_->SetData( |
| SerializedScriptValue::DataBufferPtr(buffer.first), buffer.second); |
| return std::move(serialized_script_value_); |
| } |
| |
| void V8ScriptValueSerializer::PrepareTransfer(ExceptionState& exception_state) { |
| if (!transferables_) |
| return; |
| |
| // Transfer array buffers. |
| for (uint32_t i = 0; i < transferables_->array_buffers.size(); i++) { |
| DOMArrayBufferBase* array_buffer = transferables_->array_buffers[i].Get(); |
| if (!array_buffer->IsShared()) { |
| v8::Local<v8::Value> wrapper = ToV8(array_buffer, script_state_); |
| serializer_.TransferArrayBuffer( |
| i, v8::Local<v8::ArrayBuffer>::Cast(wrapper)); |
| } else { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kDataCloneError, |
| "SharedArrayBuffer can not be in transfer list."); |
| return; |
| } |
| } |
| } |
| |
| void V8ScriptValueSerializer::FinalizeTransfer( |
| ExceptionState& exception_state) { |
| // TODO(jbroman): Strictly speaking, this is not correct; transfer should |
| // occur in the order of the transfer list. |
| // https://html.spec.whatwg.org/C/#structuredclonewithtransfer |
| |
| v8::Isolate* isolate = script_state_->GetIsolate(); |
| |
| ArrayBufferArray array_buffers; |
| // The scope object to promptly free the backing store to avoid memory |
| // regressions. |
| // TODO(bikineev): Revisit after young generation is there. |
| struct PromptlyFreeArrayBuffers { |
| // The void* is to avoid blink-gc-plugin error. |
| void* buffer; |
| ~PromptlyFreeArrayBuffers() { |
| static_cast<ArrayBufferArray*>(buffer)->clear(); |
| } |
| } promptly_free_array_buffers{&array_buffers}; |
| if (transferables_) |
| array_buffers.AppendVector(transferables_->array_buffers); |
| |
| if (!array_buffers.IsEmpty()) { |
| serialized_script_value_->TransferArrayBuffers(isolate, array_buffers, |
| exception_state); |
| if (exception_state.HadException()) |
| return; |
| } |
| |
| if (transferables_) { |
| serialized_script_value_->TransferImageBitmaps( |
| isolate, transferables_->image_bitmaps, exception_state); |
| if (exception_state.HadException()) |
| return; |
| |
| serialized_script_value_->TransferOffscreenCanvas( |
| isolate, transferables_->offscreen_canvases, exception_state); |
| if (exception_state.HadException()) |
| return; |
| |
| if (TransferableStreamsEnabled()) { |
| // Order matters here, because the order in which streams are added to the |
| // |stream_ports_| array must match the indexes which are calculated in |
| // WriteDOMObject(). |
| serialized_script_value_->TransferReadableStreams( |
| script_state_, transferables_->readable_streams, exception_state); |
| if (exception_state.HadException()) |
| return; |
| serialized_script_value_->TransferWritableStreams( |
| script_state_, transferables_->writable_streams, exception_state); |
| if (exception_state.HadException()) |
| return; |
| serialized_script_value_->TransferTransformStreams( |
| script_state_, transferables_->transform_streams, exception_state); |
| if (exception_state.HadException()) |
| return; |
| } |
| } |
| } |
| |
| void V8ScriptValueSerializer::WriteUTF8String(const String& string) { |
| // TODO(jbroman): Ideally this method would take a WTF::StringView, but the |
| // StringUTF8Adaptor trick doesn't yet work with StringView. |
| StringUTF8Adaptor utf8(string); |
| WriteUint32(utf8.size()); |
| WriteRawBytes(utf8.data(), utf8.size()); |
| } |
| |
| bool V8ScriptValueSerializer::WriteDOMObject(ScriptWrappable* wrappable, |
| ExceptionState& exception_state) { |
| const WrapperTypeInfo* wrapper_type_info = wrappable->GetWrapperTypeInfo(); |
| if (wrapper_type_info == V8Blob::GetWrapperTypeInfo()) { |
| Blob* blob = wrappable->ToImpl<Blob>(); |
| serialized_script_value_->BlobDataHandles().Set(blob->Uuid(), |
| blob->GetBlobDataHandle()); |
| if (blob_info_array_) { |
| size_t index = blob_info_array_->size(); |
| DCHECK_LE(index, std::numeric_limits<uint32_t>::max()); |
| blob_info_array_->emplace_back(blob->GetBlobDataHandle(), blob->type(), |
| blob->size()); |
| WriteTag(kBlobIndexTag); |
| WriteUint32(static_cast<uint32_t>(index)); |
| } else { |
| WriteTag(kBlobTag); |
| WriteUTF8String(blob->Uuid()); |
| WriteUTF8String(blob->type()); |
| WriteUint64(blob->size()); |
| } |
| return true; |
| } |
| if (wrapper_type_info == V8File::GetWrapperTypeInfo()) { |
| WriteTag(blob_info_array_ ? kFileIndexTag : kFileTag); |
| return WriteFile(wrappable->ToImpl<File>(), exception_state); |
| } |
| if (wrapper_type_info == V8FileList::GetWrapperTypeInfo()) { |
| // This does not presently deduplicate a File object and its entry in a |
| // FileList, which is non-standard behavior. |
| FileList* file_list = wrappable->ToImpl<FileList>(); |
| unsigned length = file_list->length(); |
| WriteTag(blob_info_array_ ? kFileListIndexTag : kFileListTag); |
| WriteUint32(length); |
| for (unsigned i = 0; i < length; i++) { |
| if (!WriteFile(file_list->item(i), exception_state)) |
| return false; |
| } |
| return true; |
| } |
| if (wrapper_type_info == V8ImageBitmap::GetWrapperTypeInfo()) { |
| ImageBitmap* image_bitmap = wrappable->ToImpl<ImageBitmap>(); |
| if (image_bitmap->IsNeutered()) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kDataCloneError, |
| "An ImageBitmap is detached and could not be cloned."); |
| return false; |
| } |
| |
| auto* execution_context = ExecutionContext::From(script_state_); |
| // If this ImageBitmap was transferred, it can be serialized by index. |
| size_t index = kNotFound; |
| if (transferables_) |
| index = transferables_->image_bitmaps.Find(image_bitmap); |
| if (index != kNotFound) { |
| if (image_bitmap->OriginClean()) { |
| execution_context->CountUse( |
| mojom::WebFeature::kOriginCleanImageBitmapTransfer); |
| } else { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kDataCloneError, |
| "Non-origin-clean ImageBitmap cannot be transferred."); |
| return false; |
| } |
| |
| DCHECK_LE(index, std::numeric_limits<uint32_t>::max()); |
| WriteTag(kImageBitmapTransferTag); |
| WriteUint32(static_cast<uint32_t>(index)); |
| return true; |
| } |
| |
| // Otherwise, it must be fully serialized. |
| if (image_bitmap->OriginClean()) { |
| execution_context->CountUse( |
| mojom::WebFeature::kOriginCleanImageBitmapSerialization); |
| } else { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kDataCloneError, |
| "Non-origin-clean ImageBitmap cannot be cloned."); |
| return false; |
| } |
| WriteTag(kImageBitmapTag); |
| SkImageInfo info = image_bitmap->GetBitmapSkImageInfo(); |
| SerializedImageBitmapSettings color_params(info); |
| WriteUint32Enum(ImageSerializationTag::kCanvasColorSpaceTag); |
| WriteUint32Enum(color_params.GetSerializedColorSpace()); |
| WriteUint32Enum(ImageSerializationTag::kCanvasPixelFormatTag); |
| WriteUint32Enum(color_params.GetSerializedPixelFormat()); |
| WriteUint32Enum(ImageSerializationTag::kCanvasOpacityModeTag); |
| WriteUint32Enum(color_params.GetSerializedOpacityMode()); |
| WriteUint32Enum(ImageSerializationTag::kOriginCleanTag); |
| WriteUint32(image_bitmap->OriginClean()); |
| WriteUint32Enum(ImageSerializationTag::kIsPremultipliedTag); |
| WriteUint32(color_params.IsPremultiplied()); |
| WriteUint32Enum(ImageSerializationTag::kEndTag); |
| WriteUint32(image_bitmap->width()); |
| WriteUint32(image_bitmap->height()); |
| Vector<uint8_t> pixels = image_bitmap->CopyBitmapData(info, false); |
| // Check if we succeeded to copy the BitmapData. |
| if (image_bitmap->width() != 0 && image_bitmap->height() != 0 && |
| pixels.size() == 0) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kDataCloneError, |
| "An ImageBitmap could not be read successfully."); |
| return false; |
| } |
| WriteUint32(pixels.size()); |
| WriteRawBytes(pixels.data(), pixels.size()); |
| return true; |
| } |
| if (wrapper_type_info == V8ImageData::GetWrapperTypeInfo()) { |
| ImageData* image_data = wrappable->ToImpl<ImageData>(); |
| WriteTag(kImageDataTag); |
| SerializedImageDataSettings settings( |
| image_data->GetCanvasColorSpace(), |
| image_data->GetImageDataStorageFormat()); |
| WriteUint32Enum(ImageSerializationTag::kCanvasColorSpaceTag); |
| WriteUint32Enum(settings.GetSerializedColorSpace()); |
| WriteUint32Enum(ImageSerializationTag::kImageDataStorageFormatTag); |
| WriteUint32Enum(settings.GetSerializedImageDataStorageFormat()); |
| WriteUint32Enum(ImageSerializationTag::kEndTag); |
| WriteUint32(image_data->width()); |
| WriteUint32(image_data->height()); |
| if (image_data->IsBufferBaseDetached()) { |
| WriteUint64(0u); |
| } else { |
| SkPixmap image_data_pixmap = image_data->GetSkPixmap(); |
| size_t pixel_buffer_length = image_data_pixmap.computeByteSize(); |
| WriteUint64(base::strict_cast<uint64_t>(pixel_buffer_length)); |
| WriteRawBytes(image_data_pixmap.addr(), pixel_buffer_length); |
| } |
| return true; |
| } |
| if (wrapper_type_info == V8DOMPoint::GetWrapperTypeInfo()) { |
| DOMPoint* point = wrappable->ToImpl<DOMPoint>(); |
| WriteTag(kDOMPointTag); |
| WriteDouble(point->x()); |
| WriteDouble(point->y()); |
| WriteDouble(point->z()); |
| WriteDouble(point->w()); |
| return true; |
| } |
| if (wrapper_type_info == V8DOMPointReadOnly::GetWrapperTypeInfo()) { |
| DOMPointReadOnly* point = wrappable->ToImpl<DOMPointReadOnly>(); |
| WriteTag(kDOMPointReadOnlyTag); |
| WriteDouble(point->x()); |
| WriteDouble(point->y()); |
| WriteDouble(point->z()); |
| WriteDouble(point->w()); |
| return true; |
| } |
| if (wrapper_type_info == V8DOMRect::GetWrapperTypeInfo()) { |
| DOMRect* rect = wrappable->ToImpl<DOMRect>(); |
| WriteTag(kDOMRectTag); |
| WriteDouble(rect->x()); |
| WriteDouble(rect->y()); |
| WriteDouble(rect->width()); |
| WriteDouble(rect->height()); |
| return true; |
| } |
| if (wrapper_type_info == V8DOMRectReadOnly::GetWrapperTypeInfo()) { |
| DOMRectReadOnly* rect = wrappable->ToImpl<DOMRectReadOnly>(); |
| WriteTag(kDOMRectReadOnlyTag); |
| WriteDouble(rect->x()); |
| WriteDouble(rect->y()); |
| WriteDouble(rect->width()); |
| WriteDouble(rect->height()); |
| return true; |
| } |
| if (wrapper_type_info == V8DOMQuad::GetWrapperTypeInfo()) { |
| DOMQuad* quad = wrappable->ToImpl<DOMQuad>(); |
| WriteTag(kDOMQuadTag); |
| for (const DOMPoint* point : |
| {quad->p1(), quad->p2(), quad->p3(), quad->p4()}) { |
| WriteDouble(point->x()); |
| WriteDouble(point->y()); |
| WriteDouble(point->z()); |
| WriteDouble(point->w()); |
| } |
| return true; |
| } |
| if (wrapper_type_info == V8DOMMatrix::GetWrapperTypeInfo()) { |
| DOMMatrix* matrix = wrappable->ToImpl<DOMMatrix>(); |
| if (matrix->is2D()) { |
| WriteTag(kDOMMatrix2DTag); |
| WriteDouble(matrix->a()); |
| WriteDouble(matrix->b()); |
| WriteDouble(matrix->c()); |
| WriteDouble(matrix->d()); |
| WriteDouble(matrix->e()); |
| WriteDouble(matrix->f()); |
| } else { |
| WriteTag(kDOMMatrixTag); |
| WriteDouble(matrix->m11()); |
| WriteDouble(matrix->m12()); |
| WriteDouble(matrix->m13()); |
| WriteDouble(matrix->m14()); |
| WriteDouble(matrix->m21()); |
| WriteDouble(matrix->m22()); |
| WriteDouble(matrix->m23()); |
| WriteDouble(matrix->m24()); |
| WriteDouble(matrix->m31()); |
| WriteDouble(matrix->m32()); |
| WriteDouble(matrix->m33()); |
| WriteDouble(matrix->m34()); |
| WriteDouble(matrix->m41()); |
| WriteDouble(matrix->m42()); |
| WriteDouble(matrix->m43()); |
| WriteDouble(matrix->m44()); |
| } |
| return true; |
| } |
| if (wrapper_type_info == V8DOMMatrixReadOnly::GetWrapperTypeInfo()) { |
| DOMMatrixReadOnly* matrix = wrappable->ToImpl<DOMMatrixReadOnly>(); |
| if (matrix->is2D()) { |
| WriteTag(kDOMMatrix2DReadOnlyTag); |
| WriteDouble(matrix->a()); |
| WriteDouble(matrix->b()); |
| WriteDouble(matrix->c()); |
| WriteDouble(matrix->d()); |
| WriteDouble(matrix->e()); |
| WriteDouble(matrix->f()); |
| } else { |
| WriteTag(kDOMMatrixReadOnlyTag); |
| WriteDouble(matrix->m11()); |
| WriteDouble(matrix->m12()); |
| WriteDouble(matrix->m13()); |
| WriteDouble(matrix->m14()); |
| WriteDouble(matrix->m21()); |
| WriteDouble(matrix->m22()); |
| WriteDouble(matrix->m23()); |
| WriteDouble(matrix->m24()); |
| WriteDouble(matrix->m31()); |
| WriteDouble(matrix->m32()); |
| WriteDouble(matrix->m33()); |
| WriteDouble(matrix->m34()); |
| WriteDouble(matrix->m41()); |
| WriteDouble(matrix->m42()); |
| WriteDouble(matrix->m43()); |
| WriteDouble(matrix->m44()); |
| } |
| return true; |
| } |
| if (wrapper_type_info == V8MessagePort::GetWrapperTypeInfo()) { |
| MessagePort* message_port = wrappable->ToImpl<MessagePort>(); |
| size_t index = kNotFound; |
| if (transferables_) |
| index = transferables_->message_ports.Find(message_port); |
| if (index == kNotFound) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kDataCloneError, |
| "A MessagePort could not be cloned because it was not transferred."); |
| return false; |
| } |
| DCHECK_LE(index, std::numeric_limits<uint32_t>::max()); |
| WriteTag(kMessagePortTag); |
| WriteUint32(static_cast<uint32_t>(index)); |
| return true; |
| } |
| if (wrapper_type_info == V8MojoHandle::GetWrapperTypeInfo() && |
| RuntimeEnabledFeatures::MojoJSEnabled()) { |
| MojoHandle* mojo_handle = wrappable->ToImpl<MojoHandle>(); |
| size_t index = kNotFound; |
| if (transferables_) |
| index = transferables_->mojo_handles.Find(mojo_handle); |
| if (index == kNotFound) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kDataCloneError, |
| "A MojoHandle could not be cloned because it was not transferred."); |
| return false; |
| } |
| DCHECK_LE(index, std::numeric_limits<uint32_t>::max()); |
| serialized_script_value_->MojoHandles().push_back( |
| mojo_handle->TakeHandle()); |
| index = serialized_script_value_->MojoHandles().size() - 1; |
| WriteTag(kMojoHandleTag); |
| WriteUint32(static_cast<uint32_t>(index)); |
| return true; |
| } |
| if (wrapper_type_info == V8OffscreenCanvas::GetWrapperTypeInfo()) { |
| OffscreenCanvas* canvas = wrappable->ToImpl<OffscreenCanvas>(); |
| size_t index = kNotFound; |
| if (transferables_) |
| index = transferables_->offscreen_canvases.Find(canvas); |
| if (index == kNotFound) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kDataCloneError, |
| "An OffscreenCanvas could not be cloned " |
| "because it was not transferred."); |
| return false; |
| } |
| if (canvas->IsNeutered()) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kDataCloneError, |
| "An OffscreenCanvas could not be cloned because it was detached."); |
| return false; |
| } |
| if (canvas->RenderingContext()) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kDataCloneError, |
| "An OffscreenCanvas could not be cloned " |
| "because it had a rendering context."); |
| return false; |
| } |
| WriteTag(kOffscreenCanvasTransferTag); |
| WriteUint32(canvas->width()); |
| WriteUint32(canvas->height()); |
| WriteUint64(canvas->PlaceholderCanvasId()); |
| WriteUint32(canvas->ClientId()); |
| WriteUint32(canvas->SinkId()); |
| WriteUint32(canvas->FilterQuality() == kNone_SkFilterQuality ? 0 : 1); |
| return true; |
| } |
| if (wrapper_type_info == V8ReadableStream::GetWrapperTypeInfo() && |
| TransferableStreamsEnabled()) { |
| ReadableStream* stream = wrappable->ToImpl<ReadableStream>(); |
| size_t index = kNotFound; |
| if (transferables_) |
| index = transferables_->readable_streams.Find(stream); |
| if (index == kNotFound) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kDataCloneError, |
| "A ReadableStream could not be cloned " |
| "because it was not transferred."); |
| return false; |
| } |
| if (stream->IsLocked()) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kDataCloneError, |
| "A ReadableStream could not be cloned because it was locked"); |
| return false; |
| } |
| WriteTag(kReadableStreamTransferTag); |
| WriteUint32(static_cast<uint32_t>(index)); |
| return true; |
| } |
| if (wrapper_type_info == V8WritableStream::GetWrapperTypeInfo() && |
| TransferableStreamsEnabled()) { |
| WritableStream* stream = wrappable->ToImpl<WritableStream>(); |
| size_t index = kNotFound; |
| if (transferables_) |
| index = transferables_->writable_streams.Find(stream); |
| if (index == kNotFound) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kDataCloneError, |
| "A WritableStream could not be cloned " |
| "because it was not transferred."); |
| return false; |
| } |
| if (stream->locked()) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kDataCloneError, |
| "A WritableStream could not be cloned because it was locked"); |
| return false; |
| } |
| WriteTag(kWritableStreamTransferTag); |
| DCHECK(transferables_); |
| // The index calculation depends on the order that TransferReadableStreams |
| // and TransferWritableStreams are called in |
| // V8ScriptValueSerializer::FinalizeTransfer. |
| WriteUint32( |
| static_cast<uint32_t>(index + transferables_->readable_streams.size())); |
| return true; |
| } |
| if (wrapper_type_info == V8TransformStream::GetWrapperTypeInfo() && |
| TransferableStreamsEnabled()) { |
| TransformStream* stream = wrappable->ToImpl<TransformStream>(); |
| size_t index = kNotFound; |
| if (transferables_) |
| index = transferables_->transform_streams.Find(stream); |
| if (index == kNotFound) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kDataCloneError, |
| "A TransformStream could not be cloned " |
| "because it was not transferred."); |
| return false; |
| } |
| // https://streams.spec.whatwg.org/#ts-transfer |
| // 3. If ! IsReadableStreamLocked(readable) is true, throw a |
| // "DataCloneError" DOMException. |
| // 4. If ! IsWritableStreamLocked(writable) is true, throw a |
| // "DataCloneError" DOMException. |
| if (stream->Readable()->locked() || stream->Writable()->locked()) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kDataCloneError, |
| "A TransformStream could not be cloned because it was locked"); |
| return false; |
| } |
| WriteTag(kTransformStreamTransferTag); |
| DCHECK(transferables_); |
| // TransformStreams use two ports each. The stored index is the index of the |
| // first one. The first TransformStream is stored in the array after all the |
| // ReadableStreams and WritableStreams. |
| WriteUint32(static_cast<uint32_t>(index * 2 + |
| transferables_->readable_streams.size() + |
| transferables_->writable_streams.size())); |
| return true; |
| } |
| if (wrapper_type_info == V8DOMException::GetWrapperTypeInfo()) { |
| DOMException* exception = wrappable->ToImpl<DOMException>(); |
| WriteTag(kDOMExceptionTag); |
| WriteUTF8String(exception->name()); |
| WriteUTF8String(exception->message()); |
| // We may serialize the stack property in the future, so we store a null |
| // string in order to avoid future scheme changes. |
| String stack_unused; |
| WriteUTF8String(stack_unused); |
| return true; |
| } |
| return false; |
| } |
| |
| bool V8ScriptValueSerializer::WriteFile(File* file, |
| ExceptionState& exception_state) { |
| serialized_script_value_->BlobDataHandles().Set(file->Uuid(), |
| file->GetBlobDataHandle()); |
| if (blob_info_array_) { |
| size_t index = blob_info_array_->size(); |
| DCHECK_LE(index, std::numeric_limits<uint32_t>::max()); |
| blob_info_array_->emplace_back( |
| file->GetBlobDataHandle(), file->name(), file->type(), |
| file->LastModifiedTimeForSerialization(), file->size()); |
| WriteUint32(static_cast<uint32_t>(index)); |
| } else { |
| WriteUTF8String(file->HasBackingFile() ? file->GetPath() : g_empty_string); |
| WriteUTF8String(file->name()); |
| WriteUTF8String(file->webkitRelativePath()); |
| WriteUTF8String(file->Uuid()); |
| WriteUTF8String(file->type()); |
| // Historically we sometimes wouldn't write metadata. This next integer was |
| // 1 or 0 to indicate if metadata is present. Now we always write metadata, |
| // hence always have this hardcoded 1. |
| WriteUint32(1); |
| WriteUint64(file->size()); |
| base::Optional<base::Time> last_modified = |
| file->LastModifiedTimeForSerialization(); |
| WriteDouble(last_modified ? last_modified->ToJsTimeIgnoringNull() |
| : std::numeric_limits<double>::quiet_NaN()); |
| WriteUint32(file->GetUserVisibility() == File::kIsUserVisible ? 1 : 0); |
| } |
| return true; |
| } |
| |
| void V8ScriptValueSerializer::ThrowDataCloneError( |
| v8::Local<v8::String> v8_message) { |
| DCHECK(exception_state_); |
| ExceptionState exception_state( |
| script_state_->GetIsolate(), exception_state_->Context(), |
| exception_state_->InterfaceName(), exception_state_->PropertyName()); |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kDataCloneError, |
| ToBlinkString<String>(v8_message, kDoNotExternalize)); |
| } |
| |
| v8::Maybe<bool> V8ScriptValueSerializer::WriteHostObject( |
| v8::Isolate* isolate, |
| v8::Local<v8::Object> object) { |
| DCHECK(exception_state_); |
| DCHECK_EQ(isolate, script_state_->GetIsolate()); |
| ExceptionState exception_state(isolate, exception_state_->Context(), |
| exception_state_->InterfaceName(), |
| exception_state_->PropertyName()); |
| |
| if (!V8DOMWrapper::IsWrapper(isolate, object)) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kDataCloneError, |
| "An object could not be cloned."); |
| return v8::Nothing<bool>(); |
| } |
| ScriptWrappable* wrappable = ToScriptWrappable(object); |
| bool wrote_dom_object = WriteDOMObject(wrappable, exception_state); |
| if (wrote_dom_object) { |
| DCHECK(!exception_state.HadException()); |
| return v8::Just(true); |
| } |
| if (!exception_state.HadException()) { |
| StringView interface = wrappable->GetWrapperTypeInfo()->interface_name; |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kDataCloneError, |
| interface + " object could not be cloned."); |
| } |
| return v8::Nothing<bool>(); |
| } |
| |
| v8::Maybe<uint32_t> V8ScriptValueSerializer::GetSharedArrayBufferId( |
| v8::Isolate* isolate, |
| v8::Local<v8::SharedArrayBuffer> v8_shared_array_buffer) { |
| if (for_storage_) { |
| DCHECK(exception_state_); |
| DCHECK_EQ(isolate, script_state_->GetIsolate()); |
| ExceptionState exception_state(isolate, exception_state_->Context(), |
| exception_state_->InterfaceName(), |
| exception_state_->PropertyName()); |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kDataCloneError, |
| "A SharedArrayBuffer can not be serialized for storage."); |
| return v8::Nothing<uint32_t>(); |
| } |
| |
| DOMSharedArrayBuffer* shared_array_buffer = |
| V8SharedArrayBuffer::ToImpl(v8_shared_array_buffer); |
| |
| // The index returned from this function will be serialized into the data |
| // stream. When deserializing, this will be used to index into the |
| // sharedArrayBufferContents array of the SerializedScriptValue. |
| uint32_t index = shared_array_buffers_.Find(shared_array_buffer); |
| if (index == kNotFound) { |
| shared_array_buffers_.push_back(shared_array_buffer); |
| index = shared_array_buffers_.size() - 1; |
| } |
| return v8::Just<uint32_t>(index); |
| } |
| |
| v8::Maybe<uint32_t> V8ScriptValueSerializer::GetWasmModuleTransferId( |
| v8::Isolate* isolate, |
| v8::Local<v8::WasmModuleObject> module) { |
| if (for_storage_) { |
| DCHECK(exception_state_); |
| DCHECK_EQ(isolate, script_state_->GetIsolate()); |
| ExceptionState exception_state(isolate, exception_state_->Context(), |
| exception_state_->InterfaceName(), |
| exception_state_->PropertyName()); |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kDataCloneError, |
| "A WebAssembly.Module can not be serialized for storage."); |
| return v8::Nothing<uint32_t>(); |
| } |
| |
| switch (wasm_policy_) { |
| case Options::kSerialize: |
| return v8::Nothing<uint32_t>(); |
| |
| case Options::kBlockedInNonSecureContext: { |
| // This happens, currently, when we try to serialize to IndexedDB |
| // in an non-secure context. |
| ExceptionState exception_state(isolate, exception_state_->Context(), |
| exception_state_->InterfaceName(), |
| exception_state_->PropertyName()); |
| exception_state.ThrowDOMException(DOMExceptionCode::kDataCloneError, |
| "Serializing WebAssembly modules in " |
| "non-secure contexts is not allowed."); |
| return v8::Nothing<uint32_t>(); |
| } |
| |
| case Options::kTransfer: { |
| // We don't expect scenarios with numerous wasm modules being transferred |
| // around. Most likely, we'll have one module. The vector approach is |
| // simple and should perform sufficiently well under these expectations. |
| serialized_script_value_->WasmModules().push_back( |
| module->GetCompiledModule()); |
| if (!serialized_script_value_->origin()) { |
| // Store the |SecurityOrigin| of the current |ExecutionContext| to count |
| // during deserialization if the WebAssembly module got transferred |
| // cross-origin. |
| serialized_script_value_->set_origin( |
| ExecutionContext::From(script_state_)->GetSecurityOrigin()); |
| } |
| uint32_t size = |
| static_cast<uint32_t>(serialized_script_value_->WasmModules().size()); |
| DCHECK_GE(size, 1u); |
| return v8::Just(size - 1); |
| } |
| |
| case Options::kUnspecified: |
| NOTREACHED(); |
| } |
| return v8::Nothing<uint32_t>(); |
| } |
| |
| void* V8ScriptValueSerializer::ReallocateBufferMemory(void* old_buffer, |
| size_t size, |
| size_t* actual_size) { |
| *actual_size = WTF::Partitions::BufferPotentialCapacity(size); |
| return WTF::Partitions::BufferTryRealloc(old_buffer, *actual_size, |
| "SerializedScriptValue buffer"); |
| } |
| |
| void V8ScriptValueSerializer::FreeBufferMemory(void* buffer) { |
| return WTF::Partitions::BufferFree(buffer); |
| } |
| |
| bool V8ScriptValueSerializer::TransferableStreamsEnabled() const { |
| return RuntimeEnabledFeatures::TransferableStreamsEnabled( |
| ExecutionContext::From(script_state_)); |
| } |
| |
| } // namespace blink |