/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=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 "base/basictypes.h" #include "IDBObjectStore.h" #include "mozilla/dom/ipc/nsIRemoteBlob.h" #include "nsIOutputStream.h" #include #include "jsfriendapi.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/dom/nsIContentParent.h" #include "mozilla/dom/MutableFileBinding.h" #include "mozilla/dom/StructuredCloneTags.h" #include "mozilla/dom/TabChild.h" #include "mozilla/dom/ipc/Blob.h" #include "mozilla/dom/quota/FileStreams.h" #include "mozilla/Endian.h" #include "mozilla/storage.h" #include "nsContentUtils.h" #include "nsDOMClassInfo.h" #include "nsDOMFile.h" #include "mozilla/dom/DOMStringList.h" #include "nsJSUtils.h" #include "nsServiceManagerUtils.h" #include "nsThreadUtils.h" #include "snappy/snappy.h" #include "AsyncConnectionHelper.h" #include "IDBCursor.h" #include "IDBEvents.h" #include "IDBIndex.h" #include "IDBKeyRange.h" #include "IDBMutableFile.h" #include "IDBTransaction.h" #include "DatabaseInfo.h" #include "KeyPath.h" #include "ProfilerHelpers.h" #include "ReportInternalError.h" #include "ipc/IndexedDBChild.h" #include "ipc/IndexedDBParent.h" #include "IndexedDatabaseInlines.h" #include "nsCharSeparatedTokenizer.h" #define FILE_COPY_BUFFER_SIZE 32768 USING_INDEXEDDB_NAMESPACE using namespace mozilla::dom; using namespace mozilla::dom::indexedDB::ipc; using mozilla::dom::quota::FileOutputStream; using mozilla::ErrorResult; using mozilla::fallible_t; using mozilla::LittleEndian; using mozilla::Move; using mozilla::NativeEndian; BEGIN_INDEXEDDB_NAMESPACE struct MutableFileData { nsString type; nsString name; }; struct BlobOrFileData { BlobOrFileData() : tag(0), size(0), lastModifiedDate(UINT64_MAX) { } uint32_t tag; uint64_t size; nsString type; nsString name; uint64_t lastModifiedDate; }; END_INDEXEDDB_NAMESPACE namespace { inline bool IgnoreNothing(char16_t c) { return false; } class ObjectStoreHelper : public AsyncConnectionHelper { public: ObjectStoreHelper(IDBTransaction* aTransaction, IDBRequest* aRequest, IDBObjectStore* aObjectStore) : AsyncConnectionHelper(aTransaction, aRequest), mObjectStore(aObjectStore), mActor(nullptr) { NS_ASSERTION(aTransaction, "Null transaction!"); NS_ASSERTION(aRequest, "Null request!"); NS_ASSERTION(aObjectStore, "Null object store!"); } virtual void ReleaseMainThreadObjects() MOZ_OVERRIDE; virtual nsresult Dispatch(nsIEventTarget* aDatabaseThread) MOZ_OVERRIDE; virtual nsresult PackArgumentsForParentProcess(ObjectStoreRequestParams& aParams, nsIContentChild* aBlobCreator) = 0; virtual nsresult UnpackResponseFromParentProcess(const ResponseValue& aResponseValue) = 0; protected: nsRefPtr mObjectStore; private: IndexedDBObjectStoreRequestChild* mActor; }; class NoRequestObjectStoreHelper : public AsyncConnectionHelper { public: NoRequestObjectStoreHelper(IDBTransaction* aTransaction, IDBObjectStore* aObjectStore) : AsyncConnectionHelper(aTransaction, nullptr), mObjectStore(aObjectStore) { NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); NS_ASSERTION(aTransaction, "Null transaction!"); NS_ASSERTION(aObjectStore, "Null object store!"); } virtual void ReleaseMainThreadObjects() MOZ_OVERRIDE; virtual nsresult UnpackResponseFromParentProcess( const ResponseValue& aResponseValue) MOZ_OVERRIDE; virtual ChildProcessSendResult SendResponseToChildProcess(nsresult aResultCode) MOZ_OVERRIDE; virtual nsresult OnSuccess() MOZ_OVERRIDE; virtual void OnError() MOZ_OVERRIDE; protected: nsRefPtr mObjectStore; }; class AddHelper : public ObjectStoreHelper { public: AddHelper(IDBTransaction* aTransaction, IDBRequest* aRequest, IDBObjectStore* aObjectStore, StructuredCloneWriteInfo&& aCloneWriteInfo, const Key& aKey, bool aOverwrite, nsTArray& aIndexUpdateInfo) : ObjectStoreHelper(aTransaction, aRequest, aObjectStore), mCloneWriteInfo(Move(aCloneWriteInfo)), mKey(aKey), mOverwrite(aOverwrite) { mIndexUpdateInfo.SwapElements(aIndexUpdateInfo); } ~AddHelper() { IDBObjectStore::ClearCloneWriteInfo(mCloneWriteInfo); } virtual nsresult DoDatabaseWork(mozIStorageConnection* aConnection) MOZ_OVERRIDE; virtual nsresult GetSuccessResult(JSContext* aCx, JS::MutableHandle aVal) MOZ_OVERRIDE; virtual void ReleaseMainThreadObjects() MOZ_OVERRIDE; virtual nsresult PackArgumentsForParentProcess(ObjectStoreRequestParams& aParams, nsIContentChild* aBlobCreator) MOZ_OVERRIDE; virtual ChildProcessSendResult SendResponseToChildProcess(nsresult aResultCode) MOZ_OVERRIDE; virtual nsresult UnpackResponseFromParentProcess(const ResponseValue& aResponseValue) MOZ_OVERRIDE; private: // These may change in the autoincrement case. StructuredCloneWriteInfo mCloneWriteInfo; Key mKey; nsTArray mIndexUpdateInfo; const bool mOverwrite; }; class GetHelper : public ObjectStoreHelper { public: GetHelper(IDBTransaction* aTransaction, IDBRequest* aRequest, IDBObjectStore* aObjectStore, IDBKeyRange* aKeyRange) : ObjectStoreHelper(aTransaction, aRequest, aObjectStore), mKeyRange(aKeyRange) { NS_ASSERTION(aKeyRange, "Null key range!"); } ~GetHelper() { IDBObjectStore::ClearCloneReadInfo(mCloneReadInfo); } virtual nsresult DoDatabaseWork(mozIStorageConnection* aConnection) MOZ_OVERRIDE; virtual nsresult GetSuccessResult(JSContext* aCx, JS::MutableHandle aVal) MOZ_OVERRIDE; virtual void ReleaseMainThreadObjects() MOZ_OVERRIDE; virtual nsresult PackArgumentsForParentProcess(ObjectStoreRequestParams& aParams, nsIContentChild* aBlobCreator) MOZ_OVERRIDE; virtual ChildProcessSendResult SendResponseToChildProcess(nsresult aResultCode) MOZ_OVERRIDE; virtual nsresult UnpackResponseFromParentProcess(const ResponseValue& aResponseValue) MOZ_OVERRIDE; protected: // In-params. nsRefPtr mKeyRange; private: // Out-params. StructuredCloneReadInfo mCloneReadInfo; }; class DeleteHelper : public GetHelper { public: DeleteHelper(IDBTransaction* aTransaction, IDBRequest* aRequest, IDBObjectStore* aObjectStore, IDBKeyRange* aKeyRange) : GetHelper(aTransaction, aRequest, aObjectStore, aKeyRange) { } virtual nsresult DoDatabaseWork(mozIStorageConnection* aConnection) MOZ_OVERRIDE; virtual nsresult GetSuccessResult(JSContext* aCx, JS::MutableHandle aVal) MOZ_OVERRIDE; virtual nsresult PackArgumentsForParentProcess(ObjectStoreRequestParams& aParams, nsIContentChild* aBlobCreator) MOZ_OVERRIDE; virtual ChildProcessSendResult SendResponseToChildProcess(nsresult aResultCode) MOZ_OVERRIDE; virtual nsresult UnpackResponseFromParentProcess(const ResponseValue& aResponseValue) MOZ_OVERRIDE; }; class ClearHelper : public ObjectStoreHelper { public: ClearHelper(IDBTransaction* aTransaction, IDBRequest* aRequest, IDBObjectStore* aObjectStore) : ObjectStoreHelper(aTransaction, aRequest, aObjectStore) { } virtual nsresult DoDatabaseWork(mozIStorageConnection* aConnection) MOZ_OVERRIDE; virtual nsresult PackArgumentsForParentProcess(ObjectStoreRequestParams& aParams, nsIContentChild* aBlobCreator) MOZ_OVERRIDE; virtual ChildProcessSendResult SendResponseToChildProcess(nsresult aResultCode) MOZ_OVERRIDE; virtual nsresult UnpackResponseFromParentProcess(const ResponseValue& aResponseValue) MOZ_OVERRIDE; }; class OpenCursorHelper : public ObjectStoreHelper { public: OpenCursorHelper(IDBTransaction* aTransaction, IDBRequest* aRequest, IDBObjectStore* aObjectStore, IDBKeyRange* aKeyRange, IDBCursor::Direction aDirection) : ObjectStoreHelper(aTransaction, aRequest, aObjectStore), mKeyRange(aKeyRange), mDirection(aDirection) { } ~OpenCursorHelper() { IDBObjectStore::ClearCloneReadInfo(mCloneReadInfo); } virtual nsresult DoDatabaseWork(mozIStorageConnection* aConnection) MOZ_OVERRIDE; virtual nsresult GetSuccessResult(JSContext* aCx, JS::MutableHandle aVal) MOZ_OVERRIDE; virtual void ReleaseMainThreadObjects() MOZ_OVERRIDE; virtual nsresult PackArgumentsForParentProcess(ObjectStoreRequestParams& aParams, nsIContentChild* aBlobCreator) MOZ_OVERRIDE; virtual ChildProcessSendResult SendResponseToChildProcess(nsresult aResultCode) MOZ_OVERRIDE; virtual nsresult UnpackResponseFromParentProcess(const ResponseValue& aResponseValue) MOZ_OVERRIDE; private: nsresult EnsureCursor(); // In-params. nsRefPtr mKeyRange; const IDBCursor::Direction mDirection; // Out-params. Key mKey; StructuredCloneReadInfo mCloneReadInfo; nsCString mContinueQuery; nsCString mContinueToQuery; Key mRangeKey; // Only used in the parent process. nsRefPtr mCursor; SerializedStructuredCloneReadInfo mSerializedCloneReadInfo; }; class OpenKeyCursorHelper MOZ_FINAL : public ObjectStoreHelper { public: OpenKeyCursorHelper(IDBTransaction* aTransaction, IDBRequest* aRequest, IDBObjectStore* aObjectStore, IDBKeyRange* aKeyRange, IDBCursor::Direction aDirection) : ObjectStoreHelper(aTransaction, aRequest, aObjectStore), mKeyRange(aKeyRange), mDirection(aDirection) { } virtual nsresult DoDatabaseWork(mozIStorageConnection* aConnection) MOZ_OVERRIDE; virtual nsresult GetSuccessResult(JSContext* aCx, JS::MutableHandle aVal) MOZ_OVERRIDE; virtual void ReleaseMainThreadObjects() MOZ_OVERRIDE; virtual nsresult PackArgumentsForParentProcess(ObjectStoreRequestParams& aParams, nsIContentChild* aBlobCreator) MOZ_OVERRIDE; virtual ChildProcessSendResult SendResponseToChildProcess(nsresult aResultCode) MOZ_OVERRIDE; virtual nsresult UnpackResponseFromParentProcess(const ResponseValue& aResponseValue) MOZ_OVERRIDE; private: ~OpenKeyCursorHelper() { } nsresult EnsureCursor(); // In-params. nsRefPtr mKeyRange; const IDBCursor::Direction mDirection; // Out-params. Key mKey; nsCString mContinueQuery; nsCString mContinueToQuery; Key mRangeKey; // Only used in the parent process. nsRefPtr mCursor; }; class CreateIndexHelper : public NoRequestObjectStoreHelper { public: CreateIndexHelper(IDBTransaction* aTransaction, IDBIndex* aIndex) : NoRequestObjectStoreHelper(aTransaction, aIndex->ObjectStore()), mIndex(aIndex) { if (sTLSIndex == BAD_TLS_INDEX) { PR_NewThreadPrivateIndex(&sTLSIndex, DestroyTLSEntry); } NS_ASSERTION(sTLSIndex != BAD_TLS_INDEX, "PR_NewThreadPrivateIndex failed!"); } virtual nsresult DoDatabaseWork(mozIStorageConnection* aConnection) MOZ_OVERRIDE; virtual void ReleaseMainThreadObjects() MOZ_OVERRIDE; private: nsresult InsertDataFromObjectStore(mozIStorageConnection* aConnection); static void DestroyTLSEntry(void* aPtr); static unsigned sTLSIndex; // In-params. nsRefPtr mIndex; }; unsigned CreateIndexHelper::sTLSIndex = unsigned(BAD_TLS_INDEX); class DeleteIndexHelper : public NoRequestObjectStoreHelper { public: DeleteIndexHelper(IDBTransaction* aTransaction, IDBObjectStore* aObjectStore, const nsAString& aName) : NoRequestObjectStoreHelper(aTransaction, aObjectStore), mName(aName) { } virtual nsresult DoDatabaseWork(mozIStorageConnection* aConnection) MOZ_OVERRIDE; private: // In-params nsString mName; }; class GetAllHelper : public ObjectStoreHelper { public: GetAllHelper(IDBTransaction* aTransaction, IDBRequest* aRequest, IDBObjectStore* aObjectStore, IDBKeyRange* aKeyRange, const uint32_t aLimit) : ObjectStoreHelper(aTransaction, aRequest, aObjectStore), mKeyRange(aKeyRange), mLimit(aLimit) { } ~GetAllHelper() { for (uint32_t index = 0; index < mCloneReadInfos.Length(); index++) { IDBObjectStore::ClearCloneReadInfo(mCloneReadInfos[index]); } } virtual nsresult DoDatabaseWork(mozIStorageConnection* aConnection) MOZ_OVERRIDE; virtual nsresult GetSuccessResult(JSContext* aCx, JS::MutableHandle aVal) MOZ_OVERRIDE; virtual void ReleaseMainThreadObjects() MOZ_OVERRIDE; virtual nsresult PackArgumentsForParentProcess(ObjectStoreRequestParams& aParams, nsIContentChild* aBlobCreator) MOZ_OVERRIDE; virtual ChildProcessSendResult SendResponseToChildProcess(nsresult aResultCode) MOZ_OVERRIDE; virtual nsresult UnpackResponseFromParentProcess(const ResponseValue& aResponseValue) MOZ_OVERRIDE; protected: // In-params. nsRefPtr mKeyRange; const uint32_t mLimit; private: // Out-params. nsTArray mCloneReadInfos; }; class GetAllKeysHelper MOZ_FINAL : public ObjectStoreHelper { public: GetAllKeysHelper(IDBTransaction* aTransaction, IDBRequest* aRequest, IDBObjectStore* aObjectStore, IDBKeyRange* aKeyRange, const uint32_t aLimit) : ObjectStoreHelper(aTransaction, aRequest, aObjectStore), mKeyRange(aKeyRange), mLimit(aLimit) { } virtual nsresult DoDatabaseWork(mozIStorageConnection* aConnection) MOZ_OVERRIDE; virtual nsresult GetSuccessResult(JSContext* aCx, JS::MutableHandle aVal) MOZ_OVERRIDE; virtual void ReleaseMainThreadObjects() MOZ_OVERRIDE; virtual nsresult PackArgumentsForParentProcess(ObjectStoreRequestParams& aParams, nsIContentChild* aBlobCreator) MOZ_OVERRIDE; virtual ChildProcessSendResult SendResponseToChildProcess(nsresult aResultCode) MOZ_OVERRIDE; virtual nsresult UnpackResponseFromParentProcess(const ResponseValue& aResponseValue) MOZ_OVERRIDE; private: ~GetAllKeysHelper() { } nsRefPtr mKeyRange; const uint32_t mLimit; nsTArray mKeys; }; class CountHelper : public ObjectStoreHelper { public: CountHelper(IDBTransaction* aTransaction, IDBRequest* aRequest, IDBObjectStore* aObjectStore, IDBKeyRange* aKeyRange) : ObjectStoreHelper(aTransaction, aRequest, aObjectStore), mKeyRange(aKeyRange), mCount(0) { } virtual nsresult DoDatabaseWork(mozIStorageConnection* aConnection) MOZ_OVERRIDE; virtual nsresult GetSuccessResult(JSContext* aCx, JS::MutableHandle aVal) MOZ_OVERRIDE; virtual void ReleaseMainThreadObjects() MOZ_OVERRIDE; virtual nsresult PackArgumentsForParentProcess(ObjectStoreRequestParams& aParams, nsIContentChild* aBlobCreator) MOZ_OVERRIDE; virtual ChildProcessSendResult SendResponseToChildProcess(nsresult aResultCode) MOZ_OVERRIDE; virtual nsresult UnpackResponseFromParentProcess(const ResponseValue& aResponseValue) MOZ_OVERRIDE; private: nsRefPtr mKeyRange; uint64_t mCount; }; class MOZ_STACK_CLASS AutoRemoveIndex { public: AutoRemoveIndex(ObjectStoreInfo* aObjectStoreInfo, const nsAString& aIndexName) : mObjectStoreInfo(aObjectStoreInfo), mIndexName(aIndexName) { } ~AutoRemoveIndex() { if (mObjectStoreInfo) { for (uint32_t i = 0; i < mObjectStoreInfo->indexes.Length(); i++) { if (mObjectStoreInfo->indexes[i].name == mIndexName) { mObjectStoreInfo->indexes.RemoveElementAt(i); break; } } } } void forget() { mObjectStoreInfo = nullptr; } private: ObjectStoreInfo* mObjectStoreInfo; nsString mIndexName; }; class ThreadLocalJSRuntime { JSRuntime* mRuntime; JSContext* mContext; JSObject* mGlobal; static const JSClass sGlobalClass; static const unsigned sRuntimeHeapSize = 768 * 1024; ThreadLocalJSRuntime() : mRuntime(nullptr), mContext(nullptr), mGlobal(nullptr) { MOZ_COUNT_CTOR(ThreadLocalJSRuntime); } nsresult Init() { mRuntime = JS_NewRuntime(sRuntimeHeapSize); NS_ENSURE_TRUE(mRuntime, NS_ERROR_OUT_OF_MEMORY); /* * Not setting this will cause JS_CHECK_RECURSION to report false * positives */ JS_SetNativeStackQuota(mRuntime, 128 * sizeof(size_t) * 1024); mContext = JS_NewContext(mRuntime, 0); NS_ENSURE_TRUE(mContext, NS_ERROR_OUT_OF_MEMORY); JSAutoRequest ar(mContext); mGlobal = JS_NewGlobalObject(mContext, &sGlobalClass, nullptr, JS::FireOnNewGlobalHook); NS_ENSURE_TRUE(mGlobal, NS_ERROR_OUT_OF_MEMORY); js::SetDefaultObjectForContext(mContext, mGlobal); return NS_OK; } public: static ThreadLocalJSRuntime *Create() { ThreadLocalJSRuntime *entry = new ThreadLocalJSRuntime(); NS_ENSURE_TRUE(entry, nullptr); if (NS_FAILED(entry->Init())) { delete entry; return nullptr; } return entry; } JSContext *Context() const { return mContext; } JSObject *Global() const { return mGlobal; } ~ThreadLocalJSRuntime() { MOZ_COUNT_DTOR(ThreadLocalJSRuntime); if (mContext) { JS_DestroyContext(mContext); } if (mRuntime) { JS_DestroyRuntime(mRuntime); } } }; const JSClass ThreadLocalJSRuntime::sGlobalClass = { "IndexedDBTransactionThreadGlobal", JSCLASS_GLOBAL_FLAGS, JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, nullptr, nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook }; inline already_AddRefed GenerateRequest(IDBObjectStore* aObjectStore) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); IDBDatabase* database = aObjectStore->Transaction()->Database(); return IDBRequest::Create(aObjectStore, database, aObjectStore->Transaction()); } struct MOZ_STACK_CLASS GetAddInfoClosure { IDBObjectStore* mThis; StructuredCloneWriteInfo& mCloneWriteInfo; JS::Handle mValue; }; nsresult GetAddInfoCallback(JSContext* aCx, void* aClosure) { GetAddInfoClosure* data = static_cast(aClosure); data->mCloneWriteInfo.mOffsetToKeyProp = 0; data->mCloneWriteInfo.mTransaction = data->mThis->Transaction(); if (!IDBObjectStore::SerializeValue(aCx, data->mCloneWriteInfo, data->mValue)) { return NS_ERROR_DOM_DATA_CLONE_ERR; } return NS_OK; } inline BlobChild* ActorFromRemoteBlob(nsIDOMBlob* aBlob) { NS_ASSERTION(!IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); nsRefPtr blob = static_cast(aBlob); nsCOMPtr remoteBlob = do_QueryInterface(blob->Impl()); if (remoteBlob) { BlobChild* actor = static_cast(static_cast(remoteBlob->GetPBlob())); NS_ASSERTION(actor, "Null actor?!"); return actor; } return nullptr; } inline bool ResolveMysteryFile(nsIDOMBlob* aBlob, const nsString& aName, const nsString& aContentType, uint64_t aSize, uint64_t aLastModifiedDate) { BlobChild* actor = ActorFromRemoteBlob(aBlob); if (actor) { return actor->SetMysteryBlobInfo(aName, aContentType, aSize, aLastModifiedDate); } return true; } inline bool ResolveMysteryBlob(nsIDOMBlob* aBlob, const nsString& aContentType, uint64_t aSize) { BlobChild* actor = ActorFromRemoteBlob(aBlob); if (actor) { return actor->SetMysteryBlobInfo(aContentType, aSize); } return true; } class MainThreadDeserializationTraits { public: static JSObject* CreateAndWrapMutableFile(JSContext* aCx, IDBDatabase* aDatabase, StructuredCloneFile& aFile, const MutableFileData& aData) { MOZ_ASSERT(NS_IsMainThread()); nsRefPtr& fileInfo = aFile.mFileInfo; nsRefPtr mutableFile = IDBMutableFile::Create(aData.name, aData.type, aDatabase, fileInfo.forget()); return mutableFile->WrapObject(aCx); } static JSObject* CreateAndWrapBlobOrFile(JSContext* aCx, IDBDatabase* aDatabase, StructuredCloneFile& aFile, const BlobOrFileData& aData) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aData.tag == SCTAG_DOM_FILE || aData.tag == SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE || aData.tag == SCTAG_DOM_BLOB); nsresult rv = NS_OK; nsRefPtr& fileInfo = aFile.mFileInfo; nsCOMPtr nativeFile; if (!aFile.mFile) { FileManager* fileManager = aDatabase->Manager(); NS_ASSERTION(fileManager, "This should never be null!"); nsCOMPtr directory = fileManager->GetDirectory(); if (!directory) { NS_WARNING("Failed to get directory!"); return nullptr; } nativeFile = fileManager->GetFileForId(directory, fileInfo->Id()); if (!nativeFile) { NS_WARNING("Failed to get file!"); return nullptr; } } if (aData.tag == SCTAG_DOM_BLOB) { nsCOMPtr domBlob; if (aFile.mFile) { if (!ResolveMysteryBlob(aFile.mFile, aData.type, aData.size)) { return nullptr; } domBlob = aFile.mFile; } else { domBlob = DOMFile::CreateFromFile(aData.type, aData.size, nativeFile, fileInfo); } JS::Rooted wrappedBlob(aCx); rv = nsContentUtils::WrapNative(aCx, domBlob, &NS_GET_IID(nsIDOMBlob), &wrappedBlob); if (NS_FAILED(rv)) { NS_WARNING("Failed to wrap native!"); return nullptr; } return wrappedBlob.toObjectOrNull(); } nsCOMPtr domFile; if (aFile.mFile) { if (!ResolveMysteryFile(aFile.mFile, aData.name, aData.type, aData.size, aData.lastModifiedDate)) { return nullptr; } domFile = do_QueryInterface(aFile.mFile); NS_ASSERTION(domFile, "This should never fail!"); } else { domFile = DOMFile::CreateFromFile(aData.name, aData.type, aData.size, nativeFile, fileInfo); } JS::Rooted wrappedFile(aCx); rv = nsContentUtils::WrapNative(aCx, domFile, &NS_GET_IID(nsIDOMFile), &wrappedFile); if (NS_FAILED(rv)) { NS_WARNING("Failed to wrap native!"); return nullptr; } return wrappedFile.toObjectOrNull(); } }; class CreateIndexDeserializationTraits { public: static JSObject* CreateAndWrapMutableFile(JSContext* aCx, IDBDatabase* aDatabase, StructuredCloneFile& aFile, const MutableFileData& aData) { // MutableFile can't be used in index creation, so just make a dummy object. return JS_NewObject(aCx, nullptr, JS::NullPtr(), JS::NullPtr()); } static JSObject* CreateAndWrapBlobOrFile(JSContext* aCx, IDBDatabase* aDatabase, StructuredCloneFile& aFile, const BlobOrFileData& aData) { MOZ_ASSERT(aData.tag == SCTAG_DOM_FILE || aData.tag == SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE || aData.tag == SCTAG_DOM_BLOB); // The following properties are available for use in index creation // Blob.size // Blob.type // File.name // File.lastModifiedDate JS::Rooted obj(aCx, JS_NewObject(aCx, nullptr, JS::NullPtr(), JS::NullPtr())); if (!obj) { NS_WARNING("Failed to create object!"); return nullptr; } // Technically these props go on the proto, but this detail won't change // the results of index creation. JS::Rooted type(aCx, JS_NewUCStringCopyN(aCx, aData.type.get(), aData.type.Length())); if (!type || !JS_DefineProperty(aCx, obj, "size", double(aData.size), 0) || !JS_DefineProperty(aCx, obj, "type", type, 0)) { return nullptr; } if (aData.tag == SCTAG_DOM_BLOB) { return obj; } JS::Rooted name(aCx, JS_NewUCStringCopyN(aCx, aData.name.get(), aData.name.Length())); JS::Rooted date(aCx, JS_NewDateObjectMsec(aCx, aData.lastModifiedDate)); if (!name || !date || !JS_DefineProperty(aCx, obj, "name", name, 0) || !JS_DefineProperty(aCx, obj, "lastModifiedDate", date, 0)) { return nullptr; } return obj; } }; } // anonymous namespace const JSClass IDBObjectStore::sDummyPropJSClass = { "dummy", 0, JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub }; // static already_AddRefed IDBObjectStore::Create(IDBTransaction* aTransaction, ObjectStoreInfo* aStoreInfo, const nsACString& aDatabaseId, bool aCreating) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); nsRefPtr objectStore = new IDBObjectStore(); objectStore->mTransaction = aTransaction; objectStore->mName = aStoreInfo->name; objectStore->mId = aStoreInfo->id; objectStore->mKeyPath = aStoreInfo->keyPath; objectStore->mAutoIncrement = aStoreInfo->autoIncrement; objectStore->mDatabaseId = aDatabaseId; objectStore->mInfo = aStoreInfo; if (!IndexedDatabaseManager::IsMainProcess()) { IndexedDBTransactionChild* transactionActor = aTransaction->GetActorChild(); NS_ASSERTION(transactionActor, "Must have an actor here!"); ObjectStoreConstructorParams params; if (aCreating) { CreateObjectStoreParams createParams; createParams.info() = *aStoreInfo; params = createParams; } else { GetObjectStoreParams getParams; getParams.name() = aStoreInfo->name; params = getParams; } IndexedDBObjectStoreChild* actor = new IndexedDBObjectStoreChild(objectStore); transactionActor->SendPIndexedDBObjectStoreConstructor(actor, params); } return objectStore.forget(); } // static nsresult IDBObjectStore::AppendIndexUpdateInfo( int64_t aIndexID, const KeyPath& aKeyPath, bool aUnique, bool aMultiEntry, JSContext* aCx, JS::Handle aVal, nsTArray& aUpdateInfoArray) { nsresult rv; if (!aMultiEntry) { Key key; rv = aKeyPath.ExtractKey(aCx, aVal, key); // If an index's keypath doesn't match an object, we ignore that object. if (rv == NS_ERROR_DOM_INDEXEDDB_DATA_ERR || key.IsUnset()) { return NS_OK; } if (NS_FAILED(rv)) { return rv; } IndexUpdateInfo* updateInfo = aUpdateInfoArray.AppendElement(); updateInfo->indexId = aIndexID; updateInfo->indexUnique = aUnique; updateInfo->value = key; return NS_OK; } JS::Rooted val(aCx); if (NS_FAILED(aKeyPath.ExtractKeyAsJSVal(aCx, aVal, val.address()))) { return NS_OK; } if (JS_IsArrayObject(aCx, val)) { JS::Rooted array(aCx, &val.toObject()); uint32_t arrayLength; if (!JS_GetArrayLength(aCx, array, &arrayLength)) { IDB_REPORT_INTERNAL_ERR(); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } for (uint32_t arrayIndex = 0; arrayIndex < arrayLength; arrayIndex++) { JS::Rooted arrayItem(aCx); if (!JS_GetElement(aCx, array, arrayIndex, &arrayItem)) { IDB_REPORT_INTERNAL_ERR(); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } Key value; if (NS_FAILED(value.SetFromJSVal(aCx, arrayItem)) || value.IsUnset()) { // Not a value we can do anything with, ignore it. continue; } IndexUpdateInfo* updateInfo = aUpdateInfoArray.AppendElement(); updateInfo->indexId = aIndexID; updateInfo->indexUnique = aUnique; updateInfo->value = value; } } else { Key value; if (NS_FAILED(value.SetFromJSVal(aCx, val)) || value.IsUnset()) { // Not a value we can do anything with, ignore it. return NS_OK; } IndexUpdateInfo* updateInfo = aUpdateInfoArray.AppendElement(); updateInfo->indexId = aIndexID; updateInfo->indexUnique = aUnique; updateInfo->value = value; } return NS_OK; } // static nsresult IDBObjectStore::UpdateIndexes(IDBTransaction* aTransaction, int64_t aObjectStoreId, const Key& aObjectStoreKey, bool aOverwrite, int64_t aObjectDataId, const nsTArray& aUpdateInfoArray) { NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); PROFILER_LABEL("IDBObjectStore", "UpdateIndexes", js::ProfileEntry::Category::STORAGE); nsresult rv; NS_ASSERTION(aObjectDataId != INT64_MIN, "Bad objectData id!"); NS_NAMED_LITERAL_CSTRING(objectDataId, "object_data_id"); if (aOverwrite) { nsCOMPtr deleteStmt = aTransaction->GetCachedStatement( "DELETE FROM unique_index_data " "WHERE object_data_id = :object_data_id; " "DELETE FROM index_data " "WHERE object_data_id = :object_data_id"); NS_ENSURE_TRUE(deleteStmt, NS_ERROR_FAILURE); mozStorageStatementScoper scoper(deleteStmt); rv = deleteStmt->BindInt64ByName(objectDataId, aObjectDataId); NS_ENSURE_SUCCESS(rv, rv); rv = deleteStmt->Execute(); NS_ENSURE_SUCCESS(rv, rv); } // Avoid lots of hash lookups for objectStores with lots of indexes by lazily // holding the necessary statements on the stack outside the loop. nsCOMPtr insertUniqueStmt; nsCOMPtr insertStmt; uint32_t infoCount = aUpdateInfoArray.Length(); for (uint32_t i = 0; i < infoCount; i++) { const IndexUpdateInfo& updateInfo = aUpdateInfoArray[i]; nsCOMPtr& stmt = updateInfo.indexUnique ? insertUniqueStmt : insertStmt; if (!stmt) { stmt = updateInfo.indexUnique ? aTransaction->GetCachedStatement( "INSERT INTO unique_index_data " "(index_id, object_data_id, object_data_key, value) " "VALUES (:index_id, :object_data_id, :object_data_key, :value)") : aTransaction->GetCachedStatement( "INSERT OR IGNORE INTO index_data (" "index_id, object_data_id, object_data_key, value) " "VALUES (:index_id, :object_data_id, :object_data_key, :value)"); } NS_ENSURE_TRUE(stmt, NS_ERROR_FAILURE); mozStorageStatementScoper scoper(stmt); rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("index_id"), updateInfo.indexId); NS_ENSURE_SUCCESS(rv, rv); rv = stmt->BindInt64ByName(objectDataId, aObjectDataId); NS_ENSURE_SUCCESS(rv, rv); rv = aObjectStoreKey.BindToStatement(stmt, NS_LITERAL_CSTRING("object_data_key")); NS_ENSURE_SUCCESS(rv, rv); rv = updateInfo.value.BindToStatement(stmt, NS_LITERAL_CSTRING("value")); NS_ENSURE_SUCCESS(rv, rv); rv = stmt->Execute(); if (rv == NS_ERROR_STORAGE_CONSTRAINT && updateInfo.indexUnique) { // If we're inserting multiple entries for the same unique index, then // we might have failed to insert due to colliding with another entry for // the same index in which case we should ignore it. for (int32_t j = (int32_t)i - 1; j >= 0 && aUpdateInfoArray[j].indexId == updateInfo.indexId; --j) { if (updateInfo.value == aUpdateInfoArray[j].value) { // We found a key with the same value for the same index. So we // must have had a collision with a value we just inserted. rv = NS_OK; break; } } } if (NS_FAILED(rv)) { return rv; } } return NS_OK; } // static nsresult IDBObjectStore::GetStructuredCloneReadInfoFromStatement( mozIStorageStatement* aStatement, uint32_t aDataIndex, uint32_t aFileIdsIndex, IDBDatabase* aDatabase, StructuredCloneReadInfo& aInfo) { NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); PROFILER_LABEL("IDBObjectStore", "GetStructuredCloneReadInfoFromStatement", js::ProfileEntry::Category::STORAGE); #ifdef DEBUG { int32_t type; NS_ASSERTION(NS_SUCCEEDED(aStatement->GetTypeOfIndex(aDataIndex, &type)) && type == mozIStorageStatement::VALUE_TYPE_BLOB, "Bad value type!"); } #endif const uint8_t* blobData; uint32_t blobDataLength; nsresult rv = aStatement->GetSharedBlob(aDataIndex, &blobDataLength, &blobData); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); const char* compressed = reinterpret_cast(blobData); size_t compressedLength = size_t(blobDataLength); static const fallible_t fallible = fallible_t(); size_t uncompressedLength; if (!snappy::GetUncompressedLength(compressed, compressedLength, &uncompressedLength)) { IDB_WARNING("Snappy can't determine uncompressed length!"); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } nsAutoArrayPtr uncompressed(new (fallible) char[uncompressedLength]); NS_ENSURE_TRUE(uncompressed, NS_ERROR_OUT_OF_MEMORY); if (!snappy::RawUncompress(compressed, compressedLength, uncompressed.get())) { IDB_WARNING("Snappy can't determine uncompressed length!"); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } JSAutoStructuredCloneBuffer& buffer = aInfo.mCloneBuffer; if (!buffer.copy(reinterpret_cast(uncompressed.get()), uncompressedLength)) { IDB_REPORT_INTERNAL_ERR(); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } bool isNull; rv = aStatement->GetIsNull(aFileIdsIndex, &isNull); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (!isNull) { nsString ids; rv = aStatement->GetString(aFileIdsIndex, ids); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); nsAutoTArray array; rv = ConvertFileIdsToArray(ids, array); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); FileManager* fileManager = aDatabase->Manager(); for (uint32_t i = 0; i < array.Length(); i++) { const int64_t& id = array[i]; nsRefPtr fileInfo = fileManager->GetFileInfo(id); NS_ASSERTION(fileInfo, "Null file info!"); StructuredCloneFile* file = aInfo.mFiles.AppendElement(); file->mFileInfo.swap(fileInfo); } } aInfo.mDatabase = aDatabase; return NS_OK; } // static void IDBObjectStore::ClearCloneWriteInfo(StructuredCloneWriteInfo& aWriteInfo) { // This is kind of tricky, we only want to release stuff on the main thread, // but we can end up being called on other threads if we have already been // cleared on the main thread. if (!aWriteInfo.mCloneBuffer.data() && !aWriteInfo.mFiles.Length()) { return; } // If there's something to clear, we should be on the main thread. NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); ClearStructuredCloneBuffer(aWriteInfo.mCloneBuffer); aWriteInfo.mFiles.Clear(); } // static void IDBObjectStore::ClearCloneReadInfo(StructuredCloneReadInfo& aReadInfo) { // This is kind of tricky, we only want to release stuff on the main thread, // but we can end up being called on other threads if we have already been // cleared on the main thread. if (!aReadInfo.mCloneBuffer.data() && !aReadInfo.mFiles.Length()) { return; } // If there's something to clear, we should be on the main thread. NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); ClearStructuredCloneBuffer(aReadInfo.mCloneBuffer); aReadInfo.mFiles.Clear(); } // static void IDBObjectStore::ClearStructuredCloneBuffer(JSAutoStructuredCloneBuffer& aBuffer) { if (aBuffer.data()) { aBuffer.clear(); } } // static bool IDBObjectStore::DeserializeValue(JSContext* aCx, StructuredCloneReadInfo& aCloneReadInfo, JS::MutableHandle aValue) { NS_ASSERTION(NS_IsMainThread(), "Should only be deserializing on the main thread!"); NS_ASSERTION(aCx, "A JSContext is required!"); JSAutoStructuredCloneBuffer& buffer = aCloneReadInfo.mCloneBuffer; if (!buffer.data()) { aValue.setUndefined(); return true; } JSAutoRequest ar(aCx); JSStructuredCloneCallbacks callbacks = { IDBObjectStore::StructuredCloneReadCallback, nullptr, nullptr, nullptr, nullptr, nullptr }; return buffer.read(aCx, aValue, &callbacks, &aCloneReadInfo); } // static bool IDBObjectStore::SerializeValue(JSContext* aCx, StructuredCloneWriteInfo& aCloneWriteInfo, JS::Handle aValue) { NS_ASSERTION(NS_IsMainThread(), "Should only be serializing on the main thread!"); NS_ASSERTION(aCx, "A JSContext is required!"); JSAutoRequest ar(aCx); JSStructuredCloneCallbacks callbacks = { nullptr, StructuredCloneWriteCallback, nullptr, nullptr, nullptr, nullptr }; JSAutoStructuredCloneBuffer& buffer = aCloneWriteInfo.mCloneBuffer; return buffer.write(aCx, aValue, &callbacks, &aCloneWriteInfo); } static inline 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_t())) { NS_WARNING("Out of memory?"); return false; } char* buffer = aString.BeginWriting(); if (!JS_ReadBytes(aReader, buffer, length)) { NS_WARNING("Failed to read type!"); return false; } return true; } // static bool IDBObjectStore::ReadMutableFile(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; } // static bool IDBObjectStore::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 && aRetval); MOZ_ASSERT(aTag == SCTAG_DOM_FILE || aTag == SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE || aTag == SCTAG_DOM_BLOB); aRetval->tag = aTag; // If it's not a MutableFile, it's a Blob or a File. uint64_t size; if (!JS_ReadBytes(aReader, &size, sizeof(uint64_t))) { NS_WARNING("Failed to read size!"); return false; } aRetval->size = NativeEndian::swapFromLittleEndian(size); nsCString type; if (!StructuredCloneReadString(aReader, type)) { return false; } CopyUTF8toUTF16(type, aRetval->type); // Blobs are done. if (aTag == SCTAG_DOM_BLOB) { return true; } NS_ASSERTION(aTag == SCTAG_DOM_FILE || aTag == SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE, "Huh?!"); uint64_t lastModifiedDate; if (aTag == SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE) { lastModifiedDate = UINT64_MAX; } else { if(!JS_ReadBytes(aReader, &lastModifiedDate, sizeof(lastModifiedDate))) { NS_WARNING("Failed to read lastModifiedDate"); return false; } lastModifiedDate = NativeEndian::swapFromLittleEndian(lastModifiedDate); } aRetval->lastModifiedDate = lastModifiedDate; nsCString name; if (!StructuredCloneReadString(aReader, name)) { return false; } CopyUTF8toUTF16(name, aRetval->name); return true; } // static template JSObject* IDBObjectStore::StructuredCloneReadCallback(JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aTag, uint32_t aData, void* aClosure) { // 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, "You changed our structured clone tag values and just ate " "everyone's IndexedDB data. I hope you are happy."); if (aTag == SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE || aTag == SCTAG_DOM_MUTABLEFILE || aTag == SCTAG_DOM_BLOB || aTag == SCTAG_DOM_FILE) { StructuredCloneReadInfo* cloneReadInfo = reinterpret_cast(aClosure); if (aData >= cloneReadInfo->mFiles.Length()) { NS_ERROR("Bad blob index!"); return nullptr; } StructuredCloneFile& file = cloneReadInfo->mFiles[aData]; IDBDatabase* database = cloneReadInfo->mDatabase; if (aTag == SCTAG_DOM_MUTABLEFILE) { MutableFileData data; if (!ReadMutableFile(aReader, &data)) { return nullptr; } return DeserializationTraits::CreateAndWrapMutableFile(aCx, database, file, data); } BlobOrFileData data; if (!ReadBlobOrFile(aReader, aTag, &data)) { return nullptr; } return DeserializationTraits::CreateAndWrapBlobOrFile(aCx, database, file, data); } const JSStructuredCloneCallbacks* runtimeCallbacks = js::GetContextStructuredCloneCallbacks(aCx); if (runtimeCallbacks) { return runtimeCallbacks->read(aCx, aReader, aTag, aData, nullptr); } return nullptr; } // static bool IDBObjectStore::StructuredCloneWriteCallback(JSContext* aCx, JSStructuredCloneWriter* aWriter, JS::Handle aObj, void* aClosure) { StructuredCloneWriteInfo* cloneWriteInfo = reinterpret_cast(aClosure); if (JS_GetClass(aObj) == &sDummyPropJSClass) { NS_ASSERTION(cloneWriteInfo->mOffsetToKeyProp == 0, "We should not have been here before!"); cloneWriteInfo->mOffsetToKeyProp = js_GetSCOffset(aWriter); uint64_t value = 0; // Omit endian swap return JS_WriteBytes(aWriter, &value, sizeof(value)); } IDBTransaction* transaction = cloneWriteInfo->mTransaction; FileManager* fileManager = transaction->Database()->Manager(); MutableFile* mutableFile = nullptr; if (NS_SUCCEEDED(UNWRAP_OBJECT(MutableFile, aObj, mutableFile))) { nsRefPtr fileInfo = mutableFile->GetFileInfo(); // Throw when trying to store non IDB mutable files or IDB mutable files // across databases. if (!fileInfo || fileInfo->Manager() != fileManager) { return false; } NS_ConvertUTF16toUTF8 convType(mutableFile->Type()); uint32_t convTypeLength = NativeEndian::swapToLittleEndian(convType.Length()); NS_ConvertUTF16toUTF8 convName(mutableFile->Name()); uint32_t convNameLength = NativeEndian::swapToLittleEndian(convName.Length()); if (!JS_WriteUint32Pair(aWriter, SCTAG_DOM_MUTABLEFILE, cloneWriteInfo->mFiles.Length()) || !JS_WriteBytes(aWriter, &convTypeLength, sizeof(uint32_t)) || !JS_WriteBytes(aWriter, convType.get(), convType.Length()) || !JS_WriteBytes(aWriter, &convNameLength, sizeof(uint32_t)) || !JS_WriteBytes(aWriter, convName.get(), convName.Length())) { return false; } StructuredCloneFile* file = cloneWriteInfo->mFiles.AppendElement(); file->mFileInfo = fileInfo.forget(); return true; } nsCOMPtr wrappedNative; nsContentUtils::XPConnect()-> GetWrappedNativeOfJSObject(aCx, aObj, getter_AddRefs(wrappedNative)); if (wrappedNative) { nsISupports* supports = wrappedNative->Native(); nsCOMPtr blob = do_QueryInterface(supports); if (blob) { nsCOMPtr inputStream; // Check if it is a blob created from this db or the blob was already // stored in this db nsRefPtr fileInfo = transaction->GetFileInfo(blob); if (!fileInfo && fileManager) { fileInfo = blob->GetFileInfo(fileManager); if (!fileInfo) { fileInfo = fileManager->GetNewFileInfo(); if (!fileInfo) { NS_WARNING("Failed to get new file info!"); return false; } if (NS_FAILED(blob->GetInternalStream(getter_AddRefs(inputStream)))) { NS_WARNING("Failed to get internal steam!"); return false; } transaction->AddFileInfo(blob, fileInfo); } } uint64_t size; if (NS_FAILED(blob->GetSize(&size))) { NS_WARNING("Failed to get size!"); return false; } size = NativeEndian::swapToLittleEndian(size); nsString type; if (NS_FAILED(blob->GetType(type))) { NS_WARNING("Failed to get type!"); return false; } NS_ConvertUTF16toUTF8 convType(type); uint32_t convTypeLength = NativeEndian::swapToLittleEndian(convType.Length()); nsCOMPtr file = do_QueryInterface(blob); if (!JS_WriteUint32Pair(aWriter, file ? SCTAG_DOM_FILE : SCTAG_DOM_BLOB, cloneWriteInfo->mFiles.Length()) || !JS_WriteBytes(aWriter, &size, sizeof(size)) || !JS_WriteBytes(aWriter, &convTypeLength, sizeof(convTypeLength)) || !JS_WriteBytes(aWriter, convType.get(), convType.Length())) { return false; } if (file) { uint64_t lastModifiedDate = 0; if (NS_FAILED(file->GetMozLastModifiedDate(&lastModifiedDate))) { NS_WARNING("Failed to get last modified date!"); return false; } lastModifiedDate = NativeEndian::swapToLittleEndian(lastModifiedDate); nsString name; if (NS_FAILED(file->GetName(name))) { NS_WARNING("Failed to get name!"); return false; } NS_ConvertUTF16toUTF8 convName(name); uint32_t convNameLength = NativeEndian::swapToLittleEndian(convName.Length()); if (!JS_WriteBytes(aWriter, &lastModifiedDate, sizeof(lastModifiedDate)) || !JS_WriteBytes(aWriter, &convNameLength, sizeof(convNameLength)) || !JS_WriteBytes(aWriter, convName.get(), convName.Length())) { return false; } } StructuredCloneFile* cloneFile = cloneWriteInfo->mFiles.AppendElement(); cloneFile->mFile = blob.forget(); cloneFile->mFileInfo = fileInfo.forget(); cloneFile->mInputStream = inputStream.forget(); return true; } } // try using the runtime callbacks const JSStructuredCloneCallbacks* runtimeCallbacks = js::GetContextStructuredCloneCallbacks(aCx); if (runtimeCallbacks) { return runtimeCallbacks->write(aCx, aWriter, aObj, nullptr); } return false; } // static nsresult IDBObjectStore::ConvertFileIdsToArray(const nsAString& aFileIds, nsTArray& aResult) { nsCharSeparatedTokenizerTemplate tokenizer(aFileIds, ' '); while (tokenizer.hasMoreTokens()) { nsString token(tokenizer.nextToken()); NS_ASSERTION(!token.IsEmpty(), "Should be a valid id!"); nsresult rv; int32_t id = token.ToInteger(&rv); NS_ENSURE_SUCCESS(rv, rv); int64_t* element = aResult.AppendElement(); *element = id; } return NS_OK; } // static void IDBObjectStore::ConvertActorsToBlobs( const InfallibleTArray& aActors, nsTArray& aFiles) { NS_ASSERTION(!IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(aFiles.IsEmpty(), "Should be empty!"); if (!aActors.IsEmpty()) { NS_ASSERTION(ContentChild::GetSingleton(), "This should never be null!"); uint32_t length = aActors.Length(); aFiles.SetCapacity(length); for (uint32_t index = 0; index < length; index++) { BlobChild* actor = static_cast(aActors[index]); StructuredCloneFile* file = aFiles.AppendElement(); file->mFile = actor->GetBlob(); } } } // static nsresult IDBObjectStore::ConvertBlobsToActors( nsIContentParent* aContentParent, FileManager* aFileManager, const nsTArray& aFiles, InfallibleTArray& aActors) { NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(aContentParent, "Null contentParent!"); NS_ASSERTION(aFileManager, "Null file manager!"); if (!aFiles.IsEmpty()) { nsCOMPtr directory = aFileManager->GetDirectory(); if (!directory) { IDB_WARNING("Failed to get directory!"); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } uint32_t fileCount = aFiles.Length(); aActors.SetCapacity(fileCount); for (uint32_t index = 0; index < fileCount; index++) { const StructuredCloneFile& file = aFiles[index]; NS_ASSERTION(file.mFileInfo, "This should never be null!"); nsCOMPtr nativeFile = aFileManager->GetFileForId(directory, file.mFileInfo->Id()); if (!nativeFile) { IDB_WARNING("Failed to get file!"); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } nsCOMPtr blob = DOMFile::CreateFromFile(nativeFile, file.mFileInfo); BlobParent* actor = aContentParent->GetOrCreateActorForBlob(blob); if (!actor) { // This can only fail if the child has crashed. IDB_REPORT_INTERNAL_ERR(); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } aActors.AppendElement(actor); } } return NS_OK; } IDBObjectStore::IDBObjectStore() : mId(INT64_MIN), mKeyPath(0), mCachedKeyPath(JSVAL_VOID), mRooted(false), mAutoIncrement(false), mActorChild(nullptr), mActorParent(nullptr) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); SetIsDOMBinding(); } IDBObjectStore::~IDBObjectStore() { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(!mActorParent, "Actor parent owns us, how can we be dying?!"); if (mActorChild) { NS_ASSERTION(!IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); mActorChild->Send__delete__(mActorChild); NS_ASSERTION(!mActorChild, "Should have cleared in Send__delete__!"); } if (mRooted) { mCachedKeyPath = JSVAL_VOID; mozilla::DropJSObjects(this); } } nsresult IDBObjectStore::GetAddInfo(JSContext* aCx, JS::Handle aValue, JS::Handle aKeyVal, StructuredCloneWriteInfo& aCloneWriteInfo, Key& aKey, nsTArray& aUpdateInfoArray) { nsresult rv; // Return DATA_ERR if a key was passed in and this objectStore uses inline // keys. if (!aKeyVal.isUndefined() && HasValidKeyPath()) { return NS_ERROR_DOM_INDEXEDDB_DATA_ERR; } JSAutoRequest ar(aCx); if (!HasValidKeyPath()) { // Out-of-line keys must be passed in. rv = aKey.SetFromJSVal(aCx, aKeyVal); if (NS_FAILED(rv)) { return rv; } } else if (!mAutoIncrement) { rv = GetKeyPath().ExtractKey(aCx, aValue, aKey); if (NS_FAILED(rv)) { return rv; } } // Return DATA_ERR if no key was specified this isn't an autoIncrement // objectStore. if (aKey.IsUnset() && !mAutoIncrement) { return NS_ERROR_DOM_INDEXEDDB_DATA_ERR; } // Figure out indexes and the index values to update here. uint32_t count = mInfo->indexes.Length(); aUpdateInfoArray.SetCapacity(count); // Pretty good estimate for (uint32_t indexesIndex = 0; indexesIndex < count; indexesIndex++) { const IndexInfo& indexInfo = mInfo->indexes[indexesIndex]; rv = AppendIndexUpdateInfo(indexInfo.id, indexInfo.keyPath, indexInfo.unique, indexInfo.multiEntry, aCx, aValue, aUpdateInfoArray); NS_ENSURE_SUCCESS(rv, rv); } GetAddInfoClosure data = {this, aCloneWriteInfo, aValue}; if (mAutoIncrement && HasValidKeyPath()) { NS_ASSERTION(aKey.IsUnset(), "Shouldn't have gotten the key yet!"); rv = GetKeyPath().ExtractOrCreateKey(aCx, aValue, aKey, &GetAddInfoCallback, &data); } else { rv = GetAddInfoCallback(aCx, &data); } return rv; } already_AddRefed IDBObjectStore::AddOrPut(JSContext* aCx, JS::Handle aValue, JS::Handle aKey, bool aOverwrite, ErrorResult& aRv) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); if (!mTransaction->IsOpen()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } if (!IsWriteAllowed()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR); return nullptr; } StructuredCloneWriteInfo cloneWriteInfo; Key key; nsTArray updateInfo; JS::Rooted value(aCx, aValue); aRv = GetAddInfo(aCx, value, aKey, cloneWriteInfo, key, updateInfo); if (aRv.Failed()) { return nullptr; } nsRefPtr request = GenerateRequest(this); if (!request) { IDB_WARNING("Failed to generate request!"); aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return nullptr; } nsRefPtr helper = new AddHelper(mTransaction, request, this, Move(cloneWriteInfo), key, aOverwrite, updateInfo); nsresult rv = helper->DispatchToTransactionPool(); if (NS_FAILED(rv)) { IDB_WARNING("Failed to dispatch!"); aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return nullptr; } #ifdef IDB_PROFILER_USE_MARKS if (aOverwrite) { IDB_PROFILER_MARK("IndexedDB Request %llu: " "database(%s).transaction(%s).objectStore(%s).%s(%s)", "IDBRequest[%llu] MT IDBObjectStore.put()", request->GetSerialNumber(), IDB_PROFILER_STRING(Transaction()->Database()), IDB_PROFILER_STRING(Transaction()), IDB_PROFILER_STRING(this), key.IsUnset() ? "" : IDB_PROFILER_STRING(key)); } else { IDB_PROFILER_MARK("IndexedDB Request %llu: " "database(%s).transaction(%s).objectStore(%s).add(%s)", "IDBRequest[%llu] MT IDBObjectStore.add()", request->GetSerialNumber(), IDB_PROFILER_STRING(Transaction()->Database()), IDB_PROFILER_STRING(Transaction()), IDB_PROFILER_STRING(this), key.IsUnset() ? "" : IDB_PROFILER_STRING(key)); } #endif return request.forget(); } nsresult IDBObjectStore::AddOrPutInternal( const SerializedStructuredCloneWriteInfo& aCloneWriteInfo, const Key& aKey, const InfallibleTArray& aUpdateInfoArray, const nsTArray >& aBlobs, bool aOverwrite, IDBRequest** _retval) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); if (!mTransaction->IsOpen()) { return NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR; } if (!IsWriteAllowed()) { return NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR; } nsRefPtr request = GenerateRequest(this); IDB_ENSURE_TRUE(request, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); StructuredCloneWriteInfo cloneWriteInfo; if (!cloneWriteInfo.SetFromSerialized(aCloneWriteInfo)) { IDB_WARNING("Failed to copy structured clone buffer!"); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } if (!aBlobs.IsEmpty()) { FileManager* fileManager = Transaction()->Database()->Manager(); NS_ASSERTION(fileManager, "Null file manager?!"); uint32_t length = aBlobs.Length(); cloneWriteInfo.mFiles.SetCapacity(length); for (uint32_t index = 0; index < length; index++) { const nsCOMPtr& blob = aBlobs[index]; nsCOMPtr inputStream; nsRefPtr fileInfo = Transaction()->GetFileInfo(blob); if (!fileInfo) { fileInfo = blob->GetFileInfo(fileManager); if (!fileInfo) { fileInfo = fileManager->GetNewFileInfo(); if (!fileInfo) { IDB_WARNING("Failed to get new file info!"); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } if (NS_FAILED(blob->GetInternalStream(getter_AddRefs(inputStream)))) { IDB_WARNING("Failed to get internal steam!"); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } // XXXbent This is where we should send a message back to the child to // update the file id. Transaction()->AddFileInfo(blob, fileInfo); } } StructuredCloneFile* file = cloneWriteInfo.mFiles.AppendElement(); file->mFile = blob; file->mFileInfo.swap(fileInfo); file->mInputStream.swap(inputStream); } } Key key(aKey); nsTArray updateInfo(aUpdateInfoArray); nsRefPtr helper = new AddHelper(mTransaction, request, this, Move(cloneWriteInfo), key, aOverwrite, updateInfo); nsresult rv = helper->DispatchToTransactionPool(); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); #ifdef IDB_PROFILER_USE_MARKS if (aOverwrite) { IDB_PROFILER_MARK("IndexedDB Request %llu: " "database(%s).transaction(%s).objectStore(%s).%s(%s)", "IDBRequest[%llu] MT IDBObjectStore.put()", request->GetSerialNumber(), IDB_PROFILER_STRING(Transaction()->Database()), IDB_PROFILER_STRING(Transaction()), IDB_PROFILER_STRING(this), key.IsUnset() ? "" : IDB_PROFILER_STRING(key)); } else { IDB_PROFILER_MARK("IndexedDB Request %llu: " "database(%s).transaction(%s).objectStore(%s).add(%s)", "IDBRequest[%llu] MT IDBObjectStore.add()", request->GetSerialNumber(), IDB_PROFILER_STRING(Transaction()->Database()), IDB_PROFILER_STRING(Transaction()), IDB_PROFILER_STRING(this), key.IsUnset() ? "" : IDB_PROFILER_STRING(key)); } #endif request.forget(_retval); return NS_OK; } already_AddRefed IDBObjectStore::GetInternal(IDBKeyRange* aKeyRange, ErrorResult& aRv) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(aKeyRange, "Null pointer!"); if (!mTransaction->IsOpen()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } nsRefPtr request = GenerateRequest(this); if (!request) { IDB_WARNING("Failed to generate request!"); aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return nullptr; } nsRefPtr helper = new GetHelper(mTransaction, request, this, aKeyRange); nsresult rv = helper->DispatchToTransactionPool(); if (NS_FAILED(rv)) { IDB_WARNING("Failed to dispatch!"); aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return nullptr; } IDB_PROFILER_MARK("IndexedDB Request %llu: " "database(%s).transaction(%s).objectStore(%s).get(%s)", "IDBRequest[%llu] MT IDBObjectStore.get()", request->GetSerialNumber(), IDB_PROFILER_STRING(Transaction()->Database()), IDB_PROFILER_STRING(Transaction()), IDB_PROFILER_STRING(this), IDB_PROFILER_STRING(aKeyRange)); return request.forget(); } already_AddRefed IDBObjectStore::GetAllInternal(IDBKeyRange* aKeyRange, uint32_t aLimit, ErrorResult& aRv) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); if (!mTransaction->IsOpen()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } nsRefPtr request = GenerateRequest(this); if (!request) { IDB_WARNING("Failed to generate request!"); aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return nullptr; } nsRefPtr helper = new GetAllHelper(mTransaction, request, this, aKeyRange, aLimit); nsresult rv = helper->DispatchToTransactionPool(); if (NS_FAILED(rv)) { IDB_WARNING("Failed to dispatch!"); aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return nullptr; } IDB_PROFILER_MARK("IndexedDB Request %llu: " "database(%s).transaction(%s).objectStore(%s)." "getAll(%s, %lu)", "IDBRequest[%llu] MT IDBObjectStore.getAll()", request->GetSerialNumber(), IDB_PROFILER_STRING(Transaction()->Database()), IDB_PROFILER_STRING(Transaction()), IDB_PROFILER_STRING(this), IDB_PROFILER_STRING(aKeyRange), aLimit); return request.forget(); } already_AddRefed IDBObjectStore::GetAllKeysInternal(IDBKeyRange* aKeyRange, uint32_t aLimit, ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread()); if (!mTransaction->IsOpen()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } nsRefPtr request = GenerateRequest(this); if (!request) { IDB_WARNING("Failed to generate request!"); aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return nullptr; } nsRefPtr helper = new GetAllKeysHelper(mTransaction, request, this, aKeyRange, aLimit); nsresult rv = helper->DispatchToTransactionPool(); if (NS_FAILED(rv)) { IDB_WARNING("Failed to dispatch!"); aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return nullptr; } IDB_PROFILER_MARK("IndexedDB Request %llu: " "database(%s).transaction(%s).objectStore(%s)." "getAllKeys(%s, %lu)", "IDBRequest[%llu] MT IDBObjectStore.getAllKeys()", request->GetSerialNumber(), IDB_PROFILER_STRING(Transaction()->Database()), IDB_PROFILER_STRING(Transaction()), IDB_PROFILER_STRING(this), IDB_PROFILER_STRING(aKeyRange), aLimit); return request.forget(); } already_AddRefed IDBObjectStore::DeleteInternal(IDBKeyRange* aKeyRange, ErrorResult& aRv) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(aKeyRange, "Null key range!"); if (!mTransaction->IsOpen()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } if (!IsWriteAllowed()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR); return nullptr; } nsRefPtr request = GenerateRequest(this); if (!request) { IDB_WARNING("Failed to generate request!"); aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return nullptr; } nsRefPtr helper = new DeleteHelper(mTransaction, request, this, aKeyRange); nsresult rv = helper->DispatchToTransactionPool(); if (NS_FAILED(rv)) { IDB_WARNING("Failed to dispatch!"); aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return nullptr; } IDB_PROFILER_MARK("IndexedDB Request %llu: " "database(%s).transaction(%s).objectStore(%s).delete(%s)", "IDBRequest[%llu] MT IDBObjectStore.delete()", request->GetSerialNumber(), IDB_PROFILER_STRING(Transaction()->Database()), IDB_PROFILER_STRING(Transaction()), IDB_PROFILER_STRING(this), IDB_PROFILER_STRING(aKeyRange)); return request.forget(); } already_AddRefed IDBObjectStore::Clear(ErrorResult& aRv) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); if (!mTransaction->IsOpen()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } if (!IsWriteAllowed()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR); return nullptr; } nsRefPtr request = GenerateRequest(this); if (!request) { IDB_WARNING("Failed to generate request!"); aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return nullptr; } nsRefPtr helper(new ClearHelper(mTransaction, request, this)); nsresult rv = helper->DispatchToTransactionPool(); if (NS_FAILED(rv)) { IDB_WARNING("Failed to dispatch!"); aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return nullptr; } IDB_PROFILER_MARK("IndexedDB Request %llu: " "database(%s).transaction(%s).objectStore(%s).clear()", "IDBRequest[%llu] MT IDBObjectStore.clear()", request->GetSerialNumber(), IDB_PROFILER_STRING(Transaction()->Database()), IDB_PROFILER_STRING(Transaction()), IDB_PROFILER_STRING(this)); return request.forget(); } already_AddRefed IDBObjectStore::CountInternal(IDBKeyRange* aKeyRange, ErrorResult& aRv) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); if (!mTransaction->IsOpen()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } nsRefPtr request = GenerateRequest(this); if (!request) { IDB_WARNING("Failed to generate request!"); aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return nullptr; } nsRefPtr helper = new CountHelper(mTransaction, request, this, aKeyRange); nsresult rv = helper->DispatchToTransactionPool(); if (NS_FAILED(rv)) { IDB_WARNING("Failed to dispatch!"); aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return nullptr; } IDB_PROFILER_MARK("IndexedDB Request %llu: " "database(%s).transaction(%s).objectStore(%s).count(%s)", "IDBRequest[%llu] MT IDBObjectStore.count()", request->GetSerialNumber(), IDB_PROFILER_STRING(Transaction()->Database()), IDB_PROFILER_STRING(Transaction()), IDB_PROFILER_STRING(this), IDB_PROFILER_STRING(aKeyRange)); return request.forget(); } already_AddRefed IDBObjectStore::OpenCursorInternal(IDBKeyRange* aKeyRange, size_t aDirection, ErrorResult& aRv) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); if (!mTransaction->IsOpen()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } IDBCursor::Direction direction = static_cast(aDirection); nsRefPtr request = GenerateRequest(this); if (!request) { IDB_WARNING("Failed to generate request!"); aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return nullptr; } nsRefPtr helper = new OpenCursorHelper(mTransaction, request, this, aKeyRange, direction); nsresult rv = helper->DispatchToTransactionPool(); if (NS_FAILED(rv)) { IDB_WARNING("Failed to dispatch!"); aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return nullptr; } IDB_PROFILER_MARK("IndexedDB Request %llu: " "database(%s).transaction(%s).objectStore(%s)." "openCursor(%s, %s)", "IDBRequest[%llu] MT IDBObjectStore.openCursor()", request->GetSerialNumber(), IDB_PROFILER_STRING(Transaction()->Database()), IDB_PROFILER_STRING(Transaction()), IDB_PROFILER_STRING(this), IDB_PROFILER_STRING(aKeyRange), IDB_PROFILER_STRING(direction)); return request.forget(); } nsresult IDBObjectStore::OpenCursorFromChildProcess( IDBRequest* aRequest, size_t aDirection, const Key& aKey, const SerializedStructuredCloneReadInfo& aCloneInfo, nsTArray& aBlobs, IDBCursor** _retval) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION((!aCloneInfo.dataLength && !aCloneInfo.data) || (aCloneInfo.dataLength && aCloneInfo.data), "Inconsistent clone info!"); IDBCursor::Direction direction = static_cast(aDirection); StructuredCloneReadInfo cloneInfo; if (!cloneInfo.SetFromSerialized(aCloneInfo)) { IDB_WARNING("Failed to copy clone buffer!"); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } cloneInfo.mFiles.SwapElements(aBlobs); nsRefPtr cursor = IDBCursor::Create(aRequest, mTransaction, this, direction, Key(), EmptyCString(), EmptyCString(), aKey, Move(cloneInfo)); IDB_ENSURE_TRUE(cursor, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); NS_ASSERTION(!cloneInfo.mCloneBuffer.data(), "Should have swapped!"); cursor.forget(_retval); return NS_OK; } nsresult IDBObjectStore::OpenCursorFromChildProcess(IDBRequest* aRequest, size_t aDirection, const Key& aKey, IDBCursor** _retval) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aRequest); auto direction = static_cast(aDirection); nsRefPtr cursor = IDBCursor::Create(aRequest, mTransaction, this, direction, Key(), EmptyCString(), EmptyCString(), aKey); IDB_ENSURE_TRUE(cursor, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); cursor.forget(_retval); return NS_OK; } already_AddRefed IDBObjectStore::OpenKeyCursorInternal(IDBKeyRange* aKeyRange, size_t aDirection, ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread()); if (!mTransaction->IsOpen()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } nsRefPtr request = GenerateRequest(this); if (!request) { IDB_WARNING("Failed to generate request!"); aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return nullptr; } auto direction = static_cast(aDirection); nsRefPtr helper = new OpenKeyCursorHelper(mTransaction, request, this, aKeyRange, direction); nsresult rv = helper->DispatchToTransactionPool(); if (NS_FAILED(rv)) { IDB_WARNING("Failed to dispatch!"); aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return nullptr; } IDB_PROFILER_MARK("IndexedDB Request %llu: " "database(%s).transaction(%s).objectStore(%s)." "openKeyCursor(%s, %s)", "IDBRequest[%llu] MT IDBObjectStore.openKeyCursor()", request->GetSerialNumber(), IDB_PROFILER_STRING(Transaction()->Database()), IDB_PROFILER_STRING(Transaction()), IDB_PROFILER_STRING(this), IDB_PROFILER_STRING(aKeyRange), IDB_PROFILER_STRING(direction)); return request.forget(); } void IDBObjectStore::SetInfo(ObjectStoreInfo* aInfo) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread"); NS_ASSERTION(aInfo != mInfo, "This is nonsense"); mInfo = aInfo; } already_AddRefed IDBObjectStore::CreateIndexInternal(const IndexInfo& aInfo, ErrorResult& aRv) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); IndexInfo* indexInfo = mInfo->indexes.AppendElement(); indexInfo->name = aInfo.name; indexInfo->id = aInfo.id; indexInfo->keyPath = aInfo.keyPath; indexInfo->unique = aInfo.unique; indexInfo->multiEntry = aInfo.multiEntry; // Don't leave this in the list if we fail below! AutoRemoveIndex autoRemove(mInfo, aInfo.name); nsRefPtr index = IDBIndex::Create(this, indexInfo, true); mCreatedIndexes.AppendElement(index); if (IndexedDatabaseManager::IsMainProcess()) { nsRefPtr helper = new CreateIndexHelper(mTransaction, index); nsresult rv = helper->DispatchToTransactionPool(); if (NS_FAILED(rv)) { IDB_WARNING("Failed to dispatch!"); aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return nullptr; } } autoRemove.forget(); IDB_PROFILER_MARK("IndexedDB Pseudo-request: " "database(%s).transaction(%s).objectStore(%s)." "createIndex(%s)", "MT IDBObjectStore.createIndex()", IDB_PROFILER_STRING(Transaction()->Database()), IDB_PROFILER_STRING(Transaction()), IDB_PROFILER_STRING(this), IDB_PROFILER_STRING(index)); return index.forget(); } already_AddRefed IDBObjectStore::Index(const nsAString& aName, ErrorResult &aRv) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); if (mTransaction->IsFinished()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } IndexInfo* indexInfo = nullptr; uint32_t indexCount = mInfo->indexes.Length(); for (uint32_t index = 0; index < indexCount; index++) { if (mInfo->indexes[index].name == aName) { indexInfo = &(mInfo->indexes[index]); break; } } if (!indexInfo) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR); return nullptr; } nsRefPtr retval; for (uint32_t i = 0; i < mCreatedIndexes.Length(); i++) { nsRefPtr& index = mCreatedIndexes[i]; if (index->Name() == aName) { retval = index; break; } } if (!retval) { retval = IDBIndex::Create(this, indexInfo, false); if (!retval) { IDB_WARNING("Failed to create index!"); aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return nullptr; } if (!mCreatedIndexes.AppendElement(retval)) { IDB_WARNING("Out of memory!"); aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return nullptr; } } return retval.forget(); } NS_IMPL_CYCLE_COLLECTION_CLASS(IDBObjectStore) NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(IDBObjectStore) NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mCachedKeyPath) NS_IMPL_CYCLE_COLLECTION_TRACE_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IDBObjectStore) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTransaction) for (uint32_t i = 0; i < tmp->mCreatedIndexes.Length(); i++) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mCreatedIndexes[i]"); cb.NoteXPCOMChild(static_cast(tmp->mCreatedIndexes[i].get())); } NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IDBObjectStore) NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER // Don't unlink mTransaction! tmp->mCreatedIndexes.Clear(); tmp->mCachedKeyPath = JSVAL_VOID; if (tmp->mRooted) { mozilla::DropJSObjects(tmp); tmp->mRooted = false; } NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IDBObjectStore) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(IDBObjectStore) NS_IMPL_CYCLE_COLLECTING_RELEASE(IDBObjectStore) JSObject* IDBObjectStore::WrapObject(JSContext* aCx) { return IDBObjectStoreBinding::Wrap(aCx, this); } void IDBObjectStore::GetKeyPath(JSContext* aCx, JS::MutableHandle aResult, ErrorResult& aRv) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); if (!mCachedKeyPath.isUndefined()) { JS::ExposeValueToActiveJS(mCachedKeyPath); aResult.set(mCachedKeyPath); return; } aRv = GetKeyPath().ToJSVal(aCx, mCachedKeyPath); if (NS_WARN_IF(aRv.Failed())) { return; } if (mCachedKeyPath.isGCThing()) { mozilla::HoldJSObjects(this); mRooted = true; } JS::ExposeValueToActiveJS(mCachedKeyPath); aResult.set(mCachedKeyPath); } already_AddRefed IDBObjectStore::GetIndexNames(ErrorResult& aRv) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); nsRefPtr list(new DOMStringList()); nsTArray& names = list->StringArray(); uint32_t count = mInfo->indexes.Length(); names.SetCapacity(count); for (uint32_t index = 0; index < count; index++) { names.InsertElementSorted(mInfo->indexes[index].name); } return list.forget(); } already_AddRefed IDBObjectStore::Get(JSContext* aCx, JS::Handle aKey, ErrorResult& aRv) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); if (!mTransaction->IsOpen()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } nsRefPtr keyRange; aRv = IDBKeyRange::FromJSVal(aCx, aKey, getter_AddRefs(keyRange)); ENSURE_SUCCESS(aRv, nullptr); if (!keyRange) { // Must specify a key or keyRange for get(). aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR); return nullptr; } return GetInternal(keyRange, aRv); } already_AddRefed IDBObjectStore::GetAll(JSContext* aCx, JS::Handle aKey, const Optional& aLimit, ErrorResult& aRv) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); if (!mTransaction->IsOpen()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } nsRefPtr keyRange; aRv = IDBKeyRange::FromJSVal(aCx, aKey, getter_AddRefs(keyRange)); ENSURE_SUCCESS(aRv, nullptr); uint32_t limit = UINT32_MAX; if (aLimit.WasPassed() && aLimit.Value() != 0) { limit = aLimit.Value(); } return GetAllInternal(keyRange, limit, aRv); } already_AddRefed IDBObjectStore::Delete(JSContext* aCx, JS::Handle aKey, ErrorResult& aRv) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); if (!mTransaction->IsOpen()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } if (!IsWriteAllowed()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR); return nullptr; } nsRefPtr keyRange; aRv = IDBKeyRange::FromJSVal(aCx, aKey, getter_AddRefs(keyRange)); ENSURE_SUCCESS(aRv, nullptr); if (!keyRange) { // Must specify a key or keyRange for delete(). aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR); return nullptr; } return DeleteInternal(keyRange, aRv); } already_AddRefed IDBObjectStore::OpenCursor(JSContext* aCx, JS::Handle aRange, IDBCursorDirection aDirection, ErrorResult& aRv) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); if (!mTransaction->IsOpen()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } nsRefPtr keyRange; aRv = IDBKeyRange::FromJSVal(aCx, aRange, getter_AddRefs(keyRange)); ENSURE_SUCCESS(aRv, nullptr); IDBCursor::Direction direction = IDBCursor::ConvertDirection(aDirection); size_t argDirection = static_cast(direction); return OpenCursorInternal(keyRange, argDirection, aRv); } already_AddRefed IDBObjectStore::CreateIndex(JSContext* aCx, const nsAString& aName, const nsAString& aKeyPath, const IDBIndexParameters& aOptionalParameters, ErrorResult& aRv) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); KeyPath keyPath(0); if (NS_FAILED(KeyPath::Parse(aCx, aKeyPath, &keyPath)) || !keyPath.IsValid()) { aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); return nullptr; } return CreateIndex(aCx, aName, keyPath, aOptionalParameters, aRv); } already_AddRefed IDBObjectStore::CreateIndex(JSContext* aCx, const nsAString& aName, const Sequence& aKeyPath, const IDBIndexParameters& aOptionalParameters, ErrorResult& aRv) { NS_PRECONDITION(NS_IsMainThread(), "Wrong thread!"); if (!aKeyPath.Length()) { aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); return nullptr; } KeyPath keyPath(0); if (NS_FAILED(KeyPath::Parse(aCx, aKeyPath, &keyPath))) { aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); return nullptr; } return CreateIndex(aCx, aName, keyPath, aOptionalParameters, aRv); } already_AddRefed IDBObjectStore::CreateIndex(JSContext* aCx, const nsAString& aName, KeyPath& aKeyPath, const IDBIndexParameters& aOptionalParameters, ErrorResult& aRv) { // Check name and current mode IDBTransaction* transaction = AsyncConnectionHelper::GetCurrentTransaction(); if (!transaction || transaction != mTransaction || mTransaction->GetMode() != IDBTransaction::VERSION_CHANGE) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); return nullptr; } bool found = false; uint32_t indexCount = mInfo->indexes.Length(); for (uint32_t index = 0; index < indexCount; index++) { if (mInfo->indexes[index].name == aName) { found = true; break; } } if (found) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR); return nullptr; } NS_ASSERTION(mTransaction->IsOpen(), "Impossible!"); #ifdef DEBUG for (uint32_t index = 0; index < mCreatedIndexes.Length(); index++) { if (mCreatedIndexes[index]->Name() == aName) { NS_ERROR("Already created this one!"); } } #endif if (aOptionalParameters.mMultiEntry && aKeyPath.IsArray()) { aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); return nullptr; } DatabaseInfo* databaseInfo = mTransaction->DBInfo(); IndexInfo info; info.name = aName; info.id = databaseInfo->nextIndexId++; info.keyPath = aKeyPath; info.unique = aOptionalParameters.mUnique; info.multiEntry = aOptionalParameters.mMultiEntry; return CreateIndexInternal(info, aRv); } void IDBObjectStore::DeleteIndex(const nsAString& aName, ErrorResult& aRv) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); IDBTransaction* transaction = AsyncConnectionHelper::GetCurrentTransaction(); if (!transaction || transaction != mTransaction || mTransaction->GetMode() != IDBTransaction::VERSION_CHANGE) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); return; } NS_ASSERTION(mTransaction->IsOpen(), "Impossible!"); uint32_t index = 0; for (; index < mInfo->indexes.Length(); index++) { if (mInfo->indexes[index].name == aName) { break; } } if (index == mInfo->indexes.Length()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR); return; } if (IndexedDatabaseManager::IsMainProcess()) { nsRefPtr helper = new DeleteIndexHelper(mTransaction, this, aName); nsresult rv = helper->DispatchToTransactionPool(); if (NS_FAILED(rv)) { IDB_WARNING("Failed to dispatch!"); aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return; } } else { NS_ASSERTION(mActorChild, "Must have an actor here!"); mActorChild->SendDeleteIndex(nsString(aName)); } mInfo->indexes.RemoveElementAt(index); for (uint32_t i = 0; i < mCreatedIndexes.Length(); i++) { if (mCreatedIndexes[i]->Name() == aName) { mCreatedIndexes.RemoveElementAt(i); break; } } IDB_PROFILER_MARK("IndexedDB Pseudo-request: " "database(%s).transaction(%s).objectStore(%s)." "deleteIndex(\"%s\")", "MT IDBObjectStore.deleteIndex()", IDB_PROFILER_STRING(Transaction()->Database()), IDB_PROFILER_STRING(Transaction()), IDB_PROFILER_STRING(this), NS_ConvertUTF16toUTF8(aName).get()); } already_AddRefed IDBObjectStore::Count(JSContext* aCx, JS::Handle aKey, ErrorResult& aRv) { if (!mTransaction->IsOpen()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } nsRefPtr keyRange; aRv = IDBKeyRange::FromJSVal(aCx, aKey, getter_AddRefs(keyRange)); ENSURE_SUCCESS(aRv, nullptr); return CountInternal(keyRange, aRv); } already_AddRefed IDBObjectStore::GetAllKeys(JSContext* aCx, JS::Handle aKey, const Optional& aLimit, ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread()); if (!mTransaction->IsOpen()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } nsRefPtr keyRange; aRv = IDBKeyRange::FromJSVal(aCx, aKey, getter_AddRefs(keyRange)); ENSURE_SUCCESS(aRv, nullptr); uint32_t limit = UINT32_MAX; if (aLimit.WasPassed() && aLimit.Value() != 0) { limit = aLimit.Value(); } return GetAllKeysInternal(keyRange, limit, aRv); } already_AddRefed IDBObjectStore::OpenKeyCursor(JSContext* aCx, JS::Handle aRange, IDBCursorDirection aDirection, ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread()); if (!mTransaction->IsOpen()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } nsRefPtr keyRange; aRv = IDBKeyRange::FromJSVal(aCx, aRange, getter_AddRefs(keyRange)); ENSURE_SUCCESS(aRv, nullptr); IDBCursor::Direction direction = IDBCursor::ConvertDirection(aDirection); return OpenKeyCursorInternal(keyRange, static_cast(direction), aRv); } inline nsresult CopyData(nsIInputStream* aInputStream, nsIOutputStream* aOutputStream) { NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); PROFILER_LABEL("IDBObjectStore", "CopyData", js::ProfileEntry::Category::STORAGE); nsresult rv; do { char copyBuffer[FILE_COPY_BUFFER_SIZE]; uint32_t numRead; rv = aInputStream->Read(copyBuffer, sizeof(copyBuffer), &numRead); NS_ENSURE_SUCCESS(rv, rv); if (!numRead) { break; } uint32_t numWrite; rv = aOutputStream->Write(copyBuffer, numRead, &numWrite); if (rv == NS_ERROR_FILE_NO_DEVICE_SPACE) { rv = NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR; } NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_TRUE(numWrite == numRead, NS_ERROR_FAILURE); } while (true); rv = aOutputStream->Flush(); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } void ObjectStoreHelper::ReleaseMainThreadObjects() { mObjectStore = nullptr; AsyncConnectionHelper::ReleaseMainThreadObjects(); } nsresult ObjectStoreHelper::Dispatch(nsIEventTarget* aDatabaseThread) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); PROFILER_MAIN_THREAD_LABEL("ObjectStoreHelper", "Dispatch", js::ProfileEntry::Category::STORAGE); if (IndexedDatabaseManager::IsMainProcess()) { return AsyncConnectionHelper::Dispatch(aDatabaseThread); } // If we've been invalidated then there's no point sending anything to the // parent process. if (mObjectStore->Transaction()->Database()->IsInvalidated()) { IDB_REPORT_INTERNAL_ERR(); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } IndexedDBObjectStoreChild* objectStoreActor = mObjectStore->GetActorChild(); NS_ASSERTION(objectStoreActor, "Must have an actor here!"); ObjectStoreRequestParams params; // Our "parent" process may be either the root process or another content // process if this indexedDB is managed by a PBrowser that is managed by a // PContentBridge. We need to find which one it is so that we can create // PBlobs that are managed by the right nsIContentChild. IndexedDBChild* rootActor = static_cast(objectStoreActor->Manager()-> Manager()->Manager()); nsIContentChild* blobCreator; if (rootActor->GetManagerContent()) { blobCreator = rootActor->GetManagerContent(); } else { blobCreator = rootActor->GetManagerTab()->Manager(); } nsresult rv = PackArgumentsForParentProcess(params, blobCreator); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); NoDispatchEventTarget target; rv = AsyncConnectionHelper::Dispatch(&target); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); mActor = new IndexedDBObjectStoreRequestChild(this, mObjectStore, params.type()); objectStoreActor->SendPIndexedDBRequestConstructor(mActor, params); return NS_OK; } void NoRequestObjectStoreHelper::ReleaseMainThreadObjects() { NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); mObjectStore = nullptr; AsyncConnectionHelper::ReleaseMainThreadObjects(); } nsresult NoRequestObjectStoreHelper::UnpackResponseFromParentProcess( const ResponseValue& aResponseValue) { MOZ_CRASH(); } AsyncConnectionHelper::ChildProcessSendResult NoRequestObjectStoreHelper::SendResponseToChildProcess(nsresult aResultCode) { NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); return Success_NotSent; } nsresult NoRequestObjectStoreHelper::OnSuccess() { NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); return NS_OK; } void NoRequestObjectStoreHelper::OnError() { NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); mTransaction->Abort(GetResultCode()); } // This is a duplicate of the js engine's byte munging in StructuredClone.cpp uint64_t ReinterpretDoubleAsUInt64(double d) { union { double d; uint64_t u; } pun; pun.d = d; return pun.u; } nsresult AddHelper::DoDatabaseWork(mozIStorageConnection* aConnection) { NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); NS_ASSERTION(aConnection, "Passed a null connection!"); PROFILER_LABEL("AddHelper", "DoDatabaseWork", js::ProfileEntry::Category::STORAGE); if (IndexedDatabaseManager::InLowDiskSpaceMode()) { NS_WARNING("Refusing to add more data because disk space is low!"); return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR; } nsresult rv; bool keyUnset = mKey.IsUnset(); int64_t osid = mObjectStore->Id(); const KeyPath& keyPath = mObjectStore->GetKeyPath(); // The "|| keyUnset" here is mostly a debugging tool. If a key isn't // specified we should never have a collision and so it shouldn't matter // if we allow overwrite or not. By not allowing overwrite we raise // detectable errors rather than corrupting data nsCOMPtr stmt = !mOverwrite || keyUnset ? mTransaction->GetCachedStatement( "INSERT INTO object_data (object_store_id, key_value, data, file_ids) " "VALUES (:osid, :key_value, :data, :file_ids)") : mTransaction->GetCachedStatement( "INSERT OR REPLACE INTO object_data (object_store_id, key_value, data, " "file_ids) " "VALUES (:osid, :key_value, :data, :file_ids)"); IDB_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); mozStorageStatementScoper scoper(stmt); rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"), osid); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); NS_ASSERTION(!keyUnset || mObjectStore->IsAutoIncrement(), "Should have key unless autoincrement"); int64_t autoIncrementNum = 0; if (mObjectStore->IsAutoIncrement()) { if (keyUnset) { autoIncrementNum = mObjectStore->Info()->nextAutoIncrementId; MOZ_ASSERT(autoIncrementNum > 0, "Generated key must always be a positive integer"); if (autoIncrementNum > (1LL << 53)) { IDB_REPORT_INTERNAL_ERR(); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } mKey.SetFromInteger(autoIncrementNum); } else if (mKey.IsFloat() && mKey.ToFloat() >= mObjectStore->Info()->nextAutoIncrementId) { autoIncrementNum = floor(mKey.ToFloat()); } if (keyUnset && keyPath.IsValid()) { // Special case where someone put an object into an autoIncrement'ing // objectStore with no key in its keyPath set. We needed to figure out // which row id we would get above before we could set that properly. LittleEndian::writeUint64((char*)mCloneWriteInfo.mCloneBuffer.data() + mCloneWriteInfo.mOffsetToKeyProp, ReinterpretDoubleAsUInt64(static_cast( autoIncrementNum))); } } mKey.BindToStatement(stmt, NS_LITERAL_CSTRING("key_value")); // Compress the bytes before adding into the database. const char* uncompressed = reinterpret_cast(mCloneWriteInfo.mCloneBuffer.data()); size_t uncompressedLength = mCloneWriteInfo.mCloneBuffer.nbytes(); // We don't have a smart pointer class that calls moz_free, so we need to // manage | compressed | manually. { size_t compressedLength = snappy::MaxCompressedLength(uncompressedLength); // moz_malloc is equivalent to NS_Alloc, which we use because mozStorage // expects to be able to free the adopted pointer with NS_Free. char* compressed = (char*)moz_malloc(compressedLength); NS_ENSURE_TRUE(compressed, NS_ERROR_OUT_OF_MEMORY); snappy::RawCompress(uncompressed, uncompressedLength, compressed, &compressedLength); uint8_t* dataBuffer = reinterpret_cast(compressed); size_t dataBufferLength = compressedLength; // If this call succeeds, | compressed | is now owned by the statement, and // we are no longer responsible for it. rv = stmt->BindAdoptedBlobByName(NS_LITERAL_CSTRING("data"), dataBuffer, dataBufferLength); if (NS_FAILED(rv)) { moz_free(compressed); } IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); } // Handle blobs uint32_t length = mCloneWriteInfo.mFiles.Length(); if (length) { nsRefPtr fileManager = mDatabase->Manager(); nsCOMPtr directory = fileManager->GetDirectory(); IDB_ENSURE_TRUE(directory, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); nsCOMPtr journalDirectory = fileManager->EnsureJournalDirectory(); IDB_ENSURE_TRUE(journalDirectory, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); nsAutoString fileIds; for (uint32_t index = 0; index < length; index++) { StructuredCloneFile& cloneFile = mCloneWriteInfo.mFiles[index]; FileInfo* fileInfo = cloneFile.mFileInfo; nsIInputStream* inputStream = cloneFile.mInputStream; int64_t id = fileInfo->Id(); if (inputStream) { // Create a journal file first nsCOMPtr nativeFile = fileManager->GetFileForId(journalDirectory, id); IDB_ENSURE_TRUE(nativeFile, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); rv = nativeFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644); IDB_ENSURE_TRUE(nativeFile, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); // Now we can copy the blob nativeFile = fileManager->GetFileForId(directory, id); IDB_ENSURE_TRUE(nativeFile, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); IDBDatabase* database = mObjectStore->Transaction()->Database(); nsRefPtr outputStream = FileOutputStream::Create(database->Type(), database->Group(), database->Origin(), nativeFile); IDB_ENSURE_TRUE(outputStream, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); rv = CopyData(inputStream, outputStream); if (NS_FAILED(rv) && NS_ERROR_GET_MODULE(rv) != NS_ERROR_MODULE_DOM_INDEXEDDB) { IDB_REPORT_INTERNAL_ERR(); rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } NS_ENSURE_SUCCESS(rv, rv); cloneFile.mFile->AddFileInfo(fileInfo); } if (index) { fileIds.Append(' '); } fileIds.AppendInt(id); } rv = stmt->BindStringByName(NS_LITERAL_CSTRING("file_ids"), fileIds); } else { rv = stmt->BindNullByName(NS_LITERAL_CSTRING("file_ids")); } IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); rv = stmt->Execute(); if (rv == NS_ERROR_STORAGE_CONSTRAINT) { NS_ASSERTION(!keyUnset, "Generated key had a collision!?"); return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR; } IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); int64_t objectDataId; rv = aConnection->GetLastInsertRowID(&objectDataId); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); // Update our indexes if needed. if (mOverwrite || !mIndexUpdateInfo.IsEmpty()) { rv = IDBObjectStore::UpdateIndexes(mTransaction, osid, mKey, mOverwrite, objectDataId, mIndexUpdateInfo); if (rv == NS_ERROR_STORAGE_CONSTRAINT) { return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR; } IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); } if (autoIncrementNum) { mObjectStore->Info()->nextAutoIncrementId = autoIncrementNum + 1; } return NS_OK; } nsresult AddHelper::GetSuccessResult(JSContext* aCx, JS::MutableHandle aVal) { NS_ASSERTION(!mKey.IsUnset(), "Badness!"); mCloneWriteInfo.mCloneBuffer.clear(); return mKey.ToJSVal(aCx, aVal); } void AddHelper::ReleaseMainThreadObjects() { IDBObjectStore::ClearCloneWriteInfo(mCloneWriteInfo); ObjectStoreHelper::ReleaseMainThreadObjects(); } nsresult AddHelper::PackArgumentsForParentProcess(ObjectStoreRequestParams& aParams, nsIContentChild* aBlobCreator) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(!IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); NS_ASSERTION(aBlobCreator, "Must have a valid creator!"); PROFILER_MAIN_THREAD_LABEL("AddHelper", "PackArgumentsForParentProcess", js::ProfileEntry::Category::STORAGE); AddPutParams commonParams; commonParams.cloneInfo() = mCloneWriteInfo; commonParams.key() = mKey; commonParams.indexUpdateInfos().AppendElements(mIndexUpdateInfo); const nsTArray& files = mCloneWriteInfo.mFiles; if (!files.IsEmpty()) { uint32_t fileCount = files.Length(); InfallibleTArray& blobsChild = commonParams.blobsChild(); blobsChild.SetCapacity(fileCount); NS_ASSERTION(aBlobCreator, "This should never be null!"); for (uint32_t index = 0; index < fileCount; index++) { const StructuredCloneFile& file = files[index]; NS_ASSERTION(file.mFile, "This should never be null!"); NS_ASSERTION(!file.mFileInfo, "This is not yet supported!"); BlobChild* actor = aBlobCreator->GetOrCreateActorForBlob(file.mFile); if (!actor) { IDB_REPORT_INTERNAL_ERR(); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } blobsChild.AppendElement(actor); } } if (mOverwrite) { PutParams putParams; putParams.commonParams() = commonParams; aParams = putParams; } else { AddParams addParams; addParams.commonParams() = commonParams; aParams = addParams; } return NS_OK; } AsyncConnectionHelper::ChildProcessSendResult AddHelper::SendResponseToChildProcess(nsresult aResultCode) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); PROFILER_MAIN_THREAD_LABEL("AddHelper", "SendResponseToChildProcess", js::ProfileEntry::Category::STORAGE); IndexedDBRequestParentBase* actor = mRequest->GetActorParent(); NS_ASSERTION(actor, "How did we get this far without an actor?"); ResponseValue response; if (NS_FAILED(aResultCode)) { response = aResultCode; } else if (mOverwrite) { PutResponse putResponse; putResponse.key() = mKey; response = putResponse; } else { AddResponse addResponse; addResponse.key() = mKey; response = addResponse; } if (!actor->SendResponse(response)) { return Error; } return Success_Sent; } nsresult AddHelper::UnpackResponseFromParentProcess(const ResponseValue& aResponseValue) { NS_ASSERTION(aResponseValue.type() == ResponseValue::TAddResponse || aResponseValue.type() == ResponseValue::TPutResponse, "Bad response type!"); mKey = mOverwrite ? aResponseValue.get_PutResponse().key() : aResponseValue.get_AddResponse().key(); return NS_OK; } nsresult GetHelper::DoDatabaseWork(mozIStorageConnection* /* aConnection */) { NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); NS_ASSERTION(mKeyRange, "Must have a key range here!"); PROFILER_LABEL("GetHelper", "DoDatabaseWork [IDBObjectStore.cpp]", js::ProfileEntry::Category::STORAGE); nsCString keyRangeClause; mKeyRange->GetBindingClause(NS_LITERAL_CSTRING("key_value"), keyRangeClause); NS_ASSERTION(!keyRangeClause.IsEmpty(), "Huh?!"); nsCString query = NS_LITERAL_CSTRING("SELECT data, file_ids FROM object_data " "WHERE object_store_id = :osid") + keyRangeClause + NS_LITERAL_CSTRING(" LIMIT 1"); nsCOMPtr stmt = mTransaction->GetCachedStatement(query); IDB_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); mozStorageStatementScoper scoper(stmt); nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"), mObjectStore->Id()); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); rv = mKeyRange->BindToStatement(stmt); NS_ENSURE_SUCCESS(rv, rv); bool hasResult; rv = stmt->ExecuteStep(&hasResult); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (hasResult) { rv = IDBObjectStore::GetStructuredCloneReadInfoFromStatement(stmt, 0, 1, mDatabase, mCloneReadInfo); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } nsresult GetHelper::GetSuccessResult(JSContext* aCx, JS::MutableHandle aVal) { bool result = IDBObjectStore::DeserializeValue(aCx, mCloneReadInfo, aVal); mCloneReadInfo.mCloneBuffer.clear(); NS_ENSURE_TRUE(result, NS_ERROR_DOM_DATA_CLONE_ERR); return NS_OK; } void GetHelper::ReleaseMainThreadObjects() { mKeyRange = nullptr; IDBObjectStore::ClearCloneReadInfo(mCloneReadInfo); ObjectStoreHelper::ReleaseMainThreadObjects(); } nsresult GetHelper::PackArgumentsForParentProcess(ObjectStoreRequestParams& aParams, nsIContentChild* aBlobCreator) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(!IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); NS_ASSERTION(mKeyRange, "This should never be null!"); NS_ASSERTION(aBlobCreator, "Must have a valid creator!"); PROFILER_MAIN_THREAD_LABEL("GetHelper", "PackArgumentsForParentProcess [IDBObjectStore.cpp]", js::ProfileEntry::Category::STORAGE); GetParams params; mKeyRange->ToSerializedKeyRange(params.keyRange()); aParams = params; return NS_OK; } AsyncConnectionHelper::ChildProcessSendResult GetHelper::SendResponseToChildProcess(nsresult aResultCode) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); PROFILER_MAIN_THREAD_LABEL("GetHelper", "SendResponseToChildProcess [IDBObjectStore.cpp]", js::ProfileEntry::Category::STORAGE); IndexedDBRequestParentBase* actor = mRequest->GetActorParent(); NS_ASSERTION(actor, "How did we get this far without an actor?"); InfallibleTArray blobsParent; if (NS_SUCCEEDED(aResultCode)) { IDBDatabase* database = mObjectStore->Transaction()->Database(); NS_ASSERTION(database, "This should never be null!"); nsIContentParent* contentParent = database->GetContentParent(); NS_ASSERTION(contentParent, "This should never be null!"); FileManager* fileManager = database->Manager(); NS_ASSERTION(fileManager, "This should never be null!"); const nsTArray& files = mCloneReadInfo.mFiles; aResultCode = IDBObjectStore::ConvertBlobsToActors(contentParent, fileManager, files, blobsParent); if (NS_FAILED(aResultCode)) { NS_WARNING("ConvertBlobsToActors failed!"); } } ResponseValue response; if (NS_FAILED(aResultCode)) { response = aResultCode; } else { GetResponse getResponse; getResponse.cloneInfo() = mCloneReadInfo; getResponse.blobsParent().SwapElements(blobsParent); response = getResponse; } if (!actor->SendResponse(response)) { return Error; } return Success_Sent; } nsresult GetHelper::UnpackResponseFromParentProcess(const ResponseValue& aResponseValue) { NS_ASSERTION(aResponseValue.type() == ResponseValue::TGetResponse, "Bad response type!"); const GetResponse& getResponse = aResponseValue.get_GetResponse(); const SerializedStructuredCloneReadInfo& cloneInfo = getResponse.cloneInfo(); NS_ASSERTION((!cloneInfo.dataLength && !cloneInfo.data) || (cloneInfo.dataLength && cloneInfo.data), "Inconsistent clone info!"); if (!mCloneReadInfo.SetFromSerialized(cloneInfo)) { IDB_WARNING("Failed to copy clone buffer!"); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } IDBObjectStore::ConvertActorsToBlobs(getResponse.blobsChild(), mCloneReadInfo.mFiles); return NS_OK; } nsresult DeleteHelper::DoDatabaseWork(mozIStorageConnection* /*aConnection */) { NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); NS_ASSERTION(mKeyRange, "Must have a key range here!"); PROFILER_LABEL("DeleteHelper", "DoDatabaseWork", js::ProfileEntry::Category::STORAGE); nsCString keyRangeClause; mKeyRange->GetBindingClause(NS_LITERAL_CSTRING("key_value"), keyRangeClause); NS_ASSERTION(!keyRangeClause.IsEmpty(), "Huh?!"); nsCString query = NS_LITERAL_CSTRING("DELETE FROM object_data " "WHERE object_store_id = :osid") + keyRangeClause; nsCOMPtr stmt = mTransaction->GetCachedStatement(query); IDB_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); mozStorageStatementScoper scoper(stmt); nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"), mObjectStore->Id()); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); rv = mKeyRange->BindToStatement(stmt); NS_ENSURE_SUCCESS(rv, rv); rv = stmt->Execute(); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return NS_OK; } nsresult DeleteHelper::GetSuccessResult(JSContext* aCx, JS::MutableHandle aVal) { aVal.setUndefined(); return NS_OK; } nsresult DeleteHelper::PackArgumentsForParentProcess(ObjectStoreRequestParams& aParams, nsIContentChild* aBlobCreator) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(!IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); NS_ASSERTION(mKeyRange, "This should never be null!"); NS_ASSERTION(aBlobCreator, "Must have a valid creator!"); PROFILER_MAIN_THREAD_LABEL("DeleteHelper", "PackArgumentsForParentProcess", js::ProfileEntry::Category::STORAGE); DeleteParams params; mKeyRange->ToSerializedKeyRange(params.keyRange()); aParams = params; return NS_OK; } AsyncConnectionHelper::ChildProcessSendResult DeleteHelper::SendResponseToChildProcess(nsresult aResultCode) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); PROFILER_MAIN_THREAD_LABEL("DeleteHelper", "SendResponseToChildProcess", js::ProfileEntry::Category::STORAGE); IndexedDBRequestParentBase* actor = mRequest->GetActorParent(); NS_ASSERTION(actor, "How did we get this far without an actor?"); ResponseValue response; if (NS_FAILED(aResultCode)) { response = aResultCode; } else { response = DeleteResponse(); } if (!actor->SendResponse(response)) { return Error; } return Success_Sent; } nsresult DeleteHelper::UnpackResponseFromParentProcess( const ResponseValue& aResponseValue) { NS_ASSERTION(aResponseValue.type() == ResponseValue::TDeleteResponse, "Bad response type!"); return NS_OK; } nsresult ClearHelper::DoDatabaseWork(mozIStorageConnection* aConnection) { NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); NS_ASSERTION(aConnection, "Passed a null connection!"); PROFILER_LABEL("ClearHelper", "DoDatabaseWork", js::ProfileEntry::Category::STORAGE); nsCOMPtr stmt = mTransaction->GetCachedStatement( NS_LITERAL_CSTRING("DELETE FROM object_data " "WHERE object_store_id = :osid")); IDB_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); mozStorageStatementScoper scoper(stmt); nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"), mObjectStore->Id()); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); rv = stmt->Execute(); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return NS_OK; } nsresult ClearHelper::PackArgumentsForParentProcess(ObjectStoreRequestParams& aParams, nsIContentChild* aBlobCreator) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(!IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); NS_ASSERTION(aBlobCreator, "Must have a valid creator!"); PROFILER_MAIN_THREAD_LABEL("ClearHelper", "PackArgumentsForParentProcess", js::ProfileEntry::Category::STORAGE); aParams = ClearParams(); return NS_OK; } AsyncConnectionHelper::ChildProcessSendResult ClearHelper::SendResponseToChildProcess(nsresult aResultCode) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); PROFILER_MAIN_THREAD_LABEL("ClearHelper", "SendResponseToChildProcess", js::ProfileEntry::Category::STORAGE); IndexedDBRequestParentBase* actor = mRequest->GetActorParent(); NS_ASSERTION(actor, "How did we get this far without an actor?"); ResponseValue response; if (NS_FAILED(aResultCode)) { response = aResultCode; } else { response = ClearResponse(); } if (!actor->SendResponse(response)) { return Error; } return Success_Sent; } nsresult ClearHelper::UnpackResponseFromParentProcess( const ResponseValue& aResponseValue) { NS_ASSERTION(aResponseValue.type() == ResponseValue::TClearResponse, "Bad response type!"); return NS_OK; } nsresult OpenCursorHelper::DoDatabaseWork(mozIStorageConnection* aConnection) { NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); PROFILER_LABEL("OpenCursorHelper", "DoDatabaseWork [IDBObjectStore.cpp]", js::ProfileEntry::Category::STORAGE); NS_NAMED_LITERAL_CSTRING(keyValue, "key_value"); nsCString keyRangeClause; if (mKeyRange) { mKeyRange->GetBindingClause(keyValue, keyRangeClause); } nsAutoCString directionClause; switch (mDirection) { case IDBCursor::NEXT: case IDBCursor::NEXT_UNIQUE: directionClause.AssignLiteral(" ORDER BY key_value ASC"); break; case IDBCursor::PREV: case IDBCursor::PREV_UNIQUE: directionClause.AssignLiteral(" ORDER BY key_value DESC"); break; default: NS_NOTREACHED("Unknown direction type!"); } nsCString firstQuery = NS_LITERAL_CSTRING("SELECT key_value, data, file_ids " "FROM object_data " "WHERE object_store_id = :id") + keyRangeClause + directionClause + NS_LITERAL_CSTRING(" LIMIT 1"); nsCOMPtr stmt = mTransaction->GetCachedStatement(firstQuery); IDB_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); mozStorageStatementScoper scoper(stmt); nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), mObjectStore->Id()); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (mKeyRange) { rv = mKeyRange->BindToStatement(stmt); NS_ENSURE_SUCCESS(rv, rv); } bool hasResult; rv = stmt->ExecuteStep(&hasResult); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (!hasResult) { mKey.Unset(); return NS_OK; } rv = mKey.SetFromStatement(stmt, 0); NS_ENSURE_SUCCESS(rv, rv); rv = IDBObjectStore::GetStructuredCloneReadInfoFromStatement(stmt, 1, 2, mDatabase, mCloneReadInfo); NS_ENSURE_SUCCESS(rv, rv); // Now we need to make the query to get the next match. keyRangeClause.Truncate(); nsAutoCString continueToKeyRangeClause; NS_NAMED_LITERAL_CSTRING(currentKey, "current_key"); NS_NAMED_LITERAL_CSTRING(rangeKey, "range_key"); switch (mDirection) { case IDBCursor::NEXT: case IDBCursor::NEXT_UNIQUE: AppendConditionClause(keyValue, currentKey, false, false, keyRangeClause); AppendConditionClause(keyValue, currentKey, false, true, continueToKeyRangeClause); if (mKeyRange && !mKeyRange->Upper().IsUnset()) { AppendConditionClause(keyValue, rangeKey, true, !mKeyRange->IsUpperOpen(), keyRangeClause); AppendConditionClause(keyValue, rangeKey, true, !mKeyRange->IsUpperOpen(), continueToKeyRangeClause); mRangeKey = mKeyRange->Upper(); } break; case IDBCursor::PREV: case IDBCursor::PREV_UNIQUE: AppendConditionClause(keyValue, currentKey, true, false, keyRangeClause); AppendConditionClause(keyValue, currentKey, true, true, continueToKeyRangeClause); if (mKeyRange && !mKeyRange->Lower().IsUnset()) { AppendConditionClause(keyValue, rangeKey, false, !mKeyRange->IsLowerOpen(), keyRangeClause); AppendConditionClause(keyValue, rangeKey, false, !mKeyRange->IsLowerOpen(), continueToKeyRangeClause); mRangeKey = mKeyRange->Lower(); } break; default: NS_NOTREACHED("Unknown direction type!"); } NS_NAMED_LITERAL_CSTRING(queryStart, "SELECT key_value, data, file_ids " "FROM object_data " "WHERE object_store_id = :id"); mContinueQuery = queryStart + keyRangeClause + directionClause + NS_LITERAL_CSTRING(" LIMIT "); mContinueToQuery = queryStart + continueToKeyRangeClause + directionClause + NS_LITERAL_CSTRING(" LIMIT "); return NS_OK; } nsresult OpenCursorHelper::EnsureCursor() { if (mCursor || mKey.IsUnset()) { return NS_OK; } mSerializedCloneReadInfo = mCloneReadInfo; NS_ASSERTION(mSerializedCloneReadInfo.data && mSerializedCloneReadInfo.dataLength, "Shouldn't be possible!"); nsRefPtr cursor = IDBCursor::Create(mRequest, mTransaction, mObjectStore, mDirection, mRangeKey, mContinueQuery, mContinueToQuery, mKey, Move(mCloneReadInfo)); IDB_ENSURE_TRUE(cursor, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); NS_ASSERTION(!mCloneReadInfo.mCloneBuffer.data(), "Should have swapped!"); mCursor.swap(cursor); return NS_OK; } nsresult OpenCursorHelper::GetSuccessResult(JSContext* aCx, JS::MutableHandle aVal) { nsresult rv = EnsureCursor(); NS_ENSURE_SUCCESS(rv, rv); if (mCursor) { rv = WrapNative(aCx, mCursor, aVal); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); } else { aVal.setUndefined(); } return NS_OK; } void OpenCursorHelper::ReleaseMainThreadObjects() { mKeyRange = nullptr; IDBObjectStore::ClearCloneReadInfo(mCloneReadInfo); mCursor = nullptr; // These don't need to be released on the main thread but they're only valid // as long as mCursor is set. mSerializedCloneReadInfo.data = nullptr; mSerializedCloneReadInfo.dataLength = 0; ObjectStoreHelper::ReleaseMainThreadObjects(); } nsresult OpenCursorHelper::PackArgumentsForParentProcess(ObjectStoreRequestParams& aParams, nsIContentChild* aBlobCreator) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(!IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); NS_ASSERTION(aBlobCreator, "Must have a valid creator!"); PROFILER_MAIN_THREAD_LABEL("OpenCursorHelper", "PackArgumentsForParentProcess [IDBObjectStore.cpp]", js::ProfileEntry::Category::STORAGE); OpenCursorParams params; if (mKeyRange) { KeyRange keyRange; mKeyRange->ToSerializedKeyRange(keyRange); params.optionalKeyRange() = keyRange; } else { params.optionalKeyRange() = mozilla::void_t(); } params.direction() = mDirection; aParams = params; return NS_OK; } AsyncConnectionHelper::ChildProcessSendResult OpenCursorHelper::SendResponseToChildProcess(nsresult aResultCode) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); NS_ASSERTION(!mCursor, "Shouldn't have this yet!"); PROFILER_MAIN_THREAD_LABEL("OpenCursorHelper", "SendResponseToChildProcess [IDBObjectStore.cpp]", js::ProfileEntry::Category::STORAGE); IndexedDBRequestParentBase* actor = mRequest->GetActorParent(); NS_ASSERTION(actor, "How did we get this far without an actor?"); InfallibleTArray blobsParent; if (NS_SUCCEEDED(aResultCode)) { IDBDatabase* database = mObjectStore->Transaction()->Database(); NS_ASSERTION(database, "This should never be null!"); nsIContentParent* contentParent = database->GetContentParent(); NS_ASSERTION(contentParent, "This should never be null!"); FileManager* fileManager = database->Manager(); NS_ASSERTION(fileManager, "This should never be null!"); const nsTArray& files = mCloneReadInfo.mFiles; aResultCode = IDBObjectStore::ConvertBlobsToActors(contentParent, fileManager, files, blobsParent); if (NS_FAILED(aResultCode)) { NS_WARNING("ConvertBlobsToActors failed!"); } } if (NS_SUCCEEDED(aResultCode)) { nsresult rv = EnsureCursor(); if (NS_FAILED(rv)) { NS_WARNING("EnsureCursor failed!"); aResultCode = rv; } } ResponseValue response; if (NS_FAILED(aResultCode)) { response = aResultCode; } else { OpenCursorResponse openCursorResponse; if (!mCursor) { openCursorResponse = mozilla::void_t(); } else { IndexedDBObjectStoreParent* objectStoreActor = mObjectStore->GetActorParent(); NS_ASSERTION(objectStoreActor, "Must have an actor here!"); IndexedDBRequestParentBase* requestActor = mRequest->GetActorParent(); NS_ASSERTION(requestActor, "Must have an actor here!"); NS_ASSERTION(mSerializedCloneReadInfo.data && mSerializedCloneReadInfo.dataLength, "Shouldn't be possible!"); ObjectStoreCursorConstructorParams params; params.requestParent() = requestActor; params.direction() = mDirection; params.key() = mKey; params.optionalCloneInfo() = mSerializedCloneReadInfo; params.blobsParent().SwapElements(blobsParent); if (!objectStoreActor->OpenCursor(mCursor, params, openCursorResponse)) { return Error; } } response = openCursorResponse; } if (!actor->SendResponse(response)) { return Error; } return Success_Sent; } nsresult OpenCursorHelper::UnpackResponseFromParentProcess( const ResponseValue& aResponseValue) { NS_ASSERTION(aResponseValue.type() == ResponseValue::TOpenCursorResponse, "Bad response type!"); NS_ASSERTION(aResponseValue.get_OpenCursorResponse().type() == OpenCursorResponse::Tvoid_t || aResponseValue.get_OpenCursorResponse().type() == OpenCursorResponse::TPIndexedDBCursorChild, "Bad response union type!"); NS_ASSERTION(!mCursor, "Shouldn't have this yet!"); const OpenCursorResponse& response = aResponseValue.get_OpenCursorResponse(); switch (response.type()) { case OpenCursorResponse::Tvoid_t: break; case OpenCursorResponse::TPIndexedDBCursorChild: { IndexedDBCursorChild* actor = static_cast( response.get_PIndexedDBCursorChild()); mCursor = actor->ForgetStrongCursor(); NS_ASSERTION(mCursor, "This should never be null!"); } break; default: MOZ_CRASH(); } return NS_OK; } nsresult OpenKeyCursorHelper::DoDatabaseWork(mozIStorageConnection* /* aConnection */) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(IndexedDatabaseManager::IsMainProcess()); PROFILER_LABEL("OpenKeyCursorHelper", "DoDatabaseWork [IDBObjectStore.cpp]", js::ProfileEntry::Category::STORAGE); NS_NAMED_LITERAL_CSTRING(keyValue, "key_value"); NS_NAMED_LITERAL_CSTRING(id, "id"); NS_NAMED_LITERAL_CSTRING(openLimit, " LIMIT "); nsAutoCString queryStart = NS_LITERAL_CSTRING("SELECT ") + keyValue + NS_LITERAL_CSTRING(" FROM object_data WHERE " "object_store_id = :") + id; nsAutoCString keyRangeClause; if (mKeyRange) { mKeyRange->GetBindingClause(keyValue, keyRangeClause); } nsAutoCString directionClause = NS_LITERAL_CSTRING(" ORDER BY ") + keyValue; switch (mDirection) { case IDBCursor::NEXT: case IDBCursor::NEXT_UNIQUE: directionClause.AppendLiteral(" ASC"); break; case IDBCursor::PREV: case IDBCursor::PREV_UNIQUE: directionClause.AppendLiteral(" DESC"); break; default: MOZ_CRASH("Unknown direction type!"); } nsCString firstQuery = queryStart + keyRangeClause + directionClause + openLimit + NS_LITERAL_CSTRING("1"); nsCOMPtr stmt = mTransaction->GetCachedStatement(firstQuery); IDB_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); mozStorageStatementScoper scoper(stmt); nsresult rv = stmt->BindInt64ByName(id, mObjectStore->Id()); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (mKeyRange) { rv = mKeyRange->BindToStatement(stmt); NS_ENSURE_SUCCESS(rv, rv); } bool hasResult; rv = stmt->ExecuteStep(&hasResult); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (!hasResult) { mKey.Unset(); return NS_OK; } rv = mKey.SetFromStatement(stmt, 0); NS_ENSURE_SUCCESS(rv, rv); // Now we need to make the query to get the next match. keyRangeClause.Truncate(); nsAutoCString continueToKeyRangeClause; NS_NAMED_LITERAL_CSTRING(currentKey, "current_key"); NS_NAMED_LITERAL_CSTRING(rangeKey, "range_key"); switch (mDirection) { case IDBCursor::NEXT: case IDBCursor::NEXT_UNIQUE: AppendConditionClause(keyValue, currentKey, false, false, keyRangeClause); AppendConditionClause(keyValue, currentKey, false, true, continueToKeyRangeClause); if (mKeyRange && !mKeyRange->Upper().IsUnset()) { AppendConditionClause(keyValue, rangeKey, true, !mKeyRange->IsUpperOpen(), keyRangeClause); AppendConditionClause(keyValue, rangeKey, true, !mKeyRange->IsUpperOpen(), continueToKeyRangeClause); mRangeKey = mKeyRange->Upper(); } break; case IDBCursor::PREV: case IDBCursor::PREV_UNIQUE: AppendConditionClause(keyValue, currentKey, true, false, keyRangeClause); AppendConditionClause(keyValue, currentKey, true, true, continueToKeyRangeClause); if (mKeyRange && !mKeyRange->Lower().IsUnset()) { AppendConditionClause(keyValue, rangeKey, false, !mKeyRange->IsLowerOpen(), keyRangeClause); AppendConditionClause(keyValue, rangeKey, false, !mKeyRange->IsLowerOpen(), continueToKeyRangeClause); mRangeKey = mKeyRange->Lower(); } break; default: MOZ_CRASH("Unknown direction type!"); } mContinueQuery = queryStart + keyRangeClause + directionClause + openLimit; mContinueToQuery = queryStart + continueToKeyRangeClause + directionClause + openLimit; return NS_OK; } nsresult OpenKeyCursorHelper::EnsureCursor() { MOZ_ASSERT(NS_IsMainThread()); PROFILER_MAIN_THREAD_LABEL("OpenKeyCursorHelper", "EnsureCursor [IDBObjectStore.cpp]", js::ProfileEntry::Category::STORAGE); if (mCursor || mKey.IsUnset()) { return NS_OK; } mCursor = IDBCursor::Create(mRequest, mTransaction, mObjectStore, mDirection, mRangeKey, mContinueQuery, mContinueToQuery, mKey); IDB_ENSURE_TRUE(mCursor, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return NS_OK; } nsresult OpenKeyCursorHelper::GetSuccessResult(JSContext* aCx, JS::MutableHandle aVal) { MOZ_ASSERT(NS_IsMainThread()); PROFILER_MAIN_THREAD_LABEL("OpenKeyCursorHelper", "GetSuccessResult [IDBObjectStore.cpp]", js::ProfileEntry::Category::STORAGE); nsresult rv = EnsureCursor(); NS_ENSURE_SUCCESS(rv, rv); if (mCursor) { rv = WrapNative(aCx, mCursor, aVal); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); } else { aVal.setUndefined(); } return NS_OK; } void OpenKeyCursorHelper::ReleaseMainThreadObjects() { MOZ_ASSERT(NS_IsMainThread()); mKeyRange = nullptr; mCursor = nullptr; ObjectStoreHelper::ReleaseMainThreadObjects(); } nsresult OpenKeyCursorHelper::PackArgumentsForParentProcess(ObjectStoreRequestParams& aParams, nsIContentChild* aBlobCreator) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!IndexedDatabaseManager::IsMainProcess()); NS_ASSERTION(aBlobCreator, "Must have a valid creator!"); PROFILER_MAIN_THREAD_LABEL("OpenKeyCursorHelper", "PackArgumentsForParentProcess [IDBObjectStore.cpp]", js::ProfileEntry::Category::STORAGE); OpenKeyCursorParams params; if (mKeyRange) { KeyRange keyRange; mKeyRange->ToSerializedKeyRange(keyRange); params.optionalKeyRange() = keyRange; } else { params.optionalKeyRange() = mozilla::void_t(); } params.direction() = mDirection; aParams = params; return NS_OK; } AsyncConnectionHelper::ChildProcessSendResult OpenKeyCursorHelper::SendResponseToChildProcess(nsresult aResultCode) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(IndexedDatabaseManager::IsMainProcess()); MOZ_ASSERT(!mCursor); PROFILER_MAIN_THREAD_LABEL("OpenKeyCursorHelper", "SendResponseToChildProcess [IDBObjectStore.cpp]", js::ProfileEntry::Category::STORAGE); IndexedDBRequestParentBase* actor = mRequest->GetActorParent(); MOZ_ASSERT(actor); if (NS_SUCCEEDED(aResultCode)) { nsresult rv = EnsureCursor(); if (NS_FAILED(rv)) { NS_WARNING("EnsureCursor failed!"); aResultCode = rv; } } ResponseValue response; if (NS_FAILED(aResultCode)) { response = aResultCode; } else { OpenCursorResponse openCursorResponse; if (!mCursor) { openCursorResponse = mozilla::void_t(); } else { IndexedDBObjectStoreParent* objectStoreActor = mObjectStore->GetActorParent(); MOZ_ASSERT(objectStoreActor); IndexedDBRequestParentBase* requestActor = mRequest->GetActorParent(); MOZ_ASSERT(requestActor); ObjectStoreCursorConstructorParams params; params.requestParent() = requestActor; params.direction() = mDirection; params.key() = mKey; params.optionalCloneInfo() = mozilla::void_t(); if (!objectStoreActor->OpenCursor(mCursor, params, openCursorResponse)) { return Error; } } response = openCursorResponse; } if (!actor->SendResponse(response)) { return Error; } return Success_Sent; } nsresult OpenKeyCursorHelper::UnpackResponseFromParentProcess( const ResponseValue& aResponseValue) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!IndexedDatabaseManager::IsMainProcess()); MOZ_ASSERT(aResponseValue.type() == ResponseValue::TOpenCursorResponse); MOZ_ASSERT(aResponseValue.get_OpenCursorResponse().type() == OpenCursorResponse::Tvoid_t || aResponseValue.get_OpenCursorResponse().type() == OpenCursorResponse::TPIndexedDBCursorChild); MOZ_ASSERT(!mCursor); PROFILER_MAIN_THREAD_LABEL("OpenKeyCursorHelper", "UnpackResponseFromParentProcess [IDBObjectStore.cpp]", js::ProfileEntry::Category::STORAGE); const OpenCursorResponse& response = aResponseValue.get_OpenCursorResponse(); switch (response.type()) { case OpenCursorResponse::Tvoid_t: break; case OpenCursorResponse::TPIndexedDBCursorChild: { IndexedDBCursorChild* actor = static_cast( response.get_PIndexedDBCursorChild()); mCursor = actor->ForgetStrongCursor(); NS_ASSERTION(mCursor, "This should never be null!"); } break; default: MOZ_CRASH("Unknown response union type!"); } return NS_OK; } nsresult CreateIndexHelper::DoDatabaseWork(mozIStorageConnection* aConnection) { NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); PROFILER_LABEL("CreateIndexHelper", "DoDatabaseWork", js::ProfileEntry::Category::STORAGE); if (IndexedDatabaseManager::InLowDiskSpaceMode()) { NS_WARNING("Refusing to create index because disk space is low!"); return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR; } // Insert the data into the database. nsCOMPtr stmt = mTransaction->GetCachedStatement( "INSERT INTO object_store_index (id, name, key_path, unique_index, " "multientry, object_store_id) " "VALUES (:id, :name, :key_path, :unique, :multientry, :osid)" ); IDB_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); mozStorageStatementScoper scoper(stmt); nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), mIndex->Id()); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); rv = stmt->BindStringByName(NS_LITERAL_CSTRING("name"), mIndex->Name()); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); nsAutoString keyPathSerialization; mIndex->GetKeyPath().SerializeToString(keyPathSerialization); rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key_path"), keyPathSerialization); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("unique"), mIndex->IsUnique() ? 1 : 0); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("multientry"), mIndex->IsMultiEntry() ? 1 : 0); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"), mIndex->ObjectStore()->Id()); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (NS_FAILED(stmt->Execute())) { return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR; } #ifdef DEBUG { int64_t id; aConnection->GetLastInsertRowID(&id); NS_ASSERTION(mIndex->Id() == id, "Bad index id!"); } #endif // Now we need to populate the index with data from the object store. rv = InsertDataFromObjectStore(aConnection); if (NS_FAILED(rv)) { return rv; } return NS_OK; } void CreateIndexHelper::ReleaseMainThreadObjects() { mIndex = nullptr; NoRequestObjectStoreHelper::ReleaseMainThreadObjects(); } nsresult CreateIndexHelper::InsertDataFromObjectStore(mozIStorageConnection* aConnection) { nsCOMPtr stmt = mTransaction->GetCachedStatement( NS_LITERAL_CSTRING("SELECT id, data, file_ids, key_value FROM " "object_data WHERE object_store_id = :osid")); IDB_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); mozStorageStatementScoper scoper(stmt); nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"), mIndex->ObjectStore()->Id()); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); IDB_ENSURE_TRUE(sTLSIndex != BAD_TLS_INDEX, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); bool hasResult; rv = stmt->ExecuteStep(&hasResult); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (!hasResult) { // Bail early if we have no data to avoid creating the below runtime return NS_OK; } ThreadLocalJSRuntime* tlsEntry = reinterpret_cast(PR_GetThreadPrivate(sTLSIndex)); if (!tlsEntry) { tlsEntry = ThreadLocalJSRuntime::Create(); IDB_ENSURE_TRUE(tlsEntry, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); PR_SetThreadPrivate(sTLSIndex, tlsEntry); } JSContext* cx = tlsEntry->Context(); JSAutoRequest ar(cx); JSAutoCompartment ac(cx, tlsEntry->Global()); do { StructuredCloneReadInfo cloneReadInfo; rv = IDBObjectStore::GetStructuredCloneReadInfoFromStatement(stmt, 1, 2, mDatabase, cloneReadInfo); NS_ENSURE_SUCCESS(rv, rv); JSAutoStructuredCloneBuffer& buffer = cloneReadInfo.mCloneBuffer; JSStructuredCloneCallbacks callbacks = { IDBObjectStore::StructuredCloneReadCallback, nullptr, nullptr, nullptr, nullptr, nullptr }; JS::Rooted clone(cx); if (!buffer.read(cx, &clone, &callbacks, &cloneReadInfo)) { NS_WARNING("Failed to deserialize structured clone data!"); return NS_ERROR_DOM_DATA_CLONE_ERR; } nsTArray updateInfo; rv = IDBObjectStore::AppendIndexUpdateInfo(mIndex->Id(), mIndex->GetKeyPath(), mIndex->IsUnique(), mIndex->IsMultiEntry(), tlsEntry->Context(), clone, updateInfo); NS_ENSURE_SUCCESS(rv, rv); int64_t objectDataID = stmt->AsInt64(0); Key key; rv = key.SetFromStatement(stmt, 3); NS_ENSURE_SUCCESS(rv, rv); rv = IDBObjectStore::UpdateIndexes(mTransaction, mIndex->Id(), key, false, objectDataID, updateInfo); NS_ENSURE_SUCCESS(rv, rv); } while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&hasResult)) && hasResult); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return NS_OK; } void CreateIndexHelper::DestroyTLSEntry(void* aPtr) { delete reinterpret_cast(aPtr); } nsresult DeleteIndexHelper::DoDatabaseWork(mozIStorageConnection* aConnection) { NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); PROFILER_LABEL("DeleteIndexHelper", "DoDatabaseWork", js::ProfileEntry::Category::STORAGE); nsCOMPtr stmt = mTransaction->GetCachedStatement( "DELETE FROM object_store_index " "WHERE name = :name " ); IDB_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); mozStorageStatementScoper scoper(stmt); nsresult rv = stmt->BindStringByName(NS_LITERAL_CSTRING("name"), mName); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (NS_FAILED(stmt->Execute())) { return NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR; } return NS_OK; } nsresult GetAllHelper::DoDatabaseWork(mozIStorageConnection* aConnection) { NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); PROFILER_LABEL("GetAllHelper", "DoDatabaseWork [IDBObjectStore.cpp]", js::ProfileEntry::Category::STORAGE); NS_NAMED_LITERAL_CSTRING(lowerKeyName, "lower_key"); NS_NAMED_LITERAL_CSTRING(upperKeyName, "upper_key"); nsAutoCString keyRangeClause; if (mKeyRange) { if (!mKeyRange->Lower().IsUnset()) { keyRangeClause = NS_LITERAL_CSTRING(" AND key_value"); if (mKeyRange->IsLowerOpen()) { keyRangeClause.AppendLiteral(" > :"); } else { keyRangeClause.AppendLiteral(" >= :"); } keyRangeClause.Append(lowerKeyName); } if (!mKeyRange->Upper().IsUnset()) { keyRangeClause += NS_LITERAL_CSTRING(" AND key_value"); if (mKeyRange->IsUpperOpen()) { keyRangeClause.AppendLiteral(" < :"); } else { keyRangeClause.AppendLiteral(" <= :"); } keyRangeClause.Append(upperKeyName); } } nsAutoCString limitClause; if (mLimit != UINT32_MAX) { limitClause.AssignLiteral(" LIMIT "); limitClause.AppendInt(mLimit); } nsCString query = NS_LITERAL_CSTRING("SELECT data, file_ids FROM object_data " "WHERE object_store_id = :osid") + keyRangeClause + NS_LITERAL_CSTRING(" ORDER BY key_value ASC") + limitClause; mCloneReadInfos.SetCapacity(50); nsCOMPtr stmt = mTransaction->GetCachedStatement(query); IDB_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); mozStorageStatementScoper scoper(stmt); nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"), mObjectStore->Id()); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (mKeyRange) { if (!mKeyRange->Lower().IsUnset()) { rv = mKeyRange->Lower().BindToStatement(stmt, lowerKeyName); NS_ENSURE_SUCCESS(rv, rv); } if (!mKeyRange->Upper().IsUnset()) { rv = mKeyRange->Upper().BindToStatement(stmt, upperKeyName); NS_ENSURE_SUCCESS(rv, rv); } } bool hasResult; while (NS_SUCCEEDED((rv = stmt->ExecuteStep(&hasResult))) && hasResult) { if (mCloneReadInfos.Capacity() == mCloneReadInfos.Length()) { mCloneReadInfos.SetCapacity(mCloneReadInfos.Capacity() * 2); } StructuredCloneReadInfo* readInfo = mCloneReadInfos.AppendElement(); NS_ASSERTION(readInfo, "Shouldn't fail since SetCapacity succeeded!"); rv = IDBObjectStore::GetStructuredCloneReadInfoFromStatement(stmt, 0, 1, mDatabase, *readInfo); NS_ENSURE_SUCCESS(rv, rv); } IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return NS_OK; } nsresult GetAllHelper::GetSuccessResult(JSContext* aCx, JS::MutableHandle aVal) { NS_ASSERTION(mCloneReadInfos.Length() <= mLimit, "Too many results!"); nsresult rv = ConvertToArrayAndCleanup(aCx, mCloneReadInfos, aVal); NS_ASSERTION(mCloneReadInfos.IsEmpty(), "Should have cleared in ConvertToArrayAndCleanup"); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } void GetAllHelper::ReleaseMainThreadObjects() { mKeyRange = nullptr; for (uint32_t index = 0; index < mCloneReadInfos.Length(); index++) { IDBObjectStore::ClearCloneReadInfo(mCloneReadInfos[index]); } ObjectStoreHelper::ReleaseMainThreadObjects(); } nsresult GetAllHelper::PackArgumentsForParentProcess(ObjectStoreRequestParams& aParams, nsIContentChild* aBlobCreator) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(!IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); NS_ASSERTION(aBlobCreator, "Must have a valid creator!"); PROFILER_MAIN_THREAD_LABEL("GetAllHelper", "PackArgumentsForParentProcess [IDBObjectStore.cpp]", js::ProfileEntry::Category::STORAGE); GetAllParams params; if (mKeyRange) { KeyRange keyRange; mKeyRange->ToSerializedKeyRange(keyRange); params.optionalKeyRange() = keyRange; } else { params.optionalKeyRange() = mozilla::void_t(); } params.limit() = mLimit; aParams = params; return NS_OK; } AsyncConnectionHelper::ChildProcessSendResult GetAllHelper::SendResponseToChildProcess(nsresult aResultCode) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); PROFILER_MAIN_THREAD_LABEL("GetAllHelper", "SendResponseToChildProcess [IDBObjectStore.cpp]", js::ProfileEntry::Category::STORAGE); IndexedDBRequestParentBase* actor = mRequest->GetActorParent(); NS_ASSERTION(actor, "How did we get this far without an actor?"); GetAllResponse getAllResponse; if (NS_SUCCEEDED(aResultCode) && !mCloneReadInfos.IsEmpty()) { IDBDatabase* database = mObjectStore->Transaction()->Database(); NS_ASSERTION(database, "This should never be null!"); nsIContentParent* contentParent = database->GetContentParent(); NS_ASSERTION(contentParent, "This should never be null!"); FileManager* fileManager = database->Manager(); NS_ASSERTION(fileManager, "This should never be null!"); uint32_t length = mCloneReadInfos.Length(); InfallibleTArray& infos = getAllResponse.cloneInfos(); infos.SetCapacity(length); InfallibleTArray& blobArrays = getAllResponse.blobs(); blobArrays.SetCapacity(length); for (uint32_t index = 0; NS_SUCCEEDED(aResultCode) && index < length; index++) { // Append the structured clone data. const StructuredCloneReadInfo& clone = mCloneReadInfos[index]; SerializedStructuredCloneReadInfo* info = infos.AppendElement(); *info = clone; // Now take care of the files. const nsTArray& files = clone.mFiles; BlobArray* blobArray = blobArrays.AppendElement(); InfallibleTArray& blobs = blobArray->blobsParent(); aResultCode = IDBObjectStore::ConvertBlobsToActors(contentParent, fileManager, files, blobs); if (NS_FAILED(aResultCode)) { NS_WARNING("ConvertBlobsToActors failed!"); break; } } } ResponseValue response; if (NS_FAILED(aResultCode)) { response = aResultCode; } else { response = getAllResponse; } if (!actor->SendResponse(response)) { return Error; } return Success_Sent; } nsresult GetAllHelper::UnpackResponseFromParentProcess( const ResponseValue& aResponseValue) { NS_ASSERTION(aResponseValue.type() == ResponseValue::TGetAllResponse, "Bad response type!"); const GetAllResponse& getAllResponse = aResponseValue.get_GetAllResponse(); const InfallibleTArray& cloneInfos = getAllResponse.cloneInfos(); const InfallibleTArray& blobArrays = getAllResponse.blobs(); mCloneReadInfos.SetCapacity(cloneInfos.Length()); for (uint32_t index = 0; index < cloneInfos.Length(); index++) { const SerializedStructuredCloneReadInfo srcInfo = cloneInfos[index]; const InfallibleTArray& blobs = blobArrays[index].blobsChild(); StructuredCloneReadInfo* destInfo = mCloneReadInfos.AppendElement(); if (!destInfo->SetFromSerialized(srcInfo)) { IDB_WARNING("Failed to copy clone buffer!"); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } IDBObjectStore::ConvertActorsToBlobs(blobs, destInfo->mFiles); } return NS_OK; } nsresult GetAllKeysHelper::DoDatabaseWork(mozIStorageConnection* /* aConnection */) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(IndexedDatabaseManager::IsMainProcess()); PROFILER_LABEL("GetAllKeysHelper", "DoDatabaseWork [IDObjectStore.cpp]", js::ProfileEntry::Category::STORAGE); NS_NAMED_LITERAL_CSTRING(keyValue, "key_value"); nsAutoCString keyRangeClause; if (mKeyRange) { mKeyRange->GetBindingClause(keyValue, keyRangeClause); } nsAutoCString limitClause; if (mLimit != UINT32_MAX) { limitClause = NS_LITERAL_CSTRING(" LIMIT "); limitClause.AppendInt(mLimit); } NS_NAMED_LITERAL_CSTRING(osid, "osid"); nsCString query = NS_LITERAL_CSTRING("SELECT ") + keyValue + NS_LITERAL_CSTRING(" FROM object_data WHERE " "object_store_id = :") + osid + keyRangeClause + NS_LITERAL_CSTRING(" ORDER BY key_value ASC") + limitClause; nsCOMPtr stmt = mTransaction->GetCachedStatement(query); IDB_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); mozStorageStatementScoper scoper(stmt); nsresult rv = stmt->BindInt64ByName(osid, mObjectStore->Id()); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (mKeyRange) { rv = mKeyRange->BindToStatement(stmt); NS_ENSURE_SUCCESS(rv, rv); } mKeys.SetCapacity(std::min(50, mLimit)); bool hasResult; while(NS_SUCCEEDED((rv = stmt->ExecuteStep(&hasResult))) && hasResult) { if (mKeys.Capacity() == mKeys.Length()) { mKeys.SetCapacity(mKeys.Capacity() * 2); } Key* key = mKeys.AppendElement(); NS_ASSERTION(key, "This shouldn't fail!"); rv = key->SetFromStatement(stmt, 0); NS_ENSURE_SUCCESS(rv, rv); } IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return NS_OK; } nsresult GetAllKeysHelper::GetSuccessResult(JSContext* aCx, JS::MutableHandle aVal) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mKeys.Length() <= mLimit); PROFILER_MAIN_THREAD_LABEL("GetAllKeysHelper", "GetSuccessResult [IDBObjectStore.cpp]", js::ProfileEntry::Category::STORAGE); nsTArray keys; mKeys.SwapElements(keys); JS::Rooted array(aCx, JS_NewArrayObject(aCx, 0)); if (!array) { IDB_WARNING("Failed to make array!"); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } if (!keys.IsEmpty()) { if (!JS_SetArrayLength(aCx, array, keys.Length())) { IDB_WARNING("Failed to set array length!"); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } for (uint32_t index = 0, count = keys.Length(); index < count; index++) { const Key& key = keys[index]; MOZ_ASSERT(!key.IsUnset()); JS::Rooted value(aCx); nsresult rv = key.ToJSVal(aCx, &value); if (NS_FAILED(rv)) { NS_WARNING("Failed to get jsval for key!"); return rv; } if (!JS_SetElement(aCx, array, index, value)) { IDB_WARNING("Failed to set array element!"); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } } } aVal.setObject(*array); return NS_OK; } void GetAllKeysHelper::ReleaseMainThreadObjects() { MOZ_ASSERT(NS_IsMainThread()); mKeyRange = nullptr; ObjectStoreHelper::ReleaseMainThreadObjects(); } nsresult GetAllKeysHelper::PackArgumentsForParentProcess(ObjectStoreRequestParams& aParams, nsIContentChild* aBlobCreator) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!IndexedDatabaseManager::IsMainProcess()); NS_ASSERTION(aBlobCreator, "Must have a valid creator!"); PROFILER_MAIN_THREAD_LABEL("GetAllKeysHelper", "PackArgumentsForParentProcess [IDBObjectStore.cpp]", js::ProfileEntry::Category::STORAGE); GetAllKeysParams params; if (mKeyRange) { KeyRange keyRange; mKeyRange->ToSerializedKeyRange(keyRange); params.optionalKeyRange() = keyRange; } else { params.optionalKeyRange() = mozilla::void_t(); } params.limit() = mLimit; aParams = params; return NS_OK; } AsyncConnectionHelper::ChildProcessSendResult GetAllKeysHelper::SendResponseToChildProcess(nsresult aResultCode) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(IndexedDatabaseManager::IsMainProcess()); PROFILER_MAIN_THREAD_LABEL("GetAllKeysHelper", "SendResponseToChildProcess [IDBObjectStore.cpp]", js::ProfileEntry::Category::STORAGE); IndexedDBRequestParentBase* actor = mRequest->GetActorParent(); MOZ_ASSERT(actor); ResponseValue response; if (NS_FAILED(aResultCode)) { response = aResultCode; } else { GetAllKeysResponse getAllKeysResponse; getAllKeysResponse.keys().AppendElements(mKeys); response = getAllKeysResponse; } if (!actor->SendResponse(response)) { return Error; } return Success_Sent; } nsresult GetAllKeysHelper::UnpackResponseFromParentProcess( const ResponseValue& aResponseValue) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!IndexedDatabaseManager::IsMainProcess()); MOZ_ASSERT(aResponseValue.type() == ResponseValue::TGetAllKeysResponse); mKeys.AppendElements(aResponseValue.get_GetAllKeysResponse().keys()); return NS_OK; } nsresult CountHelper::DoDatabaseWork(mozIStorageConnection* aConnection) { NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); PROFILER_LABEL("CountHelper", "DoDatabaseWork [IDBObjectStore.cpp]", js::ProfileEntry::Category::STORAGE); NS_NAMED_LITERAL_CSTRING(lowerKeyName, "lower_key"); NS_NAMED_LITERAL_CSTRING(upperKeyName, "upper_key"); nsAutoCString keyRangeClause; if (mKeyRange) { if (!mKeyRange->Lower().IsUnset()) { keyRangeClause = NS_LITERAL_CSTRING(" AND key_value"); if (mKeyRange->IsLowerOpen()) { keyRangeClause.AppendLiteral(" > :"); } else { keyRangeClause.AppendLiteral(" >= :"); } keyRangeClause.Append(lowerKeyName); } if (!mKeyRange->Upper().IsUnset()) { keyRangeClause += NS_LITERAL_CSTRING(" AND key_value"); if (mKeyRange->IsUpperOpen()) { keyRangeClause.AppendLiteral(" < :"); } else { keyRangeClause.AppendLiteral(" <= :"); } keyRangeClause.Append(upperKeyName); } } nsCString query = NS_LITERAL_CSTRING("SELECT count(*) FROM object_data " "WHERE object_store_id = :osid") + keyRangeClause; nsCOMPtr stmt = mTransaction->GetCachedStatement(query); IDB_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); mozStorageStatementScoper scoper(stmt); nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"), mObjectStore->Id()); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (mKeyRange) { if (!mKeyRange->Lower().IsUnset()) { rv = mKeyRange->Lower().BindToStatement(stmt, lowerKeyName); NS_ENSURE_SUCCESS(rv, rv); } if (!mKeyRange->Upper().IsUnset()) { rv = mKeyRange->Upper().BindToStatement(stmt, upperKeyName); NS_ENSURE_SUCCESS(rv, rv); } } bool hasResult; rv = stmt->ExecuteStep(&hasResult); IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); IDB_ENSURE_TRUE(hasResult, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); mCount = stmt->AsInt64(0); return NS_OK; } nsresult CountHelper::GetSuccessResult(JSContext* aCx, JS::MutableHandle aVal) { aVal.setNumber(static_cast(mCount)); return NS_OK; } void CountHelper::ReleaseMainThreadObjects() { mKeyRange = nullptr; ObjectStoreHelper::ReleaseMainThreadObjects(); } nsresult CountHelper::PackArgumentsForParentProcess(ObjectStoreRequestParams& aParams, nsIContentChild* aBlobCreator) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(!IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); NS_ASSERTION(aBlobCreator, "Must have a valid creator!"); PROFILER_MAIN_THREAD_LABEL("CountHelper", "PackArgumentsForParentProcess [IDBObjectStore.cpp]", js::ProfileEntry::Category::STORAGE); CountParams params; if (mKeyRange) { KeyRange keyRange; mKeyRange->ToSerializedKeyRange(keyRange); params.optionalKeyRange() = keyRange; } else { params.optionalKeyRange() = mozilla::void_t(); } aParams = params; return NS_OK; } AsyncConnectionHelper::ChildProcessSendResult CountHelper::SendResponseToChildProcess(nsresult aResultCode) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); PROFILER_MAIN_THREAD_LABEL("CountHelper", "SendResponseToChildProcess [IDBObjectStore.cpp]", js::ProfileEntry::Category::STORAGE); IndexedDBRequestParentBase* actor = mRequest->GetActorParent(); NS_ASSERTION(actor, "How did we get this far without an actor?"); ResponseValue response; if (NS_FAILED(aResultCode)) { response = aResultCode; } else { CountResponse countResponse = mCount; response = countResponse; } if (!actor->SendResponse(response)) { return Error; } return Success_Sent; } nsresult CountHelper::UnpackResponseFromParentProcess( const ResponseValue& aResponseValue) { NS_ASSERTION(aResponseValue.type() == ResponseValue::TCountResponse, "Bad response type!"); mCount = aResponseValue.get_CountResponse().count(); return NS_OK; }