diff options
Diffstat (limited to 'dom/indexedDB/IndexedDatabase.cpp')
-rw-r--r-- | dom/indexedDB/IndexedDatabase.cpp | 488 |
1 files changed, 488 insertions, 0 deletions
diff --git a/dom/indexedDB/IndexedDatabase.cpp b/dom/indexedDB/IndexedDatabase.cpp new file mode 100644 index 0000000000..a73d9fbf5a --- /dev/null +++ b/dom/indexedDB/IndexedDatabase.cpp @@ -0,0 +1,488 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/IndexedDatabase.h" +#include "IndexedDatabaseInlines.h" + +#include "IDBDatabase.h" + +#include "mozilla/dom/FileBlobImpl.h" +#include "mozilla/dom/StructuredCloneTags.h" +#include "mozilla/dom/URLSearchParams.h" +#include "mozilla/dom/WorkerScope.h" +#include "MainThreadUtils.h" +#include "jsapi.h" +#include "nsIFile.h" +#include "nsIGlobalObject.h" +#include "nsQueryObject.h" +#include "nsString.h" + +namespace mozilla::dom::indexedDB { +namespace { +struct MOZ_STACK_CLASS MutableFileData final { + nsString type; + nsString name; + + MOZ_COUNTED_DEFAULT_CTOR(MutableFileData) + + MOZ_COUNTED_DTOR(MutableFileData) +}; + +struct MOZ_STACK_CLASS BlobOrFileData final { + uint32_t tag = 0; + uint64_t size = 0; + nsString type; + nsString name; + int64_t lastModifiedDate = INT64_MAX; + + MOZ_COUNTED_DEFAULT_CTOR(BlobOrFileData) + + MOZ_COUNTED_DTOR(BlobOrFileData) +}; + +struct MOZ_STACK_CLASS WasmModuleData final { + uint32_t bytecodeIndex; + uint32_t compiledIndex; + uint32_t flags; + + explicit WasmModuleData(uint32_t aFlags) + : bytecodeIndex(0), compiledIndex(0), flags(aFlags) { + MOZ_COUNT_CTOR(WasmModuleData); + } + + MOZ_COUNTED_DTOR(WasmModuleData) +}; + +bool StructuredCloneReadString(JSStructuredCloneReader* aReader, + nsCString& aString) { + uint32_t length; + if (!JS_ReadBytes(aReader, &length, sizeof(uint32_t))) { + NS_WARNING("Failed to read length!"); + return false; + } + length = NativeEndian::swapFromLittleEndian(length); + + if (!aString.SetLength(length, fallible)) { + NS_WARNING("Out of memory?"); + return false; + } + char* const buffer = aString.BeginWriting(); + + if (!JS_ReadBytes(aReader, buffer, length)) { + NS_WARNING("Failed to read type!"); + return false; + } + + return true; +} + +bool ReadFileHandle(JSStructuredCloneReader* aReader, + MutableFileData* aRetval) { + static_assert(SCTAG_DOM_MUTABLEFILE == 0xFFFF8004, "Update me!"); + MOZ_ASSERT(aReader && aRetval); + + nsCString type; + if (!StructuredCloneReadString(aReader, type)) { + return false; + } + CopyUTF8toUTF16(type, aRetval->type); + + nsCString name; + if (!StructuredCloneReadString(aReader, name)) { + return false; + } + CopyUTF8toUTF16(name, aRetval->name); + + return true; +} + +bool ReadBlobOrFile(JSStructuredCloneReader* aReader, uint32_t aTag, + BlobOrFileData* aRetval) { + static_assert(SCTAG_DOM_BLOB == 0xffff8001 && + SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE == 0xffff8002 && + SCTAG_DOM_FILE == 0xffff8005, + "Update me!"); + + MOZ_ASSERT(aReader); + MOZ_ASSERT(aTag == SCTAG_DOM_FILE || + aTag == SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE || + aTag == SCTAG_DOM_BLOB); + MOZ_ASSERT(aRetval); + + aRetval->tag = aTag; + + uint64_t size; + if (NS_WARN_IF(!JS_ReadBytes(aReader, &size, sizeof(uint64_t)))) { + return false; + } + + aRetval->size = NativeEndian::swapFromLittleEndian(size); + + nsCString type; + if (NS_WARN_IF(!StructuredCloneReadString(aReader, type))) { + return false; + } + + CopyUTF8toUTF16(type, aRetval->type); + + // Blobs are done. + if (aTag == SCTAG_DOM_BLOB) { + return true; + } + + MOZ_ASSERT(aTag == SCTAG_DOM_FILE || + aTag == SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE); + + int64_t lastModifiedDate; + if (aTag == SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE) { + lastModifiedDate = INT64_MAX; + } else { + if (NS_WARN_IF(!JS_ReadBytes(aReader, &lastModifiedDate, + sizeof(lastModifiedDate)))) { + return false; + } + lastModifiedDate = NativeEndian::swapFromLittleEndian(lastModifiedDate); + } + + aRetval->lastModifiedDate = lastModifiedDate; + + nsCString name; + if (NS_WARN_IF(!StructuredCloneReadString(aReader, name))) { + return false; + } + + CopyUTF8toUTF16(name, aRetval->name); + + return true; +} + +bool ReadWasmModule(JSStructuredCloneReader* aReader, WasmModuleData* aRetval) { + static_assert(SCTAG_DOM_WASM_MODULE == 0xFFFF8006, "Update me!"); + MOZ_ASSERT(aReader && aRetval); + + uint32_t bytecodeIndex; + uint32_t compiledIndex; + if (NS_WARN_IF(!JS_ReadUint32Pair(aReader, &bytecodeIndex, &compiledIndex))) { + return false; + } + + aRetval->bytecodeIndex = bytecodeIndex; + aRetval->compiledIndex = compiledIndex; + + return true; +} + +template <typename StructuredCloneFile> +class ValueDeserializationHelper; + +class ValueDeserializationHelperBase { + public: + static bool CreateAndWrapWasmModule(JSContext* aCx, + const StructuredCloneFileBase& aFile, + const WasmModuleData& aData, + JS::MutableHandle<JSObject*> aResult) { + MOZ_ASSERT(aCx); + MOZ_ASSERT(aFile.Type() == StructuredCloneFileBase::eWasmBytecode); + + // Both on the parent and child side, just create a plain object here, + // support for de-serialization of WebAssembly.Modules has been removed in + // bug 1561876. Full removal is tracked in bug 1487479. + + JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx)); + if (NS_WARN_IF(!obj)) { + return false; + } + + aResult.set(obj); + return true; + } + + template <typename StructuredCloneFile> + static bool CreateAndWrapBlobOrFile(JSContext* aCx, IDBDatabase* aDatabase, + const StructuredCloneFile& aFile, + const BlobOrFileData& aData, + JS::MutableHandle<JSObject*> aResult) { + MOZ_ASSERT(aCx); + MOZ_ASSERT(aData.tag == SCTAG_DOM_FILE || + aData.tag == SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE || + aData.tag == SCTAG_DOM_BLOB); + MOZ_ASSERT(aFile.Type() == StructuredCloneFileBase::eBlob); + + const auto blob = ValueDeserializationHelper<StructuredCloneFile>::GetBlob( + aCx, aDatabase, aFile); + if (NS_WARN_IF(!blob)) { + return false; + } + + if (aData.tag == SCTAG_DOM_BLOB) { + blob->Impl()->SetLazyData(VoidString(), aData.type, aData.size, + INT64_MAX); + MOZ_ASSERT(!blob->IsFile()); + + // XXX The comment below is somewhat confusing, since it seems to imply + // that this branch is only executed when called from ActorsParent, but + // it's executed from both the parent and the child side code. + + // ActorsParent sends here a kind of half blob and half file wrapped into + // a DOM File object. DOM File and DOM Blob are a WebIDL wrapper around a + // BlobImpl object. SetLazyData() has just changed the BlobImpl to be a + // Blob (see the previous assert), but 'blob' still has the WebIDL DOM + // File wrapping. + // Before exposing it to content, we must recreate a DOM Blob object. + + const RefPtr<Blob> exposedBlob = + Blob::Create(blob->GetParentObject(), blob->Impl()); + if (NS_WARN_IF(!exposedBlob)) { + return false; + } + + return WrapAsJSObject(aCx, exposedBlob, aResult); + } + + blob->Impl()->SetLazyData(aData.name, aData.type, aData.size, + aData.lastModifiedDate * PR_USEC_PER_MSEC); + + MOZ_ASSERT(blob->IsFile()); + const RefPtr<File> file = blob->ToFile(); + MOZ_ASSERT(file); + + return WrapAsJSObject(aCx, file, aResult); + } +}; + +template <> +class ValueDeserializationHelper<StructuredCloneFileParent> + : public ValueDeserializationHelperBase { + public: + static bool CreateAndWrapMutableFile(JSContext* aCx, + StructuredCloneFileParent& aFile, + const MutableFileData& aData, + JS::MutableHandle<JSObject*> aResult) { + MOZ_ASSERT(aCx); + MOZ_ASSERT(aFile.Type() == StructuredCloneFileBase::eBlob); + + // We are in an IDB SQLite schema upgrade where we don't care about a real + // 'MutableFile', but we just care of having a proper |mType| flag. + + aFile.MutateType(StructuredCloneFileBase::eMutableFile); + + // Just make a dummy object. + JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx)); + + if (NS_WARN_IF(!obj)) { + return false; + } + + aResult.set(obj); + return true; + } + + static RefPtr<Blob> GetBlob(JSContext* aCx, IDBDatabase* aDatabase, + const StructuredCloneFileParent& aFile) { + // This is chrome code, so there is no parent, but still we want to set a + // correct parent for the new File object. + const auto global = [aDatabase, aCx]() -> nsCOMPtr<nsIGlobalObject> { + if (NS_IsMainThread()) { + if (aDatabase && aDatabase->GetParentObject()) { + return aDatabase->GetParentObject(); + } + return xpc::CurrentNativeGlobal(aCx); + } + const WorkerPrivate* const workerPrivate = + GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(workerPrivate); + + WorkerGlobalScope* const globalScope = workerPrivate->GlobalScope(); + MOZ_ASSERT(globalScope); + + return do_QueryObject(globalScope); + }(); + + MOZ_ASSERT(global); + + // We do not have an mBlob but do have a DatabaseFileInfo. + // + // If we are creating an index, we do need a real-looking Blob/File instance + // because the index's key path can reference their properties. Rather than + // create a fake-looking object, create a real Blob. + // + // If we are in a schema upgrade, we don't strictly need that, but we do not + // need to optimize for that, and create it anyway. + const nsCOMPtr<nsIFile> file = aFile.FileInfo().GetFileForFileInfo(); + if (!file) { + return nullptr; + } + + const auto impl = MakeRefPtr<FileBlobImpl>(file); + impl->SetFileId(aFile.FileInfo().Id()); + return File::Create(global, impl); + } +}; + +template <> +class ValueDeserializationHelper<StructuredCloneFileChild> + : public ValueDeserializationHelperBase { + public: + static bool CreateAndWrapMutableFile(JSContext* aCx, + StructuredCloneFileChild& aFile, + const MutableFileData& aData, + JS::MutableHandle<JSObject*> aResult) { + MOZ_ASSERT(aCx); + MOZ_ASSERT(aFile.Type() == StructuredCloneFileBase::eMutableFile); + + return false; + } + + static RefPtr<Blob> GetBlob(JSContext* aCx, IDBDatabase* aDatabase, + const StructuredCloneFileChild& aFile) { + if (aFile.HasBlob()) { + return aFile.BlobPtr(); + } + + MOZ_CRASH("Expected a StructuredCloneFile with a Blob"); + } +}; + +} // namespace + +template <typename StructuredCloneReadInfo> +JSObject* CommonStructuredCloneReadCallback( + JSContext* aCx, JSStructuredCloneReader* aReader, + const JS::CloneDataPolicy& aCloneDataPolicy, uint32_t aTag, uint32_t aData, + StructuredCloneReadInfo* aCloneReadInfo, IDBDatabase* aDatabase) { + // We need to statically assert that our tag values are what we expect + // so that if people accidentally change them they notice. + static_assert(SCTAG_DOM_BLOB == 0xffff8001 && + SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE == 0xffff8002 && + SCTAG_DOM_MUTABLEFILE == 0xffff8004 && + SCTAG_DOM_FILE == 0xffff8005 && + SCTAG_DOM_WASM_MODULE == 0xffff8006 && + SCTAG_DOM_URLSEARCHPARAMS == 0xffff8014, + "You changed our structured clone tag values and just ate " + "everyone's IndexedDB data. I hope you are happy."); + + if (aTag == SCTAG_DOM_URLSEARCHPARAMS) { + // Protect the result from a moving GC in ~RefPtr. + JS::Rooted<JSObject*> result(aCx); + + { + // Scope for the RefPtr below. + + nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx); + if (!global) { + return nullptr; + } + + RefPtr<URLSearchParams> params = new URLSearchParams(global); + + uint32_t paramCount; + uint32_t zero; + if (!JS_ReadUint32Pair(aReader, ¶mCount, &zero)) { + return nullptr; + } + + nsAutoString key; + nsAutoString value; + for (uint32_t index = 0; index < paramCount; index++) { + if (!StructuredCloneHolder::ReadString(aReader, key) || + !StructuredCloneHolder::ReadString(aReader, value)) { + return nullptr; + } + params->Append(key, value); + } + + if (!WrapAsJSObject(aCx, params, &result)) { + return nullptr; + } + } + + return result; + } + + using StructuredCloneFile = + typename StructuredCloneReadInfo::StructuredCloneFile; + + if (aTag == SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE || + aTag == SCTAG_DOM_BLOB || aTag == SCTAG_DOM_FILE || + aTag == SCTAG_DOM_MUTABLEFILE || aTag == SCTAG_DOM_WASM_MODULE) { + JS::Rooted<JSObject*> result(aCx); + + if (aTag == SCTAG_DOM_WASM_MODULE) { + WasmModuleData data(aData); + if (NS_WARN_IF(!ReadWasmModule(aReader, &data))) { + return nullptr; + } + + MOZ_ASSERT(data.compiledIndex == data.bytecodeIndex + 1); + MOZ_ASSERT(!data.flags); + + const auto& files = aCloneReadInfo->Files(); + if (data.bytecodeIndex >= files.Length() || + data.compiledIndex >= files.Length()) { + MOZ_ASSERT(false, "Bad index value!"); + return nullptr; + } + + const auto& file = files[data.bytecodeIndex]; + + if (NS_WARN_IF(!ValueDeserializationHelper<StructuredCloneFile>:: + CreateAndWrapWasmModule(aCx, file, data, &result))) { + return nullptr; + } + + return result; + } + + if (aData >= aCloneReadInfo->Files().Length()) { + MOZ_ASSERT(false, "Bad index value!"); + return nullptr; + } + + auto& file = aCloneReadInfo->MutableFile(aData); + + if (aTag == SCTAG_DOM_MUTABLEFILE) { + MutableFileData data; + if (NS_WARN_IF(!ReadFileHandle(aReader, &data))) { + return nullptr; + } + + if (NS_WARN_IF(!ValueDeserializationHelper<StructuredCloneFile>:: + CreateAndWrapMutableFile(aCx, file, data, &result))) { + return nullptr; + } + + return result; + } + + BlobOrFileData data; + if (NS_WARN_IF(!ReadBlobOrFile(aReader, aTag, &data))) { + return nullptr; + } + + if (NS_WARN_IF(!ValueDeserializationHelper< + StructuredCloneFile>::CreateAndWrapBlobOrFile(aCx, aDatabase, + file, data, + &result))) { + return nullptr; + } + + return result; + } + + return StructuredCloneHolder::ReadFullySerializableObjects(aCx, aReader, aTag, + true); +} + +template JSObject* CommonStructuredCloneReadCallback( + JSContext* aCx, JSStructuredCloneReader* aReader, + const JS::CloneDataPolicy& aCloneDataPolicy, uint32_t aTag, uint32_t aData, + StructuredCloneReadInfoChild* aCloneReadInfo, IDBDatabase* aDatabase); + +template JSObject* CommonStructuredCloneReadCallback( + JSContext* aCx, JSStructuredCloneReader* aReader, + const JS::CloneDataPolicy& aCloneDataPolicy, uint32_t aTag, uint32_t aData, + StructuredCloneReadInfoParent* aCloneReadInfo, IDBDatabase* aDatabase); +} // namespace mozilla::dom::indexedDB |