Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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->Shutdown();
MOZ_ASSERT(!mActor, "Shutdown 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