/* -*- 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 #include #include #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; StaticAutoPtr gLSDatabases; } // namespace StaticRefPtr 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 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& 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 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 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& database : ToTArray>>(gLSDatabases->Values())) { database->RequestAllowToClose(); } return NS_OK; } } // namespace mozilla::dom