forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			1625 lines
		
	
	
	
		
			49 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1625 lines
		
	
	
	
		
			49 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=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 "StorageDBThread.h"
 | |
| 
 | |
| #include "StorageCommon.h"
 | |
| #include "StorageDBUpdater.h"
 | |
| #include "StorageUtils.h"
 | |
| #include "LocalStorageCache.h"
 | |
| #include "LocalStorageManager.h"
 | |
| 
 | |
| #include "nsComponentManagerUtils.h"
 | |
| #include "nsDirectoryServiceUtils.h"
 | |
| #include "nsAppDirectoryServiceDefs.h"
 | |
| #include "nsThreadUtils.h"
 | |
| #include "nsProxyRelease.h"
 | |
| #include "mozStorageCID.h"
 | |
| #include "mozStorageHelper.h"
 | |
| #include "mozIStorageService.h"
 | |
| #include "mozIStorageBindingParams.h"
 | |
| #include "mozIStorageValueArray.h"
 | |
| #include "mozIStorageFunction.h"
 | |
| #include "mozilla/BasePrincipal.h"
 | |
| #include "mozilla/ipc/BackgroundParent.h"
 | |
| #include "nsIObserverService.h"
 | |
| #include "nsThread.h"
 | |
| #include "nsThreadManager.h"
 | |
| #include "nsVariant.h"
 | |
| #include "mozilla/EventQueue.h"
 | |
| #include "mozilla/IOInterposer.h"
 | |
| #include "mozilla/OriginAttributes.h"
 | |
| #include "mozilla/ThreadEventQueue.h"
 | |
| #include "mozilla/Services.h"
 | |
| #include "mozilla/Tokenizer.h"
 | |
| #include "GeckoProfiler.h"
 | |
| 
 | |
| // How long we collect write oprerations
 | |
| // before they are flushed to the database
 | |
| // In milliseconds.
 | |
| #define FLUSHING_INTERVAL_MS 5000
 | |
| 
 | |
| // Write Ahead Log's maximum size is 512KB
 | |
| #define MAX_WAL_SIZE_BYTES 512 * 1024
 | |
| 
 | |
| // Current version of the database schema
 | |
| #define CURRENT_SCHEMA_VERSION 2
 | |
| 
 | |
| namespace mozilla::dom {
 | |
| 
 | |
| using namespace StorageUtils;
 | |
| 
 | |
| namespace {  // anon
 | |
| 
 | |
| StorageDBThread* sStorageThread[kPrivateBrowsingIdCount] = {nullptr, nullptr};
 | |
| 
 | |
| // False until we shut the storage thread down.
 | |
| bool sStorageThreadDown[kPrivateBrowsingIdCount] = {false, false};
 | |
| 
 | |
| }  // namespace
 | |
| 
 | |
| // XXX Fix me!
 | |
| #if 0
 | |
| StorageDBBridge::StorageDBBridge()
 | |
| {
 | |
| }
 | |
| #endif
 | |
| 
 | |
| class StorageDBThread::InitHelper final : public Runnable {
 | |
|   nsCOMPtr<nsIEventTarget> mOwningThread;
 | |
|   mozilla::Mutex mMutex MOZ_UNANNOTATED;
 | |
|   mozilla::CondVar mCondVar;
 | |
|   nsString mProfilePath;
 | |
|   nsresult mMainThreadResultCode;
 | |
|   bool mWaiting;
 | |
| 
 | |
|  public:
 | |
|   InitHelper()
 | |
|       : Runnable("dom::StorageDBThread::InitHelper"),
 | |
|         mOwningThread(GetCurrentSerialEventTarget()),
 | |
|         mMutex("InitHelper::mMutex"),
 | |
|         mCondVar(mMutex, "InitHelper::mCondVar"),
 | |
|         mMainThreadResultCode(NS_OK),
 | |
|         mWaiting(true) {}
 | |
| 
 | |
|   // Because of the `sync Preload` IPC, we need to be able to synchronously
 | |
|   // initialize, which includes consulting and initializing
 | |
|   // some main-thread-only APIs. Bug 1386441 discusses improving this situation.
 | |
|   nsresult SyncDispatchAndReturnProfilePath(nsAString& aProfilePath);
 | |
| 
 | |
|  private:
 | |
|   ~InitHelper() override = default;
 | |
| 
 | |
|   nsresult RunOnMainThread();
 | |
| 
 | |
|   NS_DECL_NSIRUNNABLE
 | |
| };
 | |
| 
 | |
| class StorageDBThread::NoteBackgroundThreadRunnable final : public Runnable {
 | |
|   // Expected to be only 0 or 1.
 | |
|   const uint32_t mPrivateBrowsingId;
 | |
|   nsCOMPtr<nsIEventTarget> mOwningThread;
 | |
| 
 | |
|  public:
 | |
|   explicit NoteBackgroundThreadRunnable(const uint32_t aPrivateBrowsingId)
 | |
|       : Runnable("dom::StorageDBThread::NoteBackgroundThreadRunnable"),
 | |
|         mPrivateBrowsingId(aPrivateBrowsingId),
 | |
|         mOwningThread(GetCurrentSerialEventTarget()) {
 | |
|     MOZ_RELEASE_ASSERT(aPrivateBrowsingId < kPrivateBrowsingIdCount);
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   ~NoteBackgroundThreadRunnable() override = default;
 | |
| 
 | |
|   NS_DECL_NSIRUNNABLE
 | |
| };
 | |
| 
 | |
| StorageDBThread::StorageDBThread(const uint32_t aPrivateBrowsingId)
 | |
|     : mThread(nullptr),
 | |
|       mThreadObserver(new ThreadObserver()),
 | |
|       mStopIOThread(false),
 | |
|       mWALModeEnabled(false),
 | |
|       mDBReady(false),
 | |
|       mStatus(NS_OK),
 | |
|       mWorkerStatements(mWorkerConnection),
 | |
|       mReaderStatements(mReaderConnection),
 | |
|       mFlushImmediately(false),
 | |
|       mPrivateBrowsingId(aPrivateBrowsingId),
 | |
|       mPriorityCounter(0) {
 | |
|   MOZ_RELEASE_ASSERT(aPrivateBrowsingId < kPrivateBrowsingIdCount);
 | |
| }
 | |
| 
 | |
| // static
 | |
| StorageDBThread* StorageDBThread::Get(const uint32_t aPrivateBrowsingId) {
 | |
|   ::mozilla::ipc::AssertIsOnBackgroundThread();
 | |
|   MOZ_RELEASE_ASSERT(aPrivateBrowsingId < kPrivateBrowsingIdCount);
 | |
| 
 | |
|   return sStorageThread[aPrivateBrowsingId];
 | |
| }
 | |
| 
 | |
| // static
 | |
| StorageDBThread* StorageDBThread::GetOrCreate(
 | |
|     const nsString& aProfilePath, const uint32_t aPrivateBrowsingId) {
 | |
|   ::mozilla::ipc::AssertIsOnBackgroundThread();
 | |
|   MOZ_RELEASE_ASSERT(aPrivateBrowsingId < kPrivateBrowsingIdCount);
 | |
| 
 | |
|   StorageDBThread*& storageThread = sStorageThread[aPrivateBrowsingId];
 | |
|   if (storageThread || sStorageThreadDown[aPrivateBrowsingId]) {
 | |
|     // When sStorageThreadDown is at true, sStorageThread is null.
 | |
|     // Checking sStorageThreadDown flag here prevents reinitialization of
 | |
|     // the storage thread after shutdown.
 | |
|     return storageThread;
 | |
|   }
 | |
| 
 | |
|   auto newStorageThread = MakeUnique<StorageDBThread>(aPrivateBrowsingId);
 | |
| 
 | |
|   nsresult rv = newStorageThread->Init(aProfilePath);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   storageThread = newStorageThread.release();
 | |
| 
 | |
|   return storageThread;
 | |
| }
 | |
| 
 | |
| // static
 | |
| nsresult StorageDBThread::GetProfilePath(nsString& aProfilePath) {
 | |
|   MOZ_ASSERT(XRE_IsParentProcess());
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   // Need to determine location on the main thread, since
 | |
|   // NS_GetSpecialDirectory accesses the atom table that can
 | |
|   // only be accessed on the main thread.
 | |
|   nsCOMPtr<nsIFile> profileDir;
 | |
|   nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
 | |
|                                        getter_AddRefs(profileDir));
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   rv = profileDir->GetPath(aProfilePath);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   // This service has to be started on the main thread currently.
 | |
|   nsCOMPtr<mozIStorageService> ss =
 | |
|       do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult StorageDBThread::Init(const nsString& aProfilePath) {
 | |
|   ::mozilla::ipc::AssertIsOnBackgroundThread();
 | |
| 
 | |
|   if (mPrivateBrowsingId == 0) {
 | |
|     nsresult rv;
 | |
| 
 | |
|     nsString profilePath;
 | |
|     if (aProfilePath.IsEmpty()) {
 | |
|       RefPtr<InitHelper> helper = new InitHelper();
 | |
| 
 | |
|       rv = helper->SyncDispatchAndReturnProfilePath(profilePath);
 | |
|       if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|         return rv;
 | |
|       }
 | |
|     } else {
 | |
|       profilePath = aProfilePath;
 | |
|     }
 | |
| 
 | |
|     mDatabaseFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       return rv;
 | |
|     }
 | |
| 
 | |
|     rv = mDatabaseFile->InitWithPath(profilePath);
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       return rv;
 | |
|     }
 | |
| 
 | |
|     rv = mDatabaseFile->Append(u"webappsstore.sqlite"_ns);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
|   }
 | |
| 
 | |
|   // Need to keep the lock to avoid setting mThread later then
 | |
|   // the thread body executes.
 | |
|   MonitorAutoLock monitor(mThreadObserver->GetMonitor());
 | |
| 
 | |
|   mThread = PR_CreateThread(PR_USER_THREAD, &StorageDBThread::ThreadFunc, this,
 | |
|                             PR_PRIORITY_LOW, PR_GLOBAL_THREAD,
 | |
|                             PR_JOINABLE_THREAD, 262144);
 | |
|   if (!mThread) {
 | |
|     return NS_ERROR_OUT_OF_MEMORY;
 | |
|   }
 | |
| 
 | |
|   RefPtr<NoteBackgroundThreadRunnable> runnable =
 | |
|       new NoteBackgroundThreadRunnable(mPrivateBrowsingId);
 | |
|   MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable));
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult StorageDBThread::Shutdown() {
 | |
|   ::mozilla::ipc::AssertIsOnBackgroundThread();
 | |
| 
 | |
|   if (!mThread) {
 | |
|     return NS_ERROR_NOT_INITIALIZED;
 | |
|   }
 | |
| 
 | |
|   Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_SHUTDOWN_DATABASE_MS> timer;
 | |
| 
 | |
|   {
 | |
|     MonitorAutoLock monitor(mThreadObserver->GetMonitor());
 | |
| 
 | |
|     // After we stop, no other operations can be accepted
 | |
|     mFlushImmediately = true;
 | |
|     mStopIOThread = true;
 | |
|     monitor.Notify();
 | |
|   }
 | |
| 
 | |
|   PR_JoinThread(mThread);
 | |
|   mThread = nullptr;
 | |
| 
 | |
|   return mStatus;
 | |
| }
 | |
| 
 | |
| void StorageDBThread::SyncPreload(LocalStorageCacheBridge* aCache,
 | |
|                                   bool aForceSync) {
 | |
|   AUTO_PROFILER_LABEL("StorageDBThread::SyncPreload", OTHER);
 | |
|   if (!aForceSync && aCache->LoadedCount()) {
 | |
|     // Preload already started for this cache, just wait for it to finish.
 | |
|     // LoadWait will exit after LoadDone on the cache has been called.
 | |
|     SetHigherPriority();
 | |
|     aCache->LoadWait();
 | |
|     SetDefaultPriority();
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Bypass sync load when an update is pending in the queue to write, we would
 | |
|   // get incosistent data in the cache.  Also don't allow sync main-thread
 | |
|   // preload when DB open and init is still pending on the background thread.
 | |
|   if (mDBReady && mWALModeEnabled) {
 | |
|     bool pendingTasks;
 | |
|     {
 | |
|       MonitorAutoLock monitor(mThreadObserver->GetMonitor());
 | |
|       pendingTasks = mPendingTasks.IsOriginUpdatePending(
 | |
|                          aCache->OriginSuffix(), aCache->OriginNoSuffix()) ||
 | |
|                      mPendingTasks.IsOriginClearPending(
 | |
|                          aCache->OriginSuffix(), aCache->OriginNoSuffix());
 | |
|     }
 | |
| 
 | |
|     if (!pendingTasks) {
 | |
|       // WAL is enabled, thus do the load synchronously on the main thread.
 | |
|       DBOperation preload(DBOperation::opPreload, aCache);
 | |
|       preload.PerformAndFinalize(this);
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Need to go asynchronously since WAL is not allowed or scheduled updates
 | |
|   // need to be flushed first.
 | |
|   // Schedule preload for this cache as the first operation.
 | |
|   nsresult rv =
 | |
|       InsertDBOp(MakeUnique<DBOperation>(DBOperation::opPreloadUrgent, aCache));
 | |
| 
 | |
|   // LoadWait exits after LoadDone of the cache has been called.
 | |
|   if (NS_SUCCEEDED(rv)) {
 | |
|     aCache->LoadWait();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void StorageDBThread::AsyncFlush() {
 | |
|   MonitorAutoLock monitor(mThreadObserver->GetMonitor());
 | |
|   mFlushImmediately = true;
 | |
|   monitor.Notify();
 | |
| }
 | |
| 
 | |
| bool StorageDBThread::ShouldPreloadOrigin(const nsACString& aOrigin) {
 | |
|   MonitorAutoLock monitor(mThreadObserver->GetMonitor());
 | |
|   return mOriginsHavingData.Contains(aOrigin);
 | |
| }
 | |
| 
 | |
| void StorageDBThread::GetOriginsHavingData(nsTArray<nsCString>* aOrigins) {
 | |
|   MonitorAutoLock monitor(mThreadObserver->GetMonitor());
 | |
|   AppendToArray(*aOrigins, mOriginsHavingData);
 | |
| }
 | |
| 
 | |
| nsresult StorageDBThread::InsertDBOp(
 | |
|     UniquePtr<StorageDBThread::DBOperation> aOperation) {
 | |
|   MonitorAutoLock monitor(mThreadObserver->GetMonitor());
 | |
| 
 | |
|   if (NS_FAILED(mStatus)) {
 | |
|     MonitorAutoUnlock unlock(mThreadObserver->GetMonitor());
 | |
|     aOperation->Finalize(mStatus);
 | |
|     return mStatus;
 | |
|   }
 | |
| 
 | |
|   if (mStopIOThread) {
 | |
|     // Thread use after shutdown demanded.
 | |
|     MOZ_ASSERT(false);
 | |
|     return NS_ERROR_NOT_INITIALIZED;
 | |
|   }
 | |
| 
 | |
|   switch (aOperation->Type()) {
 | |
|     case DBOperation::opPreload:
 | |
|     case DBOperation::opPreloadUrgent:
 | |
|       if (mPendingTasks.IsOriginUpdatePending(aOperation->OriginSuffix(),
 | |
|                                               aOperation->OriginNoSuffix())) {
 | |
|         // If there is a pending update operation for the scope first do the
 | |
|         // flush before we preload the cache.  This may happen in an extremely
 | |
|         // rare case when a child process throws away its cache before flush on
 | |
|         // the parent has finished.  If we would preloaded the cache as a
 | |
|         // priority operation before the pending flush, we would have got an
 | |
|         // inconsistent cache content.
 | |
|         mFlushImmediately = true;
 | |
|       } else if (mPendingTasks.IsOriginClearPending(
 | |
|                      aOperation->OriginSuffix(),
 | |
|                      aOperation->OriginNoSuffix())) {
 | |
|         // The scope is scheduled to be cleared, so just quickly load as empty.
 | |
|         // We need to do this to prevent load of the DB data before the scope
 | |
|         // has actually been cleared from the database.  Preloads are processed
 | |
|         // immediately before update and clear operations on the database that
 | |
|         // are flushed periodically in batches.
 | |
|         MonitorAutoUnlock unlock(mThreadObserver->GetMonitor());
 | |
|         aOperation->Finalize(NS_OK);
 | |
|         return NS_OK;
 | |
|       }
 | |
|       [[fallthrough]];
 | |
| 
 | |
|     case DBOperation::opGetUsage:
 | |
|       if (aOperation->Type() == DBOperation::opPreloadUrgent) {
 | |
|         SetHigherPriority();  // Dropped back after urgent preload execution
 | |
|         mPreloads.InsertElementAt(0, aOperation.release());
 | |
|       } else {
 | |
|         mPreloads.AppendElement(aOperation.release());
 | |
|       }
 | |
| 
 | |
|       // Immediately start executing this.
 | |
|       monitor.Notify();
 | |
|       break;
 | |
| 
 | |
|     default:
 | |
|       // Update operations are first collected, coalesced and then flushed
 | |
|       // after a short time.
 | |
|       mPendingTasks.Add(std::move(aOperation));
 | |
| 
 | |
|       ScheduleFlush();
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void StorageDBThread::SetHigherPriority() {
 | |
|   ++mPriorityCounter;
 | |
|   PR_SetThreadPriority(mThread, PR_PRIORITY_URGENT);
 | |
| }
 | |
| 
 | |
| void StorageDBThread::SetDefaultPriority() {
 | |
|   if (--mPriorityCounter <= 0) {
 | |
|     PR_SetThreadPriority(mThread, PR_PRIORITY_LOW);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void StorageDBThread::ThreadFunc(void* aArg) {
 | |
|   {
 | |
|     auto queue = MakeRefPtr<ThreadEventQueue>(MakeUnique<EventQueue>());
 | |
|     Unused << nsThreadManager::get().CreateCurrentThread(queue);
 | |
|   }
 | |
| 
 | |
|   AUTO_PROFILER_REGISTER_THREAD("localStorage DB");
 | |
|   NS_SetCurrentThreadName("localStorage DB");
 | |
|   mozilla::IOInterposer::RegisterCurrentThread();
 | |
| 
 | |
|   StorageDBThread* thread = static_cast<StorageDBThread*>(aArg);
 | |
|   thread->ThreadFunc();
 | |
|   mozilla::IOInterposer::UnregisterCurrentThread();
 | |
| }
 | |
| 
 | |
| void StorageDBThread::ThreadFunc() {
 | |
|   nsresult rv = InitDatabase();
 | |
| 
 | |
|   MonitorAutoLock lockMonitor(mThreadObserver->GetMonitor());
 | |
| 
 | |
|   if (NS_FAILED(rv)) {
 | |
|     mStatus = rv;
 | |
|     mStopIOThread = true;
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Create an nsIThread for the current PRThread, so we can observe runnables
 | |
|   // dispatched to it.
 | |
|   nsCOMPtr<nsIThread> thread = NS_GetCurrentThread();
 | |
|   nsCOMPtr<nsIThreadInternal> threadInternal = do_QueryInterface(thread);
 | |
|   MOZ_ASSERT(threadInternal);  // Should always succeed.
 | |
|   threadInternal->SetObserver(mThreadObserver);
 | |
| 
 | |
|   while (MOZ_LIKELY(!mStopIOThread || mPreloads.Length() ||
 | |
|                     mPendingTasks.HasTasks() ||
 | |
|                     mThreadObserver->HasPendingEvents())) {
 | |
|     // Process xpcom events first.
 | |
|     while (MOZ_UNLIKELY(mThreadObserver->HasPendingEvents())) {
 | |
|       mThreadObserver->ClearPendingEvents();
 | |
|       MonitorAutoUnlock unlock(mThreadObserver->GetMonitor());
 | |
|       bool processedEvent;
 | |
|       do {
 | |
|         rv = thread->ProcessNextEvent(false, &processedEvent);
 | |
|       } while (NS_SUCCEEDED(rv) && processedEvent);
 | |
|     }
 | |
| 
 | |
|     TimeDuration timeUntilFlush = TimeUntilFlush();
 | |
|     if (MOZ_UNLIKELY(timeUntilFlush.IsZero())) {
 | |
|       // Flush time is up or flush has been forced, do it now.
 | |
|       UnscheduleFlush();
 | |
|       if (mPendingTasks.Prepare()) {
 | |
|         {
 | |
|           MonitorAutoUnlock unlockMonitor(mThreadObserver->GetMonitor());
 | |
|           rv = mPendingTasks.Execute(this);
 | |
|         }
 | |
| 
 | |
|         if (!mPendingTasks.Finalize(rv)) {
 | |
|           mStatus = rv;
 | |
|           NS_WARNING("localStorage DB access broken");
 | |
|         }
 | |
|       }
 | |
|       NotifyFlushCompletion();
 | |
|     } else if (MOZ_LIKELY(mPreloads.Length())) {
 | |
|       UniquePtr<DBOperation> op(mPreloads[0]);
 | |
|       mPreloads.RemoveElementAt(0);
 | |
|       {
 | |
|         MonitorAutoUnlock unlockMonitor(mThreadObserver->GetMonitor());
 | |
|         op->PerformAndFinalize(this);
 | |
|       }
 | |
| 
 | |
|       if (op->Type() == DBOperation::opPreloadUrgent) {
 | |
|         SetDefaultPriority();  // urgent preload unscheduled
 | |
|       }
 | |
|     } else if (MOZ_UNLIKELY(!mStopIOThread)) {
 | |
|       AUTO_PROFILER_LABEL("StorageDBThread::ThreadFunc::Wait", IDLE);
 | |
|       lockMonitor.Wait(timeUntilFlush);
 | |
|     }
 | |
|   }  // thread loop
 | |
| 
 | |
|   mStatus = ShutdownDatabase();
 | |
| 
 | |
|   if (threadInternal) {
 | |
|     threadInternal->SetObserver(nullptr);
 | |
|   }
 | |
| }
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(StorageDBThread::ThreadObserver, nsIThreadObserver)
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| StorageDBThread::ThreadObserver::OnDispatchedEvent() {
 | |
|   MonitorAutoLock lock(mMonitor);
 | |
|   mHasPendingEvents = true;
 | |
|   lock.Notify();
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| StorageDBThread::ThreadObserver::OnProcessNextEvent(nsIThreadInternal* aThread,
 | |
|                                                     bool mayWait) {
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| StorageDBThread::ThreadObserver::AfterProcessNextEvent(
 | |
|     nsIThreadInternal* aThread, bool eventWasProcessed) {
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult StorageDBThread::OpenDatabaseConnection() {
 | |
|   nsresult rv;
 | |
| 
 | |
|   MOZ_ASSERT(!NS_IsMainThread());
 | |
| 
 | |
|   nsCOMPtr<mozIStorageService> service =
 | |
|       do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   if (mPrivateBrowsingId == 0) {
 | |
|     MOZ_ASSERT(mDatabaseFile);
 | |
| 
 | |
|     rv = service->OpenUnsharedDatabase(mDatabaseFile,
 | |
|                                        mozIStorageService::CONNECTION_DEFAULT,
 | |
|                                        getter_AddRefs(mWorkerConnection));
 | |
|     if (rv == NS_ERROR_FILE_CORRUPTED) {
 | |
|       // delete the db and try opening again
 | |
|       rv = mDatabaseFile->Remove(false);
 | |
|       NS_ENSURE_SUCCESS(rv, rv);
 | |
|       rv = service->OpenUnsharedDatabase(mDatabaseFile,
 | |
|                                          mozIStorageService::CONNECTION_DEFAULT,
 | |
|                                          getter_AddRefs(mWorkerConnection));
 | |
|     }
 | |
|   } else {
 | |
|     MOZ_ASSERT(mPrivateBrowsingId == 1);
 | |
| 
 | |
|     rv = service->OpenSpecialDatabase(kMozStorageMemoryStorageKey,
 | |
|                                       "lsprivatedb"_ns,
 | |
|                                       mozIStorageService::CONNECTION_DEFAULT,
 | |
|                                       getter_AddRefs(mWorkerConnection));
 | |
|   }
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult StorageDBThread::OpenAndUpdateDatabase() {
 | |
|   nsresult rv;
 | |
| 
 | |
|   // Here we are on the worker thread. This opens the worker connection.
 | |
|   MOZ_ASSERT(!NS_IsMainThread());
 | |
| 
 | |
|   rv = OpenDatabaseConnection();
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   // SQLite doesn't support WAL journals for in-memory databases.
 | |
|   if (mPrivateBrowsingId == 0) {
 | |
|     rv = TryJournalMode();
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult StorageDBThread::InitDatabase() {
 | |
|   nsresult rv;
 | |
| 
 | |
|   // Here we are on the worker thread. This opens the worker connection.
 | |
|   MOZ_ASSERT(!NS_IsMainThread());
 | |
| 
 | |
|   rv = OpenAndUpdateDatabase();
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   rv = StorageDBUpdater::Update(mWorkerConnection);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     if (mPrivateBrowsingId == 0) {
 | |
|       // Update has failed, rather throw the database away and try
 | |
|       // opening and setting it up again.
 | |
|       rv = mWorkerConnection->Close();
 | |
|       mWorkerConnection = nullptr;
 | |
|       NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|       rv = mDatabaseFile->Remove(false);
 | |
|       NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|       rv = OpenAndUpdateDatabase();
 | |
|     }
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
|   }
 | |
| 
 | |
|   // Create a read-only clone
 | |
|   (void)mWorkerConnection->Clone(true, getter_AddRefs(mReaderConnection));
 | |
|   NS_ENSURE_TRUE(mReaderConnection, NS_ERROR_FAILURE);
 | |
| 
 | |
|   // Database open and all initiation operation are done.  Switching this flag
 | |
|   // to true allow main thread to read directly from the database.  If we would
 | |
|   // allow this sooner, we would have opened a window where main thread read
 | |
|   // might operate on a totally broken and incosistent database.
 | |
|   mDBReady = true;
 | |
| 
 | |
|   // List scopes having any stored data
 | |
|   nsCOMPtr<mozIStorageStatement> stmt;
 | |
|   // Note: result of this select must match StorageManager::CreateOrigin()
 | |
|   rv = mWorkerConnection->CreateStatement(
 | |
|       nsLiteralCString("SELECT DISTINCT originAttributes || ':' || originKey "
 | |
|                        "FROM webappsstore2"),
 | |
|       getter_AddRefs(stmt));
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
|   mozStorageStatementScoper scope(stmt);
 | |
| 
 | |
|   bool exists;
 | |
|   while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&exists)) && exists) {
 | |
|     nsAutoCString foundOrigin;
 | |
|     rv = stmt->GetUTF8String(0, foundOrigin);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|     MonitorAutoLock monitor(mThreadObserver->GetMonitor());
 | |
|     mOriginsHavingData.Insert(foundOrigin);
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult StorageDBThread::SetJournalMode(bool aIsWal) {
 | |
|   nsresult rv;
 | |
| 
 | |
|   nsAutoCString stmtString(MOZ_STORAGE_UNIQUIFY_QUERY_STR
 | |
|                            "PRAGMA journal_mode = ");
 | |
|   if (aIsWal) {
 | |
|     stmtString.AppendLiteral("wal");
 | |
|   } else {
 | |
|     stmtString.AppendLiteral("truncate");
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<mozIStorageStatement> stmt;
 | |
|   rv = mWorkerConnection->CreateStatement(stmtString, getter_AddRefs(stmt));
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
|   mozStorageStatementScoper scope(stmt);
 | |
| 
 | |
|   bool hasResult = false;
 | |
|   rv = stmt->ExecuteStep(&hasResult);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
|   if (!hasResult) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   nsAutoCString journalMode;
 | |
|   rv = stmt->GetUTF8String(0, journalMode);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
|   if ((aIsWal && !journalMode.EqualsLiteral("wal")) ||
 | |
|       (!aIsWal && !journalMode.EqualsLiteral("truncate"))) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult StorageDBThread::TryJournalMode() {
 | |
|   nsresult rv;
 | |
| 
 | |
|   rv = SetJournalMode(true);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     mWALModeEnabled = false;
 | |
| 
 | |
|     rv = SetJournalMode(false);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
|   } else {
 | |
|     mWALModeEnabled = true;
 | |
| 
 | |
|     rv = ConfigureWALBehavior();
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult StorageDBThread::ConfigureWALBehavior() {
 | |
|   // Get the DB's page size
 | |
|   nsCOMPtr<mozIStorageStatement> stmt;
 | |
|   nsresult rv = mWorkerConnection->CreateStatement(
 | |
|       nsLiteralCString(MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA page_size"),
 | |
|       getter_AddRefs(stmt));
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   bool hasResult = false;
 | |
|   rv = stmt->ExecuteStep(&hasResult);
 | |
|   NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasResult, NS_ERROR_FAILURE);
 | |
| 
 | |
|   int32_t pageSize = 0;
 | |
|   rv = stmt->GetInt32(0, &pageSize);
 | |
|   NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && pageSize > 0, NS_ERROR_UNEXPECTED);
 | |
| 
 | |
|   // Set the threshold for auto-checkpointing the WAL.
 | |
|   // We don't want giant logs slowing down reads & shutdown.
 | |
|   // Note there is a default journal_size_limit set by mozStorage.
 | |
|   int32_t thresholdInPages =
 | |
|       static_cast<int32_t>(MAX_WAL_SIZE_BYTES / pageSize);
 | |
|   nsAutoCString thresholdPragma("PRAGMA wal_autocheckpoint = ");
 | |
|   thresholdPragma.AppendInt(thresholdInPages);
 | |
|   rv = mWorkerConnection->ExecuteSimpleSQL(thresholdPragma);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult StorageDBThread::ShutdownDatabase() {
 | |
|   // Has to be called on the worker thread.
 | |
|   MOZ_ASSERT(!NS_IsMainThread());
 | |
| 
 | |
|   nsresult rv = mStatus;
 | |
| 
 | |
|   mDBReady = false;
 | |
| 
 | |
|   // Finalize the cached statements.
 | |
|   mReaderStatements.FinalizeStatements();
 | |
|   mWorkerStatements.FinalizeStatements();
 | |
| 
 | |
|   if (mReaderConnection) {
 | |
|     // No need to sync access to mReaderConnection since the main thread
 | |
|     // is right now joining this thread, unable to execute any events.
 | |
|     mReaderConnection->Close();
 | |
|     mReaderConnection = nullptr;
 | |
|   }
 | |
| 
 | |
|   if (mWorkerConnection) {
 | |
|     rv = mWorkerConnection->Close();
 | |
|     mWorkerConnection = nullptr;
 | |
|   }
 | |
| 
 | |
|   return rv;
 | |
| }
 | |
| 
 | |
| void StorageDBThread::ScheduleFlush() {
 | |
|   if (mDirtyEpoch) {
 | |
|     return;  // Already scheduled
 | |
|   }
 | |
| 
 | |
|   // Must be non-zero to indicate we are scheduled
 | |
|   mDirtyEpoch = TimeStamp::Now();
 | |
| 
 | |
|   // Wake the monitor from indefinite sleep...
 | |
|   (mThreadObserver->GetMonitor()).Notify();
 | |
| }
 | |
| 
 | |
| void StorageDBThread::UnscheduleFlush() {
 | |
|   // We are just about to do the flush, drop flags
 | |
|   mFlushImmediately = false;
 | |
|   mDirtyEpoch = TimeStamp();
 | |
| }
 | |
| 
 | |
| TimeDuration StorageDBThread::TimeUntilFlush() {
 | |
|   if (mFlushImmediately) {
 | |
|     return 0;  // Do it now regardless the timeout.
 | |
|   }
 | |
| 
 | |
|   if (!mDirtyEpoch) {
 | |
|     return TimeDuration::Forever();  // No pending task...
 | |
|   }
 | |
| 
 | |
|   TimeStamp now = TimeStamp::Now();
 | |
|   TimeDuration age = now - mDirtyEpoch;
 | |
|   static const TimeDuration kMaxAge =
 | |
|       TimeDuration::FromMilliseconds(FLUSHING_INTERVAL_MS);
 | |
|   if (age > kMaxAge) {
 | |
|     return 0;  // It is time.
 | |
|   }
 | |
| 
 | |
|   return kMaxAge - age;  // Time left. This is used to sleep the monitor.
 | |
| }
 | |
| 
 | |
| void StorageDBThread::NotifyFlushCompletion() {
 | |
| #ifdef DOM_STORAGE_TESTS
 | |
|   if (!NS_IsMainThread()) {
 | |
|     RefPtr<nsRunnableMethod<StorageDBThread, void, false>> event =
 | |
|         NewNonOwningRunnableMethod(
 | |
|             "dom::StorageDBThread::NotifyFlushCompletion", this,
 | |
|             &StorageDBThread::NotifyFlushCompletion);
 | |
|     NS_DispatchToMainThread(event);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
 | |
|   if (obs) {
 | |
|     obs->NotifyObservers(nullptr, "domstorage-test-flushed", nullptr);
 | |
|   }
 | |
| #endif
 | |
| }
 | |
| 
 | |
| // Helper SQL function classes
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| class OriginAttrsPatternMatchSQLFunction final : public mozIStorageFunction {
 | |
|   NS_DECL_ISUPPORTS
 | |
|   NS_DECL_MOZISTORAGEFUNCTION
 | |
| 
 | |
|   explicit OriginAttrsPatternMatchSQLFunction(
 | |
|       OriginAttributesPattern const& aPattern)
 | |
|       : mPattern(aPattern) {}
 | |
| 
 | |
|  private:
 | |
|   OriginAttrsPatternMatchSQLFunction() = delete;
 | |
|   ~OriginAttrsPatternMatchSQLFunction() = default;
 | |
| 
 | |
|   OriginAttributesPattern mPattern;
 | |
| };
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(OriginAttrsPatternMatchSQLFunction, mozIStorageFunction)
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| OriginAttrsPatternMatchSQLFunction::OnFunctionCall(
 | |
|     mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) {
 | |
|   nsresult rv;
 | |
| 
 | |
|   nsAutoCString suffix;
 | |
|   rv = aFunctionArguments->GetUTF8String(0, suffix);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   OriginAttributes oa;
 | |
|   bool success = oa.PopulateFromSuffix(suffix);
 | |
|   NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
 | |
|   bool result = mPattern.Matches(oa);
 | |
| 
 | |
|   RefPtr<nsVariant> outVar(new nsVariant());
 | |
|   rv = outVar->SetAsBool(result);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   outVar.forget(aResult);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| }  // namespace
 | |
| 
 | |
| // StorageDBThread::DBOperation
 | |
| 
 | |
| StorageDBThread::DBOperation::DBOperation(const OperationType aType,
 | |
|                                           LocalStorageCacheBridge* aCache,
 | |
|                                           const nsAString& aKey,
 | |
|                                           const nsAString& aValue)
 | |
|     : mType(aType), mCache(aCache), mKey(aKey), mValue(aValue) {
 | |
|   MOZ_ASSERT(mType == opPreload || mType == opPreloadUrgent ||
 | |
|              mType == opAddItem || mType == opUpdateItem ||
 | |
|              mType == opRemoveItem || mType == opClear || mType == opClearAll);
 | |
|   MOZ_COUNT_CTOR(StorageDBThread::DBOperation);
 | |
| }
 | |
| 
 | |
| StorageDBThread::DBOperation::DBOperation(const OperationType aType,
 | |
|                                           StorageUsageBridge* aUsage)
 | |
|     : mType(aType), mUsage(aUsage) {
 | |
|   MOZ_ASSERT(mType == opGetUsage);
 | |
|   MOZ_COUNT_CTOR(StorageDBThread::DBOperation);
 | |
| }
 | |
| 
 | |
| StorageDBThread::DBOperation::DBOperation(const OperationType aType,
 | |
|                                           const nsACString& aOriginNoSuffix)
 | |
|     : mType(aType), mCache(nullptr), mOrigin(aOriginNoSuffix) {
 | |
|   MOZ_ASSERT(mType == opClearMatchingOrigin);
 | |
|   MOZ_COUNT_CTOR(StorageDBThread::DBOperation);
 | |
| }
 | |
| 
 | |
| StorageDBThread::DBOperation::DBOperation(
 | |
|     const OperationType aType, const OriginAttributesPattern& aOriginNoSuffix)
 | |
|     : mType(aType), mCache(nullptr), mOriginPattern(aOriginNoSuffix) {
 | |
|   MOZ_ASSERT(mType == opClearMatchingOriginAttributes);
 | |
|   MOZ_COUNT_CTOR(StorageDBThread::DBOperation);
 | |
| }
 | |
| 
 | |
| StorageDBThread::DBOperation::~DBOperation() {
 | |
|   MOZ_COUNT_DTOR(StorageDBThread::DBOperation);
 | |
| }
 | |
| 
 | |
| const nsCString StorageDBThread::DBOperation::OriginNoSuffix() const {
 | |
|   if (mCache) {
 | |
|     return mCache->OriginNoSuffix();
 | |
|   }
 | |
| 
 | |
|   return ""_ns;
 | |
| }
 | |
| 
 | |
| const nsCString StorageDBThread::DBOperation::OriginSuffix() const {
 | |
|   if (mCache) {
 | |
|     return mCache->OriginSuffix();
 | |
|   }
 | |
| 
 | |
|   return ""_ns;
 | |
| }
 | |
| 
 | |
| const nsCString StorageDBThread::DBOperation::Origin() const {
 | |
|   if (mCache) {
 | |
|     return mCache->Origin();
 | |
|   }
 | |
| 
 | |
|   return mOrigin;
 | |
| }
 | |
| 
 | |
| const nsCString StorageDBThread::DBOperation::Target() const {
 | |
|   switch (mType) {
 | |
|     case opAddItem:
 | |
|     case opUpdateItem:
 | |
|     case opRemoveItem:
 | |
|       return Origin() + "|"_ns + NS_ConvertUTF16toUTF8(mKey);
 | |
| 
 | |
|     default:
 | |
|       return Origin();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void StorageDBThread::DBOperation::PerformAndFinalize(
 | |
|     StorageDBThread* aThread) {
 | |
|   Finalize(Perform(aThread));
 | |
| }
 | |
| 
 | |
| nsresult StorageDBThread::DBOperation::Perform(StorageDBThread* aThread) {
 | |
|   nsresult rv;
 | |
| 
 | |
|   switch (mType) {
 | |
|     case opPreload:
 | |
|     case opPreloadUrgent: {
 | |
|       // Already loaded?
 | |
|       if (mCache->Loaded()) {
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|       StatementCache* statements;
 | |
|       if (MOZ_UNLIKELY(::mozilla::ipc::IsOnBackgroundThread())) {
 | |
|         statements = &aThread->mReaderStatements;
 | |
|       } else {
 | |
|         statements = &aThread->mWorkerStatements;
 | |
|       }
 | |
| 
 | |
|       // OFFSET is an optimization when we have to do a sync load
 | |
|       // and cache has already loaded some parts asynchronously.
 | |
|       // It skips keys we have already loaded.
 | |
|       nsCOMPtr<mozIStorageStatement> stmt = statements->GetCachedStatement(
 | |
|           "SELECT key, value FROM webappsstore2 "
 | |
|           "WHERE originAttributes = :originAttributes AND originKey = "
 | |
|           ":originKey "
 | |
|           "ORDER BY key LIMIT -1 OFFSET :offset");
 | |
|       NS_ENSURE_STATE(stmt);
 | |
|       mozStorageStatementScoper scope(stmt);
 | |
| 
 | |
|       rv = stmt->BindUTF8StringByName("originAttributes"_ns,
 | |
|                                       mCache->OriginSuffix());
 | |
|       NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|       rv = stmt->BindUTF8StringByName("originKey"_ns, mCache->OriginNoSuffix());
 | |
|       NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|       rv = stmt->BindInt32ByName("offset"_ns,
 | |
|                                  static_cast<int32_t>(mCache->LoadedCount()));
 | |
|       NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|       bool exists;
 | |
|       while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&exists)) && exists) {
 | |
|         nsAutoString key;
 | |
|         rv = stmt->GetString(0, key);
 | |
|         NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|         nsAutoString value;
 | |
|         rv = stmt->GetString(1, value);
 | |
|         NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|         if (!mCache->LoadItem(key, value)) {
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|       // The loop condition's call to ExecuteStep() may have terminated because
 | |
|       // !NS_SUCCEEDED(), we need an early return to cover that case.  This also
 | |
|       // covers success cases as well, but that's inductively safe.
 | |
|       NS_ENSURE_SUCCESS(rv, rv);
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     case opGetUsage: {
 | |
|       // Bug 1676410 fixed a regression caused by bug 1165214. However, it
 | |
|       // turns out that 100% correct checking of the eTLD+1 usage is not
 | |
|       // possible to recover easily, see bug 1683299.
 | |
| #if 0
 | |
|       // This is how it should be done, but due to other problems like lack
 | |
|       // of usage synchronization between content processes, we temporarily
 | |
|       // disabled the matching using "%".
 | |
| 
 | |
|       nsCOMPtr<mozIStorageStatement> stmt =
 | |
|           aThread->mWorkerStatements.GetCachedStatement(
 | |
|               "SELECT SUM(LENGTH(key) + LENGTH(value)) FROM webappsstore2 "
 | |
|               "WHERE (originAttributes || ':' || originKey) LIKE :usageOrigin "
 | |
|               "ESCAPE '\\'");
 | |
|       NS_ENSURE_STATE(stmt);
 | |
| 
 | |
|       mozStorageStatementScoper scope(stmt);
 | |
| 
 | |
|       // The database schema is built around cleverly reversing domain names
 | |
|       // (the "originKey") so that we can efficiently group usage by eTLD+1.
 | |
|       // "foo.example.org" has an eTLD+1 of ".example.org". They reverse to
 | |
|       // "gro.elpmaxe.oof" and "gro.elpmaxe." respectively, noting that the
 | |
|       // reversed eTLD+1 is a prefix of its reversed sub-domain. To this end,
 | |
|       // we can calculate all of the usage for an eTLD+1 by summing up all the
 | |
|       // rows which have the reversed eTLD+1 as a prefix. In SQL we can
 | |
|       // accomplish this using LIKE which provides for case-insensitive
 | |
|       // matching with "_" as a single-character wildcard match and "%" any
 | |
|       // sequence of zero or more characters. So by suffixing the reversed
 | |
|       // eTLD+1 and using "%" we get our case-insensitive (domain names are
 | |
|       // case-insensitive) matching. Note that although legal domain names
 | |
|       // don't include "_" or "%", file origins can include them, so we need
 | |
|       // to escape our OriginScope for correctness.
 | |
|       nsAutoCString originScopeEscaped;
 | |
|       rv = stmt->EscapeUTF8StringForLIKE(mUsage->OriginScope(), '\\',
 | |
|                                          originScopeEscaped);
 | |
|       NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|       rv = stmt->BindUTF8StringByName("usageOrigin"_ns,
 | |
|                                       originScopeEscaped + "%"_ns);
 | |
|       NS_ENSURE_SUCCESS(rv, rv);
 | |
| #else
 | |
|       // This is the code before bug 1676410 and bug 1676973. The returned
 | |
|       // usage will be zero in most of the cases, but due to lack of usage
 | |
|       // synchronization between content processes we have to live with this
 | |
|       // semi-broken behaviour because it causes less harm than the matching
 | |
|       // using "%".
 | |
| 
 | |
|       nsCOMPtr<mozIStorageStatement> stmt =
 | |
|           aThread->mWorkerStatements.GetCachedStatement(
 | |
|               "SELECT SUM(LENGTH(key) + LENGTH(value)) FROM webappsstore2 "
 | |
|               "WHERE (originAttributes || ':' || originKey) LIKE :usageOrigin");
 | |
|       NS_ENSURE_STATE(stmt);
 | |
| 
 | |
|       mozStorageStatementScoper scope(stmt);
 | |
| 
 | |
|       rv = stmt->BindUTF8StringByName("usageOrigin"_ns, mUsage->OriginScope());
 | |
|       NS_ENSURE_SUCCESS(rv, rv);
 | |
| #endif
 | |
| 
 | |
|       bool exists;
 | |
|       rv = stmt->ExecuteStep(&exists);
 | |
|       NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|       int64_t usage = 0;
 | |
|       if (exists) {
 | |
|         rv = stmt->GetInt64(0, &usage);
 | |
|         NS_ENSURE_SUCCESS(rv, rv);
 | |
|       }
 | |
| 
 | |
|       mUsage->LoadUsage(usage);
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     case opAddItem:
 | |
|     case opUpdateItem: {
 | |
|       MOZ_ASSERT(!NS_IsMainThread());
 | |
| 
 | |
|       nsCOMPtr<mozIStorageStatement> stmt =
 | |
|           aThread->mWorkerStatements.GetCachedStatement(
 | |
|               "INSERT OR REPLACE INTO webappsstore2 (originAttributes, "
 | |
|               "originKey, scope, key, value) "
 | |
|               "VALUES (:originAttributes, :originKey, :scope, :key, :value) ");
 | |
|       NS_ENSURE_STATE(stmt);
 | |
| 
 | |
|       mozStorageStatementScoper scope(stmt);
 | |
| 
 | |
|       rv = stmt->BindUTF8StringByName("originAttributes"_ns,
 | |
|                                       mCache->OriginSuffix());
 | |
|       NS_ENSURE_SUCCESS(rv, rv);
 | |
|       rv = stmt->BindUTF8StringByName("originKey"_ns, mCache->OriginNoSuffix());
 | |
|       NS_ENSURE_SUCCESS(rv, rv);
 | |
|       // Filling the 'scope' column just for downgrade compatibility reasons
 | |
|       rv = stmt->BindUTF8StringByName(
 | |
|           "scope"_ns,
 | |
|           Scheme0Scope(mCache->OriginSuffix(), mCache->OriginNoSuffix()));
 | |
|       NS_ENSURE_SUCCESS(rv, rv);
 | |
|       rv = stmt->BindStringByName("key"_ns, mKey);
 | |
|       NS_ENSURE_SUCCESS(rv, rv);
 | |
|       rv = stmt->BindStringByName("value"_ns, mValue);
 | |
|       NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|       rv = stmt->Execute();
 | |
|       NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|       MonitorAutoLock monitor(aThread->mThreadObserver->GetMonitor());
 | |
|       aThread->mOriginsHavingData.Insert(Origin());
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     case opRemoveItem: {
 | |
|       MOZ_ASSERT(!NS_IsMainThread());
 | |
| 
 | |
|       nsCOMPtr<mozIStorageStatement> stmt =
 | |
|           aThread->mWorkerStatements.GetCachedStatement(
 | |
|               "DELETE FROM webappsstore2 "
 | |
|               "WHERE originAttributes = :originAttributes AND originKey = "
 | |
|               ":originKey "
 | |
|               "AND key = :key ");
 | |
|       NS_ENSURE_STATE(stmt);
 | |
|       mozStorageStatementScoper scope(stmt);
 | |
| 
 | |
|       rv = stmt->BindUTF8StringByName("originAttributes"_ns,
 | |
|                                       mCache->OriginSuffix());
 | |
|       NS_ENSURE_SUCCESS(rv, rv);
 | |
|       rv = stmt->BindUTF8StringByName("originKey"_ns, mCache->OriginNoSuffix());
 | |
|       NS_ENSURE_SUCCESS(rv, rv);
 | |
|       rv = stmt->BindStringByName("key"_ns, mKey);
 | |
|       NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|       rv = stmt->Execute();
 | |
|       NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     case opClear: {
 | |
|       MOZ_ASSERT(!NS_IsMainThread());
 | |
| 
 | |
|       nsCOMPtr<mozIStorageStatement> stmt =
 | |
|           aThread->mWorkerStatements.GetCachedStatement(
 | |
|               "DELETE FROM webappsstore2 "
 | |
|               "WHERE originAttributes = :originAttributes AND originKey = "
 | |
|               ":originKey");
 | |
|       NS_ENSURE_STATE(stmt);
 | |
|       mozStorageStatementScoper scope(stmt);
 | |
| 
 | |
|       rv = stmt->BindUTF8StringByName("originAttributes"_ns,
 | |
|                                       mCache->OriginSuffix());
 | |
|       NS_ENSURE_SUCCESS(rv, rv);
 | |
|       rv = stmt->BindUTF8StringByName("originKey"_ns, mCache->OriginNoSuffix());
 | |
|       NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|       rv = stmt->Execute();
 | |
|       NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|       MonitorAutoLock monitor(aThread->mThreadObserver->GetMonitor());
 | |
|       aThread->mOriginsHavingData.Remove(Origin());
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     case opClearAll: {
 | |
|       MOZ_ASSERT(!NS_IsMainThread());
 | |
| 
 | |
|       nsCOMPtr<mozIStorageStatement> stmt =
 | |
|           aThread->mWorkerStatements.GetCachedStatement(
 | |
|               "DELETE FROM webappsstore2");
 | |
|       NS_ENSURE_STATE(stmt);
 | |
|       mozStorageStatementScoper scope(stmt);
 | |
| 
 | |
|       rv = stmt->Execute();
 | |
|       NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|       MonitorAutoLock monitor(aThread->mThreadObserver->GetMonitor());
 | |
|       aThread->mOriginsHavingData.Clear();
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     case opClearMatchingOrigin: {
 | |
|       MOZ_ASSERT(!NS_IsMainThread());
 | |
| 
 | |
|       nsCOMPtr<mozIStorageStatement> stmt =
 | |
|           aThread->mWorkerStatements.GetCachedStatement(
 | |
|               "DELETE FROM webappsstore2"
 | |
|               " WHERE originKey GLOB :scope");
 | |
|       NS_ENSURE_STATE(stmt);
 | |
|       mozStorageStatementScoper scope(stmt);
 | |
| 
 | |
|       rv = stmt->BindUTF8StringByName("scope"_ns, mOrigin + "*"_ns);
 | |
|       NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|       rv = stmt->Execute();
 | |
|       NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|       // No need to selectively clear mOriginsHavingData here.  That hashtable
 | |
|       // only prevents preload for scopes with no data.  Leaving a false record
 | |
|       // in it has a negligible effect on performance.
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     case opClearMatchingOriginAttributes: {
 | |
|       MOZ_ASSERT(!NS_IsMainThread());
 | |
| 
 | |
|       // Register the ORIGIN_ATTRS_PATTERN_MATCH function, initialized with the
 | |
|       // pattern
 | |
|       nsCOMPtr<mozIStorageFunction> patternMatchFunction(
 | |
|           new OriginAttrsPatternMatchSQLFunction(mOriginPattern));
 | |
| 
 | |
|       rv = aThread->mWorkerConnection->CreateFunction(
 | |
|           "ORIGIN_ATTRS_PATTERN_MATCH"_ns, 1, patternMatchFunction);
 | |
|       NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|       nsCOMPtr<mozIStorageStatement> stmt =
 | |
|           aThread->mWorkerStatements.GetCachedStatement(
 | |
|               "DELETE FROM webappsstore2"
 | |
|               " WHERE ORIGIN_ATTRS_PATTERN_MATCH(originAttributes)");
 | |
| 
 | |
|       if (stmt) {
 | |
|         mozStorageStatementScoper scope(stmt);
 | |
|         rv = stmt->Execute();
 | |
|       } else {
 | |
|         rv = NS_ERROR_UNEXPECTED;
 | |
|       }
 | |
| 
 | |
|       // Always remove the function
 | |
|       aThread->mWorkerConnection->RemoveFunction(
 | |
|           "ORIGIN_ATTRS_PATTERN_MATCH"_ns);
 | |
| 
 | |
|       NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|       // No need to selectively clear mOriginsHavingData here.  That hashtable
 | |
|       // only prevents preload for scopes with no data.  Leaving a false record
 | |
|       // in it has a negligible effect on performance.
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     default:
 | |
|       NS_ERROR("Unknown task type");
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void StorageDBThread::DBOperation::Finalize(nsresult aRv) {
 | |
|   switch (mType) {
 | |
|     case opPreloadUrgent:
 | |
|     case opPreload:
 | |
|       if (NS_FAILED(aRv)) {
 | |
|         // When we are here, something failed when loading from the database.
 | |
|         // Notify that the storage is loaded to prevent deadlock of the main
 | |
|         // thread, even though it is actually empty or incomplete.
 | |
|         NS_WARNING("Failed to preload localStorage");
 | |
|       }
 | |
| 
 | |
|       mCache->LoadDone(aRv);
 | |
|       break;
 | |
| 
 | |
|     case opGetUsage:
 | |
|       if (NS_FAILED(aRv)) {
 | |
|         mUsage->LoadUsage(0);
 | |
|       }
 | |
| 
 | |
|       break;
 | |
| 
 | |
|     default:
 | |
|       if (NS_FAILED(aRv)) {
 | |
|         NS_WARNING(
 | |
|             "localStorage update/clear operation failed,"
 | |
|             " data may not persist or clean up");
 | |
|       }
 | |
| 
 | |
|       break;
 | |
|   }
 | |
| }
 | |
| 
 | |
| // StorageDBThread::PendingOperations
 | |
| 
 | |
| StorageDBThread::PendingOperations::PendingOperations()
 | |
|     : mFlushFailureCount(0) {}
 | |
| 
 | |
| bool StorageDBThread::PendingOperations::HasTasks() const {
 | |
|   return !!mUpdates.Count() || !!mClears.Count();
 | |
| }
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| bool OriginPatternMatches(const nsACString& aOriginSuffix,
 | |
|                           const OriginAttributesPattern& aPattern) {
 | |
|   OriginAttributes oa;
 | |
|   DebugOnly<bool> rv = oa.PopulateFromSuffix(aOriginSuffix);
 | |
|   MOZ_ASSERT(rv);
 | |
|   return aPattern.Matches(oa);
 | |
| }
 | |
| 
 | |
| }  // namespace
 | |
| 
 | |
| bool StorageDBThread::PendingOperations::CheckForCoalesceOpportunity(
 | |
|     DBOperation* aNewOp, DBOperation::OperationType aPendingType,
 | |
|     DBOperation::OperationType aNewType) {
 | |
|   if (aNewOp->Type() != aNewType) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   StorageDBThread::DBOperation* pendingTask;
 | |
|   if (!mUpdates.Get(aNewOp->Target(), &pendingTask)) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (pendingTask->Type() != aPendingType) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void StorageDBThread::PendingOperations::Add(
 | |
|     UniquePtr<StorageDBThread::DBOperation> aOperation) {
 | |
|   // Optimize: when a key to remove has never been written to disk
 | |
|   // just bypass this operation.  A key is new when an operation scheduled
 | |
|   // to write it to the database is of type opAddItem.
 | |
|   if (CheckForCoalesceOpportunity(aOperation.get(), DBOperation::opAddItem,
 | |
|                                   DBOperation::opRemoveItem)) {
 | |
|     mUpdates.Remove(aOperation->Target());
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Optimize: when changing a key that is new and has never been
 | |
|   // written to disk, keep type of the operation to store it at opAddItem.
 | |
|   // This allows optimization to just forget adding a new key when
 | |
|   // it is removed from the storage before flush.
 | |
|   if (CheckForCoalesceOpportunity(aOperation.get(), DBOperation::opAddItem,
 | |
|                                   DBOperation::opUpdateItem)) {
 | |
|     aOperation->mType = DBOperation::opAddItem;
 | |
|   }
 | |
| 
 | |
|   // Optimize: to prevent lose of remove operation on a key when doing
 | |
|   // remove/set/remove on a previously existing key we have to change
 | |
|   // opAddItem to opUpdateItem on the new operation when there is opRemoveItem
 | |
|   // pending for the key.
 | |
|   if (CheckForCoalesceOpportunity(aOperation.get(), DBOperation::opRemoveItem,
 | |
|                                   DBOperation::opAddItem)) {
 | |
|     aOperation->mType = DBOperation::opUpdateItem;
 | |
|   }
 | |
| 
 | |
|   switch (aOperation->Type()) {
 | |
|       // Operations on single keys
 | |
| 
 | |
|     case DBOperation::opAddItem:
 | |
|     case DBOperation::opUpdateItem:
 | |
|     case DBOperation::opRemoveItem:
 | |
|       // Override any existing operation for the target (=scope+key).
 | |
|       mUpdates.InsertOrUpdate(aOperation->Target(), std::move(aOperation));
 | |
|       break;
 | |
| 
 | |
|       // Clear operations
 | |
| 
 | |
|     case DBOperation::opClear:
 | |
|     case DBOperation::opClearMatchingOrigin:
 | |
|     case DBOperation::opClearMatchingOriginAttributes:
 | |
|       // Drop all update (insert/remove) operations for equivavelent or matching
 | |
|       // scope.  We do this as an optimization as well as a must based on the
 | |
|       // logic, if we would not delete the update tasks, changes would have been
 | |
|       // stored to the database after clear operations have been executed.
 | |
|       for (auto iter = mUpdates.Iter(); !iter.Done(); iter.Next()) {
 | |
|         const auto& pendingTask = iter.Data();
 | |
| 
 | |
|         if (aOperation->Type() == DBOperation::opClear &&
 | |
|             (pendingTask->OriginNoSuffix() != aOperation->OriginNoSuffix() ||
 | |
|              pendingTask->OriginSuffix() != aOperation->OriginSuffix())) {
 | |
|           continue;
 | |
|         }
 | |
| 
 | |
|         if (aOperation->Type() == DBOperation::opClearMatchingOrigin &&
 | |
|             !StringBeginsWith(pendingTask->OriginNoSuffix(),
 | |
|                               aOperation->Origin())) {
 | |
|           continue;
 | |
|         }
 | |
| 
 | |
|         if (aOperation->Type() ==
 | |
|                 DBOperation::opClearMatchingOriginAttributes &&
 | |
|             !OriginPatternMatches(pendingTask->OriginSuffix(),
 | |
|                                   aOperation->OriginPattern())) {
 | |
|           continue;
 | |
|         }
 | |
| 
 | |
|         iter.Remove();
 | |
|       }
 | |
| 
 | |
|       mClears.InsertOrUpdate(aOperation->Target(), std::move(aOperation));
 | |
|       break;
 | |
| 
 | |
|     case DBOperation::opClearAll:
 | |
|       // Drop simply everything, this is a super-operation.
 | |
|       mUpdates.Clear();
 | |
|       mClears.Clear();
 | |
|       mClears.InsertOrUpdate(aOperation->Target(), std::move(aOperation));
 | |
|       break;
 | |
| 
 | |
|     default:
 | |
|       MOZ_ASSERT(false);
 | |
|       break;
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool StorageDBThread::PendingOperations::Prepare() {
 | |
|   // Called under the lock
 | |
| 
 | |
|   // First collect clear operations and then updates, we can
 | |
|   // do this since whenever a clear operation for a scope is
 | |
|   // scheduled, we drop all updates matching that scope. So,
 | |
|   // all scope-related update operations we have here now were
 | |
|   // scheduled after the clear operations.
 | |
|   for (auto iter = mClears.Iter(); !iter.Done(); iter.Next()) {
 | |
|     mExecList.AppendElement(std::move(iter.Data()));
 | |
|   }
 | |
|   mClears.Clear();
 | |
| 
 | |
|   for (auto iter = mUpdates.Iter(); !iter.Done(); iter.Next()) {
 | |
|     mExecList.AppendElement(std::move(iter.Data()));
 | |
|   }
 | |
|   mUpdates.Clear();
 | |
| 
 | |
|   return !!mExecList.Length();
 | |
| }
 | |
| 
 | |
| nsresult StorageDBThread::PendingOperations::Execute(StorageDBThread* aThread) {
 | |
|   // Called outside the lock
 | |
| 
 | |
|   mozStorageTransaction transaction(aThread->mWorkerConnection, false);
 | |
| 
 | |
|   nsresult rv = transaction.Start();
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   for (uint32_t i = 0; i < mExecList.Length(); ++i) {
 | |
|     const auto& task = mExecList[i];
 | |
|     rv = task->Perform(aThread);
 | |
|     if (NS_FAILED(rv)) {
 | |
|       return rv;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   rv = transaction.Commit();
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| bool StorageDBThread::PendingOperations::Finalize(nsresult aRv) {
 | |
|   // Called under the lock
 | |
| 
 | |
|   // The list is kept on a failure to retry it
 | |
|   if (NS_FAILED(aRv)) {
 | |
|     // XXX Followup: we may try to reopen the database and flush these
 | |
|     // pending tasks, however testing showed that even though I/O is actually
 | |
|     // broken some amount of operations is left in sqlite+system buffers and
 | |
|     // seems like successfully flushed to disk.
 | |
|     // Tested by removing a flash card and disconnecting from network while
 | |
|     // using a network drive on Windows system.
 | |
|     NS_WARNING("Flush operation on localStorage database failed");
 | |
| 
 | |
|     ++mFlushFailureCount;
 | |
| 
 | |
|     return mFlushFailureCount >= 5;
 | |
|   }
 | |
| 
 | |
|   mFlushFailureCount = 0;
 | |
|   mExecList.Clear();
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| bool FindPendingClearForOrigin(
 | |
|     const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix,
 | |
|     StorageDBThread::DBOperation* aPendingOperation) {
 | |
|   if (aPendingOperation->Type() == StorageDBThread::DBOperation::opClearAll) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   if (aPendingOperation->Type() == StorageDBThread::DBOperation::opClear &&
 | |
|       aOriginNoSuffix == aPendingOperation->OriginNoSuffix() &&
 | |
|       aOriginSuffix == aPendingOperation->OriginSuffix()) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   if (aPendingOperation->Type() ==
 | |
|           StorageDBThread::DBOperation::opClearMatchingOrigin &&
 | |
|       StringBeginsWith(aOriginNoSuffix, aPendingOperation->Origin())) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   if (aPendingOperation->Type() ==
 | |
|           StorageDBThread::DBOperation::opClearMatchingOriginAttributes &&
 | |
|       OriginPatternMatches(aOriginSuffix, aPendingOperation->OriginPattern())) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| }  // namespace
 | |
| 
 | |
| bool StorageDBThread::PendingOperations::IsOriginClearPending(
 | |
|     const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix) const {
 | |
|   // Called under the lock
 | |
| 
 | |
|   for (const auto& clear : mClears.Values()) {
 | |
|     if (FindPendingClearForOrigin(aOriginSuffix, aOriginNoSuffix,
 | |
|                                   clear.get())) {
 | |
|       return true;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   for (uint32_t i = 0; i < mExecList.Length(); ++i) {
 | |
|     if (FindPendingClearForOrigin(aOriginSuffix, aOriginNoSuffix,
 | |
|                                   mExecList[i].get())) {
 | |
|       return true;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| bool FindPendingUpdateForOrigin(
 | |
|     const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix,
 | |
|     StorageDBThread::DBOperation* aPendingOperation) {
 | |
|   if ((aPendingOperation->Type() == StorageDBThread::DBOperation::opAddItem ||
 | |
|        aPendingOperation->Type() ==
 | |
|            StorageDBThread::DBOperation::opUpdateItem ||
 | |
|        aPendingOperation->Type() ==
 | |
|            StorageDBThread::DBOperation::opRemoveItem) &&
 | |
|       aOriginNoSuffix == aPendingOperation->OriginNoSuffix() &&
 | |
|       aOriginSuffix == aPendingOperation->OriginSuffix()) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| }  // namespace
 | |
| 
 | |
| bool StorageDBThread::PendingOperations::IsOriginUpdatePending(
 | |
|     const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix) const {
 | |
|   // Called under the lock
 | |
| 
 | |
|   for (const auto& update : mUpdates.Values()) {
 | |
|     if (FindPendingUpdateForOrigin(aOriginSuffix, aOriginNoSuffix,
 | |
|                                    update.get())) {
 | |
|       return true;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   for (uint32_t i = 0; i < mExecList.Length(); ++i) {
 | |
|     if (FindPendingUpdateForOrigin(aOriginSuffix, aOriginNoSuffix,
 | |
|                                    mExecList[i].get())) {
 | |
|       return true;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| nsresult StorageDBThread::InitHelper::SyncDispatchAndReturnProfilePath(
 | |
|     nsAString& aProfilePath) {
 | |
|   ::mozilla::ipc::AssertIsOnBackgroundThread();
 | |
| 
 | |
|   MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
 | |
| 
 | |
|   mozilla::MutexAutoLock autolock(mMutex);
 | |
|   while (mWaiting) {
 | |
|     mCondVar.Wait();
 | |
|   }
 | |
| 
 | |
|   if (NS_WARN_IF(NS_FAILED(mMainThreadResultCode))) {
 | |
|     return mMainThreadResultCode;
 | |
|   }
 | |
| 
 | |
|   aProfilePath = mProfilePath;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| StorageDBThread::InitHelper::Run() {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   nsresult rv = GetProfilePath(mProfilePath);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     mMainThreadResultCode = rv;
 | |
|   }
 | |
| 
 | |
|   mozilla::MutexAutoLock lock(mMutex);
 | |
|   MOZ_ASSERT(mWaiting);
 | |
| 
 | |
|   mWaiting = false;
 | |
|   mCondVar.Notify();
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| StorageDBThread::NoteBackgroundThreadRunnable::Run() {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   StorageObserver* observer = StorageObserver::Self();
 | |
|   MOZ_ASSERT(observer);
 | |
| 
 | |
|   observer->NoteBackgroundThread(mPrivateBrowsingId, mOwningThread);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| StorageDBThread::ShutdownRunnable::Run() {
 | |
|   if (NS_IsMainThread()) {
 | |
|     mDone = true;
 | |
| 
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   ::mozilla::ipc::AssertIsOnBackgroundThread();
 | |
|   MOZ_RELEASE_ASSERT(mPrivateBrowsingId < kPrivateBrowsingIdCount);
 | |
| 
 | |
|   StorageDBThread*& storageThread = sStorageThread[mPrivateBrowsingId];
 | |
|   if (storageThread) {
 | |
|     sStorageThreadDown[mPrivateBrowsingId] = true;
 | |
| 
 | |
|     storageThread->Shutdown();
 | |
| 
 | |
|     delete storageThread;
 | |
|     storageThread = nullptr;
 | |
|   }
 | |
| 
 | |
|   MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| }  // namespace mozilla::dom
 | 
