blob: ea1063cd83e39567c066c153f96c39455ad1c81d [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 IDBValueWrapping_h
#define IDBValueWrapping_h
#include "base/memory/scoped_refptr.h"
#include "bindings/core/v8/ExceptionState.h"
#include "bindings/core/v8/serialization/SerializedScriptValue.h"
#include "modules/ModulesExport.h"
#include "platform/SharedBuffer.h"
#include "platform/wtf/Allocator.h"
#include "platform/wtf/Vector.h"
#include "public/platform/WebBlobInfo.h"
#include "v8/include/v8.h"
namespace blink {
class Blob;
class BlobDataHandle;
class ExceptionState;
class IDBValue;
class ScriptState;
class ScriptValue;
class SerializedScriptValue;
class SharedBuffer;
// Logic for serializing V8 values for storage in IndexedDB.
//
// 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.
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 WrapIfBiggerThan().
void Clone(ScriptState*, ScriptValue* clone);
// 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 ExtractWireBytes() and cannot be called
// after ExtractWireBytes().
bool WrapIfBiggerThan(unsigned max_bytes);
// Obtains the BlobDataHandles from the serialized value's Blob array.
//
// This method must be called at most once, and must be called after
// WrapIfBiggerThan().
void ExtractBlobDataHandles(
Vector<scoped_refptr<BlobDataHandle>>* blob_data_handles);
// 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> ExtractWireBytes();
// Obtains WebBlobInfos for the serialized value's Blob array.
//
// This method must be called at most once, and must be called after
// WrapIfBiggerThan().
inline Vector<WebBlobInfo>& WrappedBlobInfo() {
#if DCHECK_IS_ON()
DCHECK(!had_exception_)
<< "WrapBlobInfo() called on wrapper with serialization exception";
#endif // DCHECK_IS_ON()
return 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);
private:
scoped_refptr<SerializedScriptValue> serialized_value_;
scoped_refptr<BlobDataHandle> wrapper_handle_;
Vector<WebBlobInfo> blob_info_;
Vector<char> wire_bytes_;
size_t original_data_length_ = 0;
#if DCHECK_IS_ON()
bool had_exception_ = false;
bool wrap_called_ = false;
#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<scoped_refptr<IDBValue>>&);
// Pieces together an unwrapped IDBValue from a wrapped value and Blob data.
static scoped_refptr<IDBValue> Unwrap(
IDBValue* wrapped_value,
scoped_refptr<SharedBuffer>&& wrapper_blob_content);
// 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 IDBValueUnwrapperReadVarintTestHelper;
// Used to deserialize the wrapped value.
bool ReadVarint(unsigned& value);
// 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 // IDBValueWrapping_h