diff options
Diffstat (limited to 'dom/localstorage/LSDatabase.cpp')
-rw-r--r-- | dom/localstorage/LSDatabase.cpp | 448 |
1 files changed, 448 insertions, 0 deletions
diff --git a/dom/localstorage/LSDatabase.cpp b/dom/localstorage/LSDatabase.cpp new file mode 100644 index 0000000000..2dccdaad26 --- /dev/null +++ b/dom/localstorage/LSDatabase.cpp @@ -0,0 +1,448 @@ +/* -*- 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 "LSDatabase.h" + +// Local includes +#include "ActorsChild.h" +#include "LSObject.h" +#include "LSSnapshot.h" + +// Global includes +#include <cstring> +#include <new> +#include <utility> +#include "MainThreadUtils.h" +#include "mozilla/MacroForEach.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/dom/PBackgroundLSDatabase.h" +#include "nsBaseHashtable.h" +#include "nsCOMPtr.h" +#include "nsTHashMap.h" +#include "nsDebug.h" +#include "nsError.h" +#include "nsHashKeys.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nscore.h" + +namespace mozilla::dom { + +namespace { + +#define XPCOM_SHUTDOWN_OBSERVER_TOPIC "xpcom-shutdown" + +using LSDatabaseHashtable = nsTHashMap<nsCStringHashKey, LSDatabase*>; + +StaticAutoPtr<LSDatabaseHashtable> gLSDatabases; + +} // namespace + +StaticRefPtr<LSDatabase::Observer> LSDatabase::sObserver; + +class LSDatabase::Observer final : public nsIObserver { + bool mInvalidated; + + public: + Observer() : mInvalidated(false) { MOZ_ASSERT(NS_IsMainThread()); } + + void Invalidate() { mInvalidated = true; } + + private: + ~Observer() { MOZ_ASSERT(NS_IsMainThread()); } + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER +}; + +LSDatabase::LSDatabase(const nsACString& aOrigin) + : mActor(nullptr), + mSnapshot(nullptr), + mOrigin(aOrigin), + mAllowedToClose(false), + mRequestedAllowToClose(false) { + AssertIsOnOwningThread(); + + if (!gLSDatabases) { + gLSDatabases = new LSDatabaseHashtable(); + + MOZ_ASSERT(!sObserver); + + sObserver = new Observer(); + + nsCOMPtr<nsIObserverService> obsSvc = + mozilla::services::GetObserverService(); + MOZ_ASSERT(obsSvc); + + MOZ_ALWAYS_SUCCEEDS( + obsSvc->AddObserver(sObserver, XPCOM_SHUTDOWN_OBSERVER_TOPIC, false)); + } + + MOZ_ASSERT(!gLSDatabases->Contains(mOrigin)); + gLSDatabases->InsertOrUpdate(mOrigin, this); +} + +LSDatabase::~LSDatabase() { + AssertIsOnOwningThread(); + MOZ_ASSERT(!mSnapshot); + + if (!mAllowedToClose) { + AllowToClose(); + } + + if (mActor) { + mActor->SendDeleteMeInternal(); + MOZ_ASSERT(!mActor, "SendDeleteMeInternal should have cleared!"); + } +} + +// static +LSDatabase* LSDatabase::Get(const nsACString& aOrigin) { + return gLSDatabases ? gLSDatabases->Get(aOrigin) : nullptr; +} + +void LSDatabase::SetActor(LSDatabaseChild* aActor) { + AssertIsOnOwningThread(); + MOZ_ASSERT(aActor); + MOZ_ASSERT(!mActor); + + mActor = aActor; +} + +void LSDatabase::RequestAllowToClose() { + AssertIsOnOwningThread(); + + if (mRequestedAllowToClose) { + return; + } + + mRequestedAllowToClose = true; + + if (mSnapshot) { + mSnapshot->MarkDirty(); + } else { + AllowToClose(); + } +} + +void LSDatabase::NoteFinishedSnapshot(LSSnapshot* aSnapshot) { + AssertIsOnOwningThread(); + MOZ_ASSERT(aSnapshot == mSnapshot); + + mSnapshot = nullptr; + + if (mRequestedAllowToClose) { + AllowToClose(); + } +} + +// All these methods assert `!mAllowedToClose` because they shoudn't be called +// if the database is being closed. Callers should first check the state by +// calling `IsAlloweToClose` and eventually obtain a new database. + +nsresult LSDatabase::GetLength(LSObject* aObject, uint32_t* aResult) { + AssertIsOnOwningThread(); + MOZ_ASSERT(aObject); + MOZ_ASSERT(mActor); + MOZ_ASSERT(!mAllowedToClose); + + nsresult rv = EnsureSnapshot(aObject, VoidString()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = mSnapshot->GetLength(aResult); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult LSDatabase::GetKey(LSObject* aObject, uint32_t aIndex, + nsAString& aResult) { + AssertIsOnOwningThread(); + MOZ_ASSERT(aObject); + MOZ_ASSERT(mActor); + MOZ_ASSERT(!mAllowedToClose); + + nsresult rv = EnsureSnapshot(aObject, VoidString()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = mSnapshot->GetKey(aIndex, aResult); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult LSDatabase::GetItem(LSObject* aObject, const nsAString& aKey, + nsAString& aResult) { + AssertIsOnOwningThread(); + MOZ_ASSERT(aObject); + MOZ_ASSERT(mActor); + MOZ_ASSERT(!mAllowedToClose); + + nsresult rv = EnsureSnapshot(aObject, aKey); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = mSnapshot->GetItem(aKey, aResult); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult LSDatabase::GetKeys(LSObject* aObject, nsTArray<nsString>& aKeys) { + AssertIsOnOwningThread(); + MOZ_ASSERT(aObject); + MOZ_ASSERT(mActor); + MOZ_ASSERT(!mAllowedToClose); + + nsresult rv = EnsureSnapshot(aObject, VoidString()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = mSnapshot->GetKeys(aKeys); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult LSDatabase::SetItem(LSObject* aObject, const nsAString& aKey, + const nsAString& aValue, + LSNotifyInfo& aNotifyInfo) { + AssertIsOnOwningThread(); + MOZ_ASSERT(aObject); + MOZ_ASSERT(mActor); + MOZ_ASSERT(!mAllowedToClose); + + nsresult rv = EnsureSnapshot(aObject, aKey); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = mSnapshot->SetItem(aKey, aValue, aNotifyInfo); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult LSDatabase::RemoveItem(LSObject* aObject, const nsAString& aKey, + LSNotifyInfo& aNotifyInfo) { + AssertIsOnOwningThread(); + MOZ_ASSERT(aObject); + MOZ_ASSERT(mActor); + MOZ_ASSERT(!mAllowedToClose); + + nsresult rv = EnsureSnapshot(aObject, aKey); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = mSnapshot->RemoveItem(aKey, aNotifyInfo); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult LSDatabase::Clear(LSObject* aObject, LSNotifyInfo& aNotifyInfo) { + AssertIsOnOwningThread(); + MOZ_ASSERT(aObject); + MOZ_ASSERT(mActor); + MOZ_ASSERT(!mAllowedToClose); + + nsresult rv = EnsureSnapshot(aObject, VoidString()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = mSnapshot->Clear(aNotifyInfo); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult LSDatabase::BeginExplicitSnapshot(LSObject* aObject) { + AssertIsOnOwningThread(); + MOZ_ASSERT(aObject); + MOZ_ASSERT(mActor); + MOZ_ASSERT(!mAllowedToClose); + MOZ_ASSERT(!mSnapshot); + + nsresult rv = EnsureSnapshot(aObject, VoidString(), /* aExplicit */ true); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult LSDatabase::CheckpointExplicitSnapshot() { + AssertIsOnOwningThread(); + MOZ_ASSERT(mActor); + MOZ_ASSERT(!mAllowedToClose); + MOZ_ASSERT(mSnapshot); + MOZ_ASSERT(mSnapshot->Explicit()); + + nsresult rv = mSnapshot->ExplicitCheckpoint(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult LSDatabase::EndExplicitSnapshot() { + AssertIsOnOwningThread(); + MOZ_ASSERT(mActor); + MOZ_ASSERT(!mAllowedToClose); + MOZ_ASSERT(mSnapshot); + MOZ_ASSERT(mSnapshot->Explicit()); + + nsresult rv = mSnapshot->ExplicitEnd(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +bool LSDatabase::HasSnapshot() const { + AssertIsOnOwningThread(); + MOZ_ASSERT(mActor); + MOZ_ASSERT(!mAllowedToClose); + + return !!mSnapshot; +} + +int64_t LSDatabase::GetSnapshotUsage() const { + AssertIsOnOwningThread(); + MOZ_ASSERT(mActor); + MOZ_ASSERT(!mAllowedToClose); + MOZ_ASSERT(mSnapshot); + + return mSnapshot->GetUsage(); +} + +nsresult LSDatabase::EnsureSnapshot(LSObject* aObject, const nsAString& aKey, + bool aExplicit) { + MOZ_ASSERT(aObject); + MOZ_ASSERT(mActor); + MOZ_ASSERT_IF(mSnapshot, !aExplicit); + MOZ_ASSERT(!mAllowedToClose); + + if (mSnapshot) { + return NS_OK; + } + + RefPtr<LSSnapshot> snapshot = new LSSnapshot(this); + + LSSnapshotChild* actor = new LSSnapshotChild(snapshot); + + LSSnapshotInitInfo initInfo; + bool ok = mActor->SendPBackgroundLSSnapshotConstructor( + actor, aObject->DocumentURI(), nsString(aKey), + /* increasePeakUsage */ true, + /* minSize */ 0, &initInfo); + if (NS_WARN_IF(!ok)) { + return NS_ERROR_FAILURE; + } + + snapshot->SetActor(actor); + + // This add refs snapshot. + nsresult rv = snapshot->Init(aKey, initInfo, aExplicit); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // This is cleared in LSSnapshot::Run() before the snapshot is destroyed. + mSnapshot = snapshot; + + return NS_OK; +} + +void LSDatabase::AllowToClose() { + AssertIsOnOwningThread(); + MOZ_ASSERT(!mAllowedToClose); + MOZ_ASSERT(!mSnapshot); + + mAllowedToClose = true; + + if (mActor) { + mActor->SendAllowToClose(); + } + + MOZ_ASSERT(gLSDatabases); + MOZ_ASSERT(gLSDatabases->Get(mOrigin)); + gLSDatabases->Remove(mOrigin); + + if (!gLSDatabases->Count()) { + gLSDatabases = nullptr; + + MOZ_ASSERT(sObserver); + + nsCOMPtr<nsIObserverService> obsSvc = + mozilla::services::GetObserverService(); + MOZ_ASSERT(obsSvc); + + MOZ_ALWAYS_SUCCEEDS( + obsSvc->RemoveObserver(sObserver, XPCOM_SHUTDOWN_OBSERVER_TOPIC)); + + // We also need to invalidate the observer because AllowToClose can be + // triggered by an indirectly related observer, so the observer service + // may still keep our observer alive and call Observe on it. This is + // possible because observer service snapshots the observer list for given + // subject before looping over the list. + sObserver->Invalidate(); + + sObserver = nullptr; + } +} + +NS_IMPL_ISUPPORTS(LSDatabase::Observer, nsIObserver) + +NS_IMETHODIMP +LSDatabase::Observer::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!strcmp(aTopic, XPCOM_SHUTDOWN_OBSERVER_TOPIC)); + + if (mInvalidated) { + return NS_OK; + } + + MOZ_ASSERT(gLSDatabases); + + for (const RefPtr<LSDatabase>& database : + ToTArray<nsTArray<RefPtr<LSDatabase>>>(gLSDatabases->Values())) { + database->RequestAllowToClose(); + } + + return NS_OK; +} + +} // namespace mozilla::dom |