forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			448 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			448 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
 | 
