/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ /* 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 "DataStoreDB.h" #include "DataStoreCallbacks.h" #include "mozilla/dom/IDBDatabaseBinding.h" #include "mozilla/dom/IDBFactoryBinding.h" #include "mozilla/dom/indexedDB/IDBDatabase.h" #include "mozilla/dom/indexedDB/IDBEvents.h" #include "mozilla/dom/indexedDB/IDBFactory.h" #include "mozilla/dom/indexedDB/IDBIndex.h" #include "mozilla/dom/indexedDB/IDBObjectStore.h" #include "mozilla/dom/indexedDB/IDBRequest.h" #include "nsIDOMEvent.h" #define DATASTOREDB_VERSION 1 #define DATASTOREDB_NAME "DataStoreDB" #define DATASTOREDB_REVISION_INDEX "revisionIndex" using namespace mozilla::dom::indexedDB; namespace mozilla { namespace dom { class VersionChangeListener MOZ_FINAL : public nsIDOMEventListener { public: NS_DECL_ISUPPORTS VersionChangeListener(IDBDatabase* aDatabase) : mDatabase(aDatabase) {} // nsIDOMEventListener NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) { nsString type; nsresult rv = aEvent->GetType(type); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!type.EqualsASCII("versionchange")) { MOZ_ASSERT_UNREACHABLE("Expected a versionchange event"); return NS_ERROR_FAILURE; } rv = mDatabase->RemoveEventListener(NS_LITERAL_STRING("versionchange"), this, false); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } #ifdef DEBUG nsCOMPtr event = do_QueryInterface(aEvent); MOZ_ASSERT(event); Nullable version = event->GetNewVersion(); MOZ_ASSERT(version.IsNull()); #endif return mDatabase->Close(); } private: IDBDatabase* mDatabase; ~VersionChangeListener() {} }; NS_IMPL_ISUPPORTS(VersionChangeListener, nsIDOMEventListener) NS_IMPL_ISUPPORTS(DataStoreDB, nsIDOMEventListener) DataStoreDB::DataStoreDB(const nsAString& aManifestURL, const nsAString& aName) : mState(Inactive) { mDatabaseName.Assign(aName); mDatabaseName.Append('|'); mDatabaseName.Append(aManifestURL); } DataStoreDB::~DataStoreDB() { } nsresult DataStoreDB::CreateFactoryIfNeeded() { if (!mFactory) { nsresult rv = IDBFactory::Create(nullptr, getter_AddRefs(mFactory)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } return NS_OK; } nsresult DataStoreDB::Open(IDBTransactionMode aMode, const Sequence& aDbs, DataStoreDBCallback* aCallback) { MOZ_ASSERT(mState == Inactive); nsresult rv = CreateFactoryIfNeeded(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } ErrorResult error; mRequest = mFactory->Open(mDatabaseName, DATASTOREDB_VERSION, error); if (NS_WARN_IF(error.Failed())) { return error.ErrorCode(); } rv = AddEventListeners(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } mState = Active; mTransactionMode = aMode; mObjectStores = aDbs; mCallback = aCallback; return NS_OK; } NS_IMETHODIMP DataStoreDB::HandleEvent(nsIDOMEvent* aEvent) { nsString type; nsresult rv = aEvent->GetType(type); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (type.EqualsASCII("success")) { RemoveEventListeners(); mState = Inactive; rv = DatabaseOpened(); if (NS_WARN_IF(NS_FAILED(rv))) { mCallback->Run(this, false); } else { mCallback->Run(this, true); } mRequest = nullptr; return NS_OK; } if (type.EqualsASCII("upgradeneeded")) { return UpgradeSchema(); } if (type.EqualsASCII("error") || type.EqualsASCII("blocked")) { RemoveEventListeners(); mState = Inactive; mCallback->Run(this, false); mRequest = nullptr; return NS_OK; } MOZ_CRASH("This should not happen"); } nsresult DataStoreDB::UpgradeSchema() { MOZ_ASSERT(NS_IsMainThread()); AutoSafeJSContext cx; ErrorResult error; JS::Rooted result(cx); mRequest->GetResult(&result, error); if (NS_WARN_IF(error.Failed())) { return error.ErrorCode(); } MOZ_ASSERT(result.isObject()); IDBDatabase* database = nullptr; nsresult rv = UNWRAP_OBJECT(IDBDatabase, &result.toObject(), database); if (NS_FAILED(rv)) { NS_WARNING("Didn't get the object we expected!"); return rv; } { RootedDictionary params(cx); params.Init(NS_LITERAL_STRING("{ \"autoIncrement\": true }")); nsRefPtr store = database->CreateObjectStore(cx, NS_LITERAL_STRING(DATASTOREDB_NAME), params, error); if (NS_WARN_IF(error.Failed())) { return error.ErrorCode(); } } nsRefPtr store; { RootedDictionary params(cx); params.Init(NS_LITERAL_STRING("{ \"autoIncrement\": true, \"keyPath\": \"internalRevisionId\" }")); store = database->CreateObjectStore(cx, NS_LITERAL_STRING(DATASTOREDB_REVISION), params, error); if (NS_WARN_IF(error.Failed())) { return error.ErrorCode(); } } { RootedDictionary params(cx); params.Init(NS_LITERAL_STRING("{ \"unique\": true }")); nsRefPtr index = store->CreateIndex(cx, NS_LITERAL_STRING(DATASTOREDB_REVISION_INDEX), NS_LITERAL_STRING("revisionId"), params, error); if (NS_WARN_IF(error.Failed())) { return error.ErrorCode(); } } return NS_OK; } nsresult DataStoreDB::DatabaseOpened() { MOZ_ASSERT(NS_IsMainThread()); AutoSafeJSContext cx; ErrorResult error; JS::Rooted result(cx); mRequest->GetResult(&result, error); if (NS_WARN_IF(error.Failed())) { return error.ErrorCode(); } MOZ_ASSERT(result.isObject()); nsresult rv = UNWRAP_OBJECT(IDBDatabase, &result.toObject(), mDatabase); if (NS_FAILED(rv)) { NS_WARNING("Didn't get the object we expected!"); return rv; } nsRefPtr listener = new VersionChangeListener(mDatabase); rv = mDatabase->EventTarget::AddEventListener( NS_LITERAL_STRING("versionchange"), listener, false); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsRefPtr txn = mDatabase->Transaction(mObjectStores, mTransactionMode, error); if (NS_WARN_IF(error.Failed())) { return error.ErrorCode(); } mTransaction = txn.forget(); return NS_OK; } nsresult DataStoreDB::Delete() { MOZ_ASSERT(mState == Inactive); nsresult rv = CreateFactoryIfNeeded(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } mTransaction = nullptr; if (mDatabase) { rv = mDatabase->Close(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } mDatabase = nullptr; } ErrorResult error; nsRefPtr request = mFactory->DeleteDatabase(mDatabaseName, IDBOpenDBOptions(), error); if (NS_WARN_IF(error.Failed())) { return error.ErrorCode(); } return NS_OK; } indexedDB::IDBTransaction* DataStoreDB::Transaction() const { MOZ_ASSERT(mTransaction); MOZ_ASSERT(mTransaction->IsOpen()); return mTransaction; } nsresult DataStoreDB::AddEventListeners() { nsresult rv; rv = mRequest->EventTarget::AddEventListener(NS_LITERAL_STRING("success"), this, false); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = mRequest->EventTarget::AddEventListener(NS_LITERAL_STRING("upgradeneeded"), this, false); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = mRequest->EventTarget::AddEventListener(NS_LITERAL_STRING("error"), this, false); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = mRequest->EventTarget::AddEventListener(NS_LITERAL_STRING("blocked"), this, false); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult DataStoreDB::RemoveEventListeners() { nsresult rv; rv = mRequest->RemoveEventListener(NS_LITERAL_STRING("success"), this, false); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = mRequest->RemoveEventListener(NS_LITERAL_STRING("upgradeneeded"), this, false); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = mRequest->RemoveEventListener(NS_LITERAL_STRING("error"), this, false); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = mRequest->RemoveEventListener(NS_LITERAL_STRING("blocked"), this, false); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } } // namespace dom } // namespace mozilla