blob: b2ff10e3127d2bac60ea7830ff7b1618cc4dd1f0 [file] [log] [blame]
// 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_