| // Copyright 2017 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. |
| |
| #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_INDEXEDDB_IDB_VALUE_WRAPPING_H_ |
| #define THIRD_PARTY_BLINK_RENDERER_MODULES_INDEXEDDB_IDB_VALUE_WRAPPING_H_ |
| |
| #include "base/feature_list.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "third_party/blink/public/platform/web_blob_info.h" |
| #include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.h" |
| #include "third_party/blink/renderer/modules/modules_export.h" |
| #include "third_party/blink/renderer/platform/bindings/exception_state.h" |
| #include "third_party/blink/renderer/platform/shared_buffer.h" |
| #include "third_party/blink/renderer/platform/wtf/allocator.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_view.h" |
| #include "third_party/blink/renderer/platform/wtf/vector.h" |
| #include "v8/include/v8.h" |
| |
| namespace blink { |
| |
| class Blob; |
| class BlobDataHandle; |
| class ExceptionState; |
| class IDBValue; |
| class ScriptState; |
| class ScriptValue; |
| class SerializedScriptValue; |
| class SharedBuffer; |
| |
| const base::Feature kIndexedDBLargeValueWrapping{ |
| "IndexedDBLargeValueWrapping", base::FEATURE_DISABLED_BY_DEFAULT}; |
| |
| // Logic for serializing V8 values for storage in IndexedDB. |
| // |
| // An IDBValueWrapper instance drives the serialization of a single V8 value to |
| // IndexedDB. An instance's lifecycle goes through the following stages: |
| // 1) Cloning - Right after an instance is constructed, its internal |
| // representation is optimized for structured cloning via the Clone() method. |
| // This may be necessary when extracting the primary key and/or index keys |
| // for the serialized value. |
| // 2) Wrapping - DoneCloning() transitions the instance to an internal |
| // representation optimized for wrapping via WrapIfBiggerThan(). |
| // 3) Reading results - After any desired wrapping is performed, the Take*() |
| // methods yield the serialized value components passed to the backing store. |
| // To avoid unnecessary copies, the Take*() methods move out parts of the |
| // internal representation, so each Take*() method can be called at most |
| // once. |
| // |
| // Example usage: |
| // auto wrapper = new IDBValueWrapper(); |
| // wrapper.Clone(...); // Structured clone used to extract keys. |
| // wrapper.DoneCloning(); |
| // wrapper.WrapIfBiggerThan(kWrapThreshold); |
| // wrapper.TakeWireBytes(); |
| // wrapper.TakeBlobDataHandles(); |
| // wrapper.TakeBlobInfo(); |
| // |
| // V8 values are stored on disk using the format implemented in |
| // SerializedScriptValue (SSV), which is essentialy a byte array plus an array |
| // of attached Blobs. For "normal" (not too large) V8 values, the SSV output's |
| // byte array is stored directly in IndexedDB's backing store, together with |
| // references to the attached Blobs. |
| // |
| // "Large" V8 values are wrapped in Blobs, in order to avoid operating the |
| // backing store in a sub-optimal region. Specifically, the byte array in the |
| // SSV output is replaced with a "wrapped value" marker, and stored inside a |
| // Blob that is tacked to the end of the SSV's Blob array. IndexedDB's backing |
| // store receives the "wrapped value" marker and the references to the Blobs, |
| // while the large byte array in the SSV output is handled by the Blob storage |
| // system. |
| // |
| // In summary: |
| // "normal" v8::Value -> SSV -> IDBValue (stores SSV output) -> LevelDB |
| // "large" v8::Value -> SSV -> IDBValue (stores SSV output) -> |
| // Blob (stores SSV output) + IDBValue (stores Blob reference) -> LevelDB |
| // |
| // Full picture that accounts for Blob attachments: |
| // "normal" v8::Value -> SSV (byte array, Blob attachments) -> |
| // IDBValue (bytes: SSV byte array, blobs: SSV Blob attachments) -> LevelDB |
| // "large" v8::Value -> SSV (byte array, Blob attachments) -> |
| // IDBValue (bytes: "wrapped value" marker, |
| // blobs: SSV Blob attachments + [wrapper Blob(SSV byte array)] -> |
| // LevelDB |
| class MODULES_EXPORT IDBValueWrapper { |
| STACK_ALLOCATED(); |
| |
| public: |
| // Wrapper for an IndexedDB value. |
| // |
| // The serialization process can throw an exception. The caller is responsible |
| // for checking exception_state. |
| // |
| // The wrapper's internal representation is optimized for cloning the |
| // serialized value. DoneCloning() must be called to transition to an internal |
| // representation optimized for writing. |
| IDBValueWrapper( |
| v8::Isolate*, |
| v8::Local<v8::Value>, |
| SerializedScriptValue::SerializeOptions::WasmSerializationPolicy, |
| ExceptionState&); |
| ~IDBValueWrapper(); |
| |
| // Creates a clone of the serialized value. |
| // |
| // This method is used to fulfill the IndexedDB specification requirement that |
| // a value's key and index keys are extracted from a structured clone of the |
| // value, which avoids the issue of side-effects in custom getters. |
| // |
| // This method cannot be called after DoneCloning(). |
| void Clone(ScriptState*, ScriptValue* clone); |
| |
| // Optimizes the serialized value's internal representation for writing to |
| // disk. |
| // |
| // This must be called before Take*() methods can be called. After this method |
| // is called, Clone() cannot be called anymore. |
| void DoneCloning(); |
| |
| // Conditionally wraps the serialized value's byte array into a Blob. |
| // |
| // The byte array is wrapped if its size exceeds max_bytes. In production, the |
| // max_bytes threshold is currently always kWrapThreshold. |
| // |
| // This method must be called before the Take*() methods are called. |
| bool WrapIfBiggerThan(unsigned max_bytes); |
| |
| // Obtains the byte array for the serialized value. |
| // |
| // This method must be called at most once, and must be called after |
| // WrapIfBiggerThan(). |
| scoped_refptr<SharedBuffer> TakeWireBytes(); |
| |
| // Obtains the BlobDataHandles from the serialized value's Blob array. |
| // |
| // This method must be called at most once, and must be called after |
| // DoneCloning(). |
| Vector<scoped_refptr<BlobDataHandle>> TakeBlobDataHandles() { |
| #if DCHECK_IS_ON() |
| DCHECK(done_cloning_) << __func__ << " called before DoneCloning()"; |
| DCHECK(owns_blob_handles_) << __func__ << " called twice"; |
| owns_blob_handles_ = false; |
| #endif // DCHECK_IS_ON() |
| |
| return std::move(blob_handles_); |
| } |
| |
| // Obtains WebBlobInfos for the serialized value's Blob array. |
| // |
| // This method must be called at most once, and must be called after |
| // DoneCloning(). |
| inline Vector<WebBlobInfo> TakeBlobInfo() { |
| #if DCHECK_IS_ON() |
| DCHECK(done_cloning_) << __func__ << " called before DoneCloning()"; |
| DCHECK(owns_blob_info_) << __func__ << " called twice"; |
| owns_blob_info_ = false; |
| #endif // DCHECK_IS_ON() |
| return std::move(blob_info_); |
| } |
| |
| size_t DataLengthBeforeWrapInBytes() { return original_data_length_; } |
| |
| // Default threshold for WrapIfBiggerThan(). |
| // |
| // This should be tuned to achieve a compromise between short-term IndexedDB |
| // throughput and long-term I/O load and memory usage. LevelDB, the underlying |
| // storage for IndexedDB, was not designed with large values in mind. At the |
| // very least, large values will slow down compaction, causing occasional I/O |
| // spikes. |
| static constexpr unsigned kWrapThreshold = 64 * 1024; |
| |
| // MIME type used for Blobs that wrap IDBValues. |
| static constexpr const char* kWrapMimeType = |
| "application/vnd.blink-idb-value-wrapper"; |
| |
| // Used to serialize the wrapped value. Exposed for testing. |
| static void WriteVarInt(unsigned value, Vector<char>& output); |
| static void WriteBytes(const Vector<uint8_t>& bytes, Vector<char>& output); |
| |
| private: |
| // V8 value serialization state. |
| scoped_refptr<SerializedScriptValue> serialized_value_; |
| Vector<scoped_refptr<BlobDataHandle>> blob_handles_; |
| Vector<WebBlobInfo> blob_info_; |
| |
| // Buffer for wire data that is not stored in SerializedScriptValue. |
| // |
| // This buffer ends up storing metadata generated by wrapping operations. |
| Vector<char> wire_data_buffer_; |
| |
| // Points into SerializedScriptValue's data buffer, or into wire_data_buffer_. |
| base::span<const uint8_t> wire_data_; |
| |
| size_t original_data_length_ = 0; |
| |
| #if DCHECK_IS_ON() |
| // Accounting for lifecycle stages. |
| bool had_exception_ = false; |
| bool done_cloning_ = false; |
| bool owns_blob_handles_ = true; |
| bool owns_blob_info_ = true; |
| bool owns_wire_bytes_ = true; |
| #endif // DCHECK_IS_ON() |
| }; |
| |
| // State and logic for unwrapping large IndexedDB values from Blobs. |
| // |
| // See IDBValueWrapper for an explanation of the wrapping concept. |
| // |
| // Once created, an IDBValueUnwrapper instance can be used to unwrap multiple |
| // Blobs. For each Blob to be unwrapped, the caller should first call Parse(). |
| // If the method succeeds, the IDBValueUnwrapper will store the parse state, |
| // which can be obtained using WrapperBlobSize() and WrapperBlobHandle(). |
| class MODULES_EXPORT IDBValueUnwrapper { |
| STACK_ALLOCATED(); |
| |
| public: |
| IDBValueUnwrapper(); |
| |
| // True if the IDBValue's data was wrapped in a Blob. |
| static bool IsWrapped(IDBValue*); |
| |
| // True if at least one of the IDBValues' data was wrapped in a Blob. |
| static bool IsWrapped(const Vector<std::unique_ptr<IDBValue>>&); |
| |
| // Unwraps an IDBValue that has wrapped Blob data. |
| // |
| // The caller should own the IDBValue (have a std::unique_ptr for it). |
| static void Unwrap(scoped_refptr<SharedBuffer>&& wrapper_blob_content, |
| IDBValue* wrapped_value); |
| |
| // Parses the wrapper Blob information from a wrapped IDBValue. |
| // |
| // Returns true for success, and false for failure. Failure can mean that the |
| // given value was not a wrapped IDBValue, or that the value bytes were |
| // corrupted. |
| bool Parse(IDBValue*); |
| |
| // Returns the size of the Blob obtained by the last Unwrap() call. |
| // |
| // Should only be called after a successful result from Unwrap(). |
| inline unsigned WrapperBlobSize() const { |
| DCHECK(end_); |
| return blob_size_; |
| } |
| |
| // Returns a handle to the Blob obtained by the last Unwrap() call. |
| // |
| // Should only be called exactly once after a successful result from Unwrap(). |
| scoped_refptr<BlobDataHandle> WrapperBlobHandle(); |
| |
| private: |
| // Only present in tests. |
| friend class IDBValueUnwrapperReadTestHelper; |
| |
| // Used to deserialize the wrapped value. |
| bool ReadVarInt(unsigned&); |
| bool ReadBytes(Vector<uint8_t>&); |
| |
| // Resets the parsing state. |
| bool Reset(); |
| |
| // Deserialization cursor in the SharedBuffer of the IDBValue being unwrapped. |
| const uint8_t* current_; |
| |
| // Smallest invalid position_ value. |
| const uint8_t* end_; |
| |
| // The size of the Blob holding the data for the last unwrapped IDBValue. |
| unsigned blob_size_; |
| |
| // Handle to the Blob holding the data for the last unwrapped IDBValue. |
| scoped_refptr<BlobDataHandle> blob_handle_; |
| }; |
| |
| } // namespace blink |
| |
| #endif // THIRD_PARTY_BLINK_RENDERER_MODULES_INDEXEDDB_IDB_VALUE_WRAPPING_H_ |