mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 05:11:16 +00:00
01ad68bec6
Differential Revision: https://phabricator.services.mozilla.com/D138405
449 lines
10 KiB
C++
449 lines
10 KiB
C++
/* -*- 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
|