LCOV - code coverage report
Current view: top level - dom/storage - StorageDBThread.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 220 653 33.7 %
Date: 2017-07-14 16:53:18 Functions: 27 61 44.3 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
       2             : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
       3             : /* This Source Code Form is subject to the terms of the Mozilla Public
       4             :  * License, v. 2.0. If a copy of the MPL was not distributed with this
       5             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
       6             : 
       7             : #include "StorageDBThread.h"
       8             : #include "StorageDBUpdater.h"
       9             : #include "StorageUtils.h"
      10             : #include "LocalStorageCache.h"
      11             : #include "LocalStorageManager.h"
      12             : 
      13             : #include "nsIEffectiveTLDService.h"
      14             : #include "nsDirectoryServiceUtils.h"
      15             : #include "nsAppDirectoryServiceDefs.h"
      16             : #include "nsThreadUtils.h"
      17             : #include "nsProxyRelease.h"
      18             : #include "mozStorageCID.h"
      19             : #include "mozStorageHelper.h"
      20             : #include "mozIStorageService.h"
      21             : #include "mozIStorageBindingParamsArray.h"
      22             : #include "mozIStorageBindingParams.h"
      23             : #include "mozIStorageValueArray.h"
      24             : #include "mozIStorageFunction.h"
      25             : #include "mozilla/BasePrincipal.h"
      26             : #include "nsIObserverService.h"
      27             : #include "nsVariant.h"
      28             : #include "mozilla/IOInterposer.h"
      29             : #include "mozilla/Services.h"
      30             : #include "mozilla/Tokenizer.h"
      31             : #include "GeckoProfiler.h"
      32             : 
      33             : // How long we collect write oprerations
      34             : // before they are flushed to the database
      35             : // In milliseconds.
      36             : #define FLUSHING_INTERVAL_MS 5000
      37             : 
      38             : // Write Ahead Log's maximum size is 512KB
      39             : #define MAX_WAL_SIZE_BYTES 512 * 1024
      40             : 
      41             : // Current version of the database schema
      42             : #define CURRENT_SCHEMA_VERSION 2
      43             : 
      44             : namespace mozilla {
      45             : namespace dom {
      46             : 
      47             : using namespace StorageUtils;
      48             : 
      49             : namespace { // anon
      50             : 
      51             : // This is only a compatibility code for schema version 0.  Returns the 'scope'
      52             : // key in the schema version 0 format for the scope column.
      53             : nsCString
      54           0 : Scheme0Scope(LocalStorageCacheBridge* aCache)
      55             : {
      56           0 :   nsCString result;
      57             : 
      58           0 :   nsCString suffix = aCache->OriginSuffix();
      59             : 
      60           0 :   OriginAttributes oa;
      61           0 :   if (!suffix.IsEmpty()) {
      62           0 :     DebugOnly<bool> success = oa.PopulateFromSuffix(suffix);
      63           0 :     MOZ_ASSERT(success);
      64             :   }
      65             : 
      66           0 :   if (oa.mAppId != nsIScriptSecurityManager::NO_APP_ID ||
      67           0 :       oa.mInIsolatedMozBrowser) {
      68           0 :     result.AppendInt(oa.mAppId);
      69           0 :     result.Append(':');
      70           0 :     result.Append(oa.mInIsolatedMozBrowser ? 't' : 'f');
      71           0 :     result.Append(':');
      72             :   }
      73             : 
      74             :   // If there is more than just appid and/or inbrowser stored in origin
      75             :   // attributes, put it to the schema 0 scope as well.  We must do that
      76             :   // to keep the scope column unique (same resolution as schema 1 has
      77             :   // with originAttributes and originKey columns) so that switch between
      78             :   // schema 1 and 0 always works in both ways.
      79           0 :   nsAutoCString remaining;
      80           0 :   oa.mAppId = 0;
      81           0 :   oa.mInIsolatedMozBrowser = false;
      82           0 :   oa.CreateSuffix(remaining);
      83           0 :   if (!remaining.IsEmpty()) {
      84           0 :     MOZ_ASSERT(!suffix.IsEmpty());
      85             : 
      86           0 :     if (result.IsEmpty()) {
      87             :       // Must contain the old prefix, otherwise we won't search for the whole
      88             :       // origin attributes suffix.
      89           0 :       result.Append(NS_LITERAL_CSTRING("0:f:"));
      90             :     }
      91             :     // Append the whole origin attributes suffix despite we have already stored
      92             :     // appid and inbrowser.  We are only looking for it when the scope string
      93             :     // starts with "$appid:$inbrowser:" (with whatever valid values).
      94             :     //
      95             :     // The OriginAttributes suffix is a string in a form like:
      96             :     // "^addonId=101&userContextId=5" and it's ensured it always starts with '^'
      97             :     // and never contains ':'.  See OriginAttributes::CreateSuffix.
      98           0 :     result.Append(suffix);
      99           0 :     result.Append(':');
     100             :   }
     101             : 
     102           0 :   result.Append(aCache->OriginNoSuffix());
     103             : 
     104           0 :   return result;
     105             : }
     106             : 
     107             : } // anon
     108             : 
     109             : 
     110           2 : StorageDBBridge::StorageDBBridge()
     111             : {
     112           2 : }
     113             : 
     114             : 
     115           1 : StorageDBThread::StorageDBThread()
     116             :   : mThread(nullptr)
     117           1 :   , mThreadObserver(new ThreadObserver())
     118             :   , mStopIOThread(false)
     119             :   , mWALModeEnabled(false)
     120             :   , mDBReady(false)
     121             :   , mStatus(NS_OK)
     122             :   , mWorkerStatements(mWorkerConnection)
     123             :   , mReaderStatements(mReaderConnection)
     124             :   , mDirtyEpoch(0)
     125             :   , mFlushImmediately(false)
     126           2 :   , mPriorityCounter(0)
     127             : {
     128           1 : }
     129             : 
     130             : nsresult
     131           1 : StorageDBThread::Init()
     132             : {
     133             :   nsresult rv;
     134             : 
     135             :   // Need to determine location on the main thread, since
     136             :   // NS_GetSpecialDirectory access the atom table that can
     137             :   // be accessed only on the main thread.
     138           1 :   rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
     139           2 :                               getter_AddRefs(mDatabaseFile));
     140           1 :   NS_ENSURE_SUCCESS(rv, rv);
     141             : 
     142           1 :   rv = mDatabaseFile->Append(NS_LITERAL_STRING("webappsstore.sqlite"));
     143           1 :   NS_ENSURE_SUCCESS(rv, rv);
     144             : 
     145             :   // Ensure mozIStorageService init on the main thread first.
     146             :   nsCOMPtr<mozIStorageService> service =
     147           2 :     do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
     148           1 :   NS_ENSURE_SUCCESS(rv, rv);
     149             : 
     150             :   // Need to keep the lock to avoid setting mThread later then
     151             :   // the thread body executes.
     152           2 :   MonitorAutoLock monitor(mThreadObserver->GetMonitor());
     153             : 
     154           1 :   mThread = PR_CreateThread(PR_USER_THREAD, &StorageDBThread::ThreadFunc, this,
     155             :                             PR_PRIORITY_LOW, PR_GLOBAL_THREAD,
     156             :                             PR_JOINABLE_THREAD, 262144);
     157           1 :   if (!mThread) {
     158           0 :     return NS_ERROR_OUT_OF_MEMORY;
     159             :   }
     160             : 
     161           1 :   return NS_OK;
     162             : }
     163             : 
     164             : nsresult
     165           0 : StorageDBThread::Shutdown()
     166             : {
     167           0 :   if (!mThread) {
     168           0 :     return NS_ERROR_NOT_INITIALIZED;
     169             :   }
     170             : 
     171           0 :   Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_SHUTDOWN_DATABASE_MS> timer;
     172             : 
     173             :   {
     174           0 :     MonitorAutoLock monitor(mThreadObserver->GetMonitor());
     175             : 
     176             :     // After we stop, no other operations can be accepted
     177           0 :     mFlushImmediately = true;
     178           0 :     mStopIOThread = true;
     179           0 :     monitor.Notify();
     180             :   }
     181             : 
     182           0 :   PR_JoinThread(mThread);
     183           0 :   mThread = nullptr;
     184             : 
     185           0 :   return mStatus;
     186             : }
     187             : 
     188             : void
     189           0 : StorageDBThread::SyncPreload(LocalStorageCacheBridge* aCache, bool aForceSync)
     190             : {
     191           0 :   AUTO_PROFILER_LABEL("StorageDBThread::SyncPreload", STORAGE);
     192           0 :   if (!aForceSync && aCache->LoadedCount()) {
     193             :     // Preload already started for this cache, just wait for it to finish.
     194             :     // LoadWait will exit after LoadDone on the cache has been called.
     195           0 :     SetHigherPriority();
     196           0 :     aCache->LoadWait();
     197           0 :     SetDefaultPriority();
     198           0 :     return;
     199             :   }
     200             : 
     201             :   // Bypass sync load when an update is pending in the queue to write, we would
     202             :   // get incosistent data in the cache.  Also don't allow sync main-thread
     203             :   // preload when DB open and init is still pending on the background thread.
     204           0 :   if (mDBReady && mWALModeEnabled) {
     205             :     bool pendingTasks;
     206             :     {
     207           0 :       MonitorAutoLock monitor(mThreadObserver->GetMonitor());
     208           0 :       pendingTasks = mPendingTasks.IsOriginUpdatePending(aCache->OriginSuffix(), aCache->OriginNoSuffix()) ||
     209           0 :                      mPendingTasks.IsOriginClearPending(aCache->OriginSuffix(), aCache->OriginNoSuffix());
     210             :     }
     211             : 
     212           0 :     if (!pendingTasks) {
     213             :       // WAL is enabled, thus do the load synchronously on the main thread.
     214           0 :       DBOperation preload(DBOperation::opPreload, aCache);
     215           0 :       preload.PerformAndFinalize(this);
     216           0 :       return;
     217             :     }
     218             :   }
     219             : 
     220             :   // Need to go asynchronously since WAL is not allowed or scheduled updates
     221             :   // need to be flushed first.
     222             :   // Schedule preload for this cache as the first operation.
     223           0 :   nsresult rv = InsertDBOp(new DBOperation(DBOperation::opPreloadUrgent, aCache));
     224             : 
     225             :   // LoadWait exits after LoadDone of the cache has been called.
     226           0 :   if (NS_SUCCEEDED(rv)) {
     227           0 :     aCache->LoadWait();
     228             :   }
     229             : }
     230             : 
     231             : void
     232           0 : StorageDBThread::AsyncFlush()
     233             : {
     234           0 :   MonitorAutoLock monitor(mThreadObserver->GetMonitor());
     235           0 :   mFlushImmediately = true;
     236           0 :   monitor.Notify();
     237           0 : }
     238             : 
     239             : bool
     240           0 : StorageDBThread::ShouldPreloadOrigin(const nsACString& aOrigin)
     241             : {
     242           0 :   MonitorAutoLock monitor(mThreadObserver->GetMonitor());
     243           0 :   return mOriginsHavingData.Contains(aOrigin);
     244             : }
     245             : 
     246             : void
     247           0 : StorageDBThread::GetOriginsHavingData(InfallibleTArray<nsCString>* aOrigins)
     248             : {
     249           0 :   MonitorAutoLock monitor(mThreadObserver->GetMonitor());
     250           0 :   for (auto iter = mOriginsHavingData.Iter(); !iter.Done(); iter.Next()) {
     251           0 :     aOrigins->AppendElement(iter.Get()->GetKey());
     252             :   }
     253           0 : }
     254             : 
     255             : nsresult
     256           3 : StorageDBThread::InsertDBOp(StorageDBThread::DBOperation* aOperation)
     257             : {
     258           6 :   MonitorAutoLock monitor(mThreadObserver->GetMonitor());
     259             : 
     260             :   // Sentinel to don't forget to delete the operation when we exit early.
     261           6 :   nsAutoPtr<StorageDBThread::DBOperation> opScope(aOperation);
     262             : 
     263           3 :   if (NS_FAILED(mStatus)) {
     264           0 :     MonitorAutoUnlock unlock(mThreadObserver->GetMonitor());
     265           0 :     aOperation->Finalize(mStatus);
     266           0 :     return mStatus;
     267             :   }
     268             : 
     269           3 :   if (mStopIOThread) {
     270             :     // Thread use after shutdown demanded.
     271           0 :     MOZ_ASSERT(false);
     272             :     return NS_ERROR_NOT_INITIALIZED;
     273             :   }
     274             : 
     275           3 :   switch (aOperation->Type()) {
     276             :   case DBOperation::opPreload:
     277             :   case DBOperation::opPreloadUrgent:
     278           2 :     if (mPendingTasks.IsOriginUpdatePending(aOperation->OriginSuffix(), aOperation->OriginNoSuffix())) {
     279             :       // If there is a pending update operation for the scope first do the flush
     280             :       // before we preload the cache.  This may happen in an extremely rare case
     281             :       // when a child process throws away its cache before flush on the parent
     282             :       // has finished.  If we would preloaded the cache as a priority operation
     283             :       // before the pending flush, we would have got an inconsistent cache
     284             :       // content.
     285           0 :       mFlushImmediately = true;
     286           2 :     } else if (mPendingTasks.IsOriginClearPending(aOperation->OriginSuffix(), aOperation->OriginNoSuffix())) {
     287             :       // The scope is scheduled to be cleared, so just quickly load as empty.
     288             :       // We need to do this to prevent load of the DB data before the scope has
     289             :       // actually been cleared from the database.  Preloads are processed
     290             :       // immediately before update and clear operations on the database that are
     291             :       // flushed periodically in batches.
     292           0 :       MonitorAutoUnlock unlock(mThreadObserver->GetMonitor());
     293           0 :       aOperation->Finalize(NS_OK);
     294           0 :       return NS_OK;
     295             :     }
     296             :     MOZ_FALLTHROUGH;
     297             : 
     298             :   case DBOperation::opGetUsage:
     299           3 :     if (aOperation->Type() == DBOperation::opPreloadUrgent) {
     300           0 :       SetHigherPriority(); // Dropped back after urgent preload execution
     301           0 :       mPreloads.InsertElementAt(0, aOperation);
     302             :     } else {
     303           3 :       mPreloads.AppendElement(aOperation);
     304             :     }
     305             : 
     306             :     // DB operation adopted, don't delete it.
     307           3 :     opScope.forget();
     308             : 
     309             :     // Immediately start executing this.
     310           3 :     monitor.Notify();
     311           3 :     break;
     312             : 
     313             :   default:
     314             :     // Update operations are first collected, coalesced and then flushed
     315             :     // after a short time.
     316           0 :     mPendingTasks.Add(aOperation);
     317             : 
     318             :     // DB operation adopted, don't delete it.
     319           0 :     opScope.forget();
     320             : 
     321           0 :     ScheduleFlush();
     322           0 :     break;
     323             :   }
     324             : 
     325           3 :   return NS_OK;
     326             : }
     327             : 
     328             : void
     329           0 : StorageDBThread::SetHigherPriority()
     330             : {
     331           0 :   ++mPriorityCounter;
     332           0 :   PR_SetThreadPriority(mThread, PR_PRIORITY_URGENT);
     333           0 : }
     334             : 
     335             : void
     336           0 : StorageDBThread::SetDefaultPriority()
     337             : {
     338           0 :   if (--mPriorityCounter <= 0) {
     339           0 :     PR_SetThreadPriority(mThread, PR_PRIORITY_LOW);
     340             :   }
     341           0 : }
     342             : 
     343             : void
     344           1 : StorageDBThread::ThreadFunc(void* aArg)
     345             : {
     346           1 :   AutoProfilerRegisterThread registerThread("localStorage DB");
     347           1 :   NS_SetCurrentThreadName("localStorage DB");
     348           1 :   mozilla::IOInterposer::RegisterCurrentThread();
     349             : 
     350           1 :   StorageDBThread* thread = static_cast<StorageDBThread*>(aArg);
     351           1 :   thread->ThreadFunc();
     352           0 :   mozilla::IOInterposer::UnregisterCurrentThread();
     353           0 : }
     354             : 
     355             : void
     356           1 : StorageDBThread::ThreadFunc()
     357             : {
     358           1 :   nsresult rv = InitDatabase();
     359             : 
     360           1 :   MonitorAutoLock lockMonitor(mThreadObserver->GetMonitor());
     361             : 
     362           1 :   if (NS_FAILED(rv)) {
     363           0 :     mStatus = rv;
     364           0 :     mStopIOThread = true;
     365           0 :     return;
     366             :   }
     367             : 
     368             :   // Create an nsIThread for the current PRThread, so we can observe runnables
     369             :   // dispatched to it.
     370           1 :   nsCOMPtr<nsIThread> thread = NS_GetCurrentThread();
     371           1 :   nsCOMPtr<nsIThreadInternal> threadInternal = do_QueryInterface(thread);
     372           1 :   MOZ_ASSERT(threadInternal); // Should always succeed.
     373           1 :   threadInternal->SetObserver(mThreadObserver);
     374             : 
     375           7 :   while (MOZ_LIKELY(!mStopIOThread || mPreloads.Length() ||
     376             :                     mPendingTasks.HasTasks() ||
     377             :                     mThreadObserver->HasPendingEvents())) {
     378             :     // Process xpcom events first.
     379           4 :     while (MOZ_UNLIKELY(mThreadObserver->HasPendingEvents())) {
     380           0 :       mThreadObserver->ClearPendingEvents();
     381           0 :       MonitorAutoUnlock unlock(mThreadObserver->GetMonitor());
     382             :       bool processedEvent;
     383           0 :       do {
     384           0 :         rv = thread->ProcessNextEvent(false, &processedEvent);
     385           0 :       } while (NS_SUCCEEDED(rv) && processedEvent);
     386             :     }
     387             : 
     388           4 :     if (MOZ_UNLIKELY(TimeUntilFlush() == 0)) {
     389             :       // Flush time is up or flush has been forced, do it now.
     390           0 :       UnscheduleFlush();
     391           0 :       if (mPendingTasks.Prepare()) {
     392             :         {
     393           0 :           MonitorAutoUnlock unlockMonitor(mThreadObserver->GetMonitor());
     394           0 :           rv = mPendingTasks.Execute(this);
     395             :         }
     396             : 
     397           0 :         if (!mPendingTasks.Finalize(rv)) {
     398           0 :           mStatus = rv;
     399           0 :           NS_WARNING("localStorage DB access broken");
     400             :         }
     401             :       }
     402           0 :       NotifyFlushCompletion();
     403           4 :     } else if (MOZ_LIKELY(mPreloads.Length())) {
     404           6 :       nsAutoPtr<DBOperation> op(mPreloads[0]);
     405           3 :       mPreloads.RemoveElementAt(0);
     406             :       {
     407           6 :         MonitorAutoUnlock unlockMonitor(mThreadObserver->GetMonitor());
     408           3 :         op->PerformAndFinalize(this);
     409             :       }
     410             : 
     411           3 :       if (op->Type() == DBOperation::opPreloadUrgent) {
     412           0 :         SetDefaultPriority(); // urgent preload unscheduled
     413             :       }
     414           1 :     } else if (MOZ_UNLIKELY(!mStopIOThread)) {
     415           1 :       lockMonitor.Wait(TimeUntilFlush());
     416             :     }
     417             :   } // thread loop
     418             : 
     419           0 :   mStatus = ShutdownDatabase();
     420             : 
     421           0 :   if (threadInternal) {
     422           0 :     threadInternal->SetObserver(nullptr);
     423             :   }
     424             : }
     425             : 
     426             : 
     427           5 : NS_IMPL_ISUPPORTS(StorageDBThread::ThreadObserver, nsIThreadObserver)
     428             : 
     429             : NS_IMETHODIMP
     430           0 : StorageDBThread::ThreadObserver::OnDispatchedEvent(nsIThreadInternal* aThread)
     431             : {
     432           0 :   MonitorAutoLock lock(mMonitor);
     433           0 :   mHasPendingEvents = true;
     434           0 :   lock.Notify();
     435           0 :   return NS_OK;
     436             : }
     437             : 
     438             : NS_IMETHODIMP
     439           0 : StorageDBThread::ThreadObserver::OnProcessNextEvent(nsIThreadInternal* aThread,
     440             :                                                     bool mayWait)
     441             : {
     442           0 :   return NS_OK;
     443             : }
     444             : 
     445             : NS_IMETHODIMP
     446           0 : StorageDBThread::ThreadObserver::AfterProcessNextEvent(nsIThreadInternal* aThread,
     447             :                                                        bool eventWasProcessed)
     448             : {
     449           0 :   return NS_OK;
     450             : }
     451             : 
     452             : nsresult
     453           1 : StorageDBThread::OpenDatabaseConnection()
     454             : {
     455             :   nsresult rv;
     456             : 
     457           1 :   MOZ_ASSERT(!NS_IsMainThread());
     458             : 
     459             :   nsCOMPtr<mozIStorageService> service
     460           2 :       = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
     461           1 :   NS_ENSURE_SUCCESS(rv, rv);
     462             : 
     463           1 :   rv = service->OpenUnsharedDatabase(mDatabaseFile, getter_AddRefs(mWorkerConnection));
     464           1 :   if (rv == NS_ERROR_FILE_CORRUPTED) {
     465             :     // delete the db and try opening again
     466           0 :     rv = mDatabaseFile->Remove(false);
     467           0 :     NS_ENSURE_SUCCESS(rv, rv);
     468           0 :     rv = service->OpenUnsharedDatabase(mDatabaseFile, getter_AddRefs(mWorkerConnection));
     469             :   }
     470           1 :   NS_ENSURE_SUCCESS(rv, rv);
     471             : 
     472           1 :   return NS_OK;
     473             : }
     474             : 
     475             : nsresult
     476           1 : StorageDBThread::OpenAndUpdateDatabase()
     477             : {
     478             :   nsresult rv;
     479             : 
     480             :   // Here we are on the worker thread. This opens the worker connection.
     481           1 :   MOZ_ASSERT(!NS_IsMainThread());
     482             : 
     483           1 :   rv = OpenDatabaseConnection();
     484           1 :   NS_ENSURE_SUCCESS(rv, rv);
     485             : 
     486           1 :   rv = TryJournalMode();
     487           1 :   NS_ENSURE_SUCCESS(rv, rv);
     488             : 
     489           1 :   return NS_OK;
     490             : }
     491             : 
     492             : nsresult
     493           1 : StorageDBThread::InitDatabase()
     494             : {
     495             :   nsresult rv;
     496             : 
     497             :   // Here we are on the worker thread. This opens the worker connection.
     498           1 :   MOZ_ASSERT(!NS_IsMainThread());
     499             : 
     500           1 :   rv = OpenAndUpdateDatabase();
     501           1 :   NS_ENSURE_SUCCESS(rv, rv);
     502             : 
     503           1 :   rv = StorageDBUpdater::Update(mWorkerConnection);
     504           1 :   if (NS_FAILED(rv)) {
     505             :     // Update has failed, rather throw the database away and try
     506             :     // opening and setting it up again.
     507           0 :     rv = mWorkerConnection->Close();
     508           0 :     mWorkerConnection = nullptr;
     509           0 :     NS_ENSURE_SUCCESS(rv, rv);
     510             : 
     511           0 :     rv = mDatabaseFile->Remove(false);
     512           0 :     NS_ENSURE_SUCCESS(rv, rv);
     513             : 
     514           0 :     rv = OpenAndUpdateDatabase();
     515           0 :     NS_ENSURE_SUCCESS(rv, rv);
     516             :   }
     517             : 
     518             :   // Create a read-only clone
     519           1 :   (void)mWorkerConnection->Clone(true, getter_AddRefs(mReaderConnection));
     520           1 :   NS_ENSURE_TRUE(mReaderConnection, NS_ERROR_FAILURE);
     521             : 
     522             :   // Database open and all initiation operation are done.  Switching this flag
     523             :   // to true allow main thread to read directly from the database.  If we would
     524             :   // allow this sooner, we would have opened a window where main thread read
     525             :   // might operate on a totally broken and incosistent database.
     526           1 :   mDBReady = true;
     527             : 
     528             :   // List scopes having any stored data
     529           2 :   nsCOMPtr<mozIStorageStatement> stmt;
     530             :   // Note: result of this select must match StorageManager::CreateOrigin()
     531           4 :   rv = mWorkerConnection->CreateStatement(NS_LITERAL_CSTRING(
     532             :         "SELECT DISTINCT originAttributes || ':' || originKey FROM webappsstore2"),
     533           4 :         getter_AddRefs(stmt));
     534           1 :   NS_ENSURE_SUCCESS(rv, rv);
     535           2 :   mozStorageStatementScoper scope(stmt);
     536             : 
     537             :   bool exists;
     538           1 :   while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&exists)) && exists) {
     539           0 :     nsAutoCString foundOrigin;
     540           0 :     rv = stmt->GetUTF8String(0, foundOrigin);
     541           0 :     NS_ENSURE_SUCCESS(rv, rv);
     542             : 
     543           0 :     MonitorAutoLock monitor(mThreadObserver->GetMonitor());
     544           0 :     mOriginsHavingData.PutEntry(foundOrigin);
     545             :   }
     546             : 
     547           1 :   return NS_OK;
     548             : }
     549             : 
     550             : nsresult
     551           1 : StorageDBThread::SetJournalMode(bool aIsWal)
     552             : {
     553             :   nsresult rv;
     554             : 
     555             :   nsAutoCString stmtString(
     556           2 :     MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA journal_mode = ");
     557           1 :   if (aIsWal) {
     558           1 :     stmtString.AppendLiteral("wal");
     559             :   } else {
     560           0 :     stmtString.AppendLiteral("truncate");
     561             :   }
     562             : 
     563           2 :   nsCOMPtr<mozIStorageStatement> stmt;
     564           1 :   rv = mWorkerConnection->CreateStatement(stmtString, getter_AddRefs(stmt));
     565           1 :   NS_ENSURE_SUCCESS(rv, rv);
     566           2 :   mozStorageStatementScoper scope(stmt);
     567             : 
     568           1 :   bool hasResult = false;
     569           1 :   rv = stmt->ExecuteStep(&hasResult);
     570           1 :   NS_ENSURE_SUCCESS(rv, rv);
     571           1 :   if (!hasResult) {
     572           0 :     return NS_ERROR_FAILURE;
     573             :   }
     574             : 
     575           2 :   nsAutoCString journalMode;
     576           1 :   rv = stmt->GetUTF8String(0, journalMode);
     577           1 :   NS_ENSURE_SUCCESS(rv, rv);
     578           2 :   if ((aIsWal && !journalMode.EqualsLiteral("wal")) ||
     579           1 :       (!aIsWal && !journalMode.EqualsLiteral("truncate"))) {
     580           0 :     return NS_ERROR_FAILURE;
     581             :   }
     582             : 
     583           1 :   return NS_OK;
     584             : }
     585             : 
     586             : nsresult
     587           1 : StorageDBThread::TryJournalMode()
     588             : {
     589             :   nsresult rv;
     590             : 
     591           1 :   rv = SetJournalMode(true);
     592           1 :   if (NS_FAILED(rv)) {
     593           0 :     mWALModeEnabled = false;
     594             : 
     595           0 :     rv = SetJournalMode(false);
     596           0 :     NS_ENSURE_SUCCESS(rv, rv);
     597             :   } else {
     598           1 :     mWALModeEnabled = true;
     599             : 
     600           1 :     rv = ConfigureWALBehavior();
     601           1 :     NS_ENSURE_SUCCESS(rv, rv);
     602             :   }
     603             : 
     604           1 :   return NS_OK;
     605             : }
     606             : 
     607             : nsresult
     608           1 : StorageDBThread::ConfigureWALBehavior()
     609             : {
     610             :   // Get the DB's page size
     611           2 :   nsCOMPtr<mozIStorageStatement> stmt;
     612           4 :   nsresult rv = mWorkerConnection->CreateStatement(NS_LITERAL_CSTRING(
     613             :     MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA page_size"
     614           4 :   ), getter_AddRefs(stmt));
     615           1 :   NS_ENSURE_SUCCESS(rv, rv);
     616             : 
     617           1 :   bool hasResult = false;
     618           1 :   rv = stmt->ExecuteStep(&hasResult);
     619           1 :   NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasResult, NS_ERROR_FAILURE);
     620             : 
     621           1 :   int32_t pageSize = 0;
     622           1 :   rv = stmt->GetInt32(0, &pageSize);
     623           1 :   NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && pageSize > 0, NS_ERROR_UNEXPECTED);
     624             : 
     625             :   // Set the threshold for auto-checkpointing the WAL.
     626             :   // We don't want giant logs slowing down reads & shutdown.
     627           1 :   int32_t thresholdInPages = static_cast<int32_t>(MAX_WAL_SIZE_BYTES / pageSize);
     628           2 :   nsAutoCString thresholdPragma("PRAGMA wal_autocheckpoint = ");
     629           1 :   thresholdPragma.AppendInt(thresholdInPages);
     630           1 :   rv = mWorkerConnection->ExecuteSimpleSQL(thresholdPragma);
     631           1 :   NS_ENSURE_SUCCESS(rv, rv);
     632             : 
     633             :   // Set the maximum WAL log size to reduce footprint on mobile (large empty
     634             :   // WAL files will be truncated)
     635           2 :   nsAutoCString journalSizePragma("PRAGMA journal_size_limit = ");
     636             :   // bug 600307: mak recommends setting this to 3 times the auto-checkpoint
     637             :   // threshold
     638           1 :   journalSizePragma.AppendInt(MAX_WAL_SIZE_BYTES * 3);
     639           1 :   rv = mWorkerConnection->ExecuteSimpleSQL(journalSizePragma);
     640           1 :   NS_ENSURE_SUCCESS(rv, rv);
     641             : 
     642           1 :   return NS_OK;
     643             : }
     644             : 
     645             : nsresult
     646           0 : StorageDBThread::ShutdownDatabase()
     647             : {
     648             :   // Has to be called on the worker thread.
     649           0 :   MOZ_ASSERT(!NS_IsMainThread());
     650             : 
     651           0 :   nsresult rv = mStatus;
     652             : 
     653           0 :   mDBReady = false;
     654             : 
     655             :   // Finalize the cached statements.
     656           0 :   mReaderStatements.FinalizeStatements();
     657           0 :   mWorkerStatements.FinalizeStatements();
     658             : 
     659           0 :   if (mReaderConnection) {
     660             :     // No need to sync access to mReaderConnection since the main thread
     661             :     // is right now joining this thread, unable to execute any events.
     662           0 :     mReaderConnection->Close();
     663           0 :     mReaderConnection = nullptr;
     664             :   }
     665             : 
     666           0 :   if (mWorkerConnection) {
     667           0 :     rv = mWorkerConnection->Close();
     668           0 :     mWorkerConnection = nullptr;
     669             :   }
     670             : 
     671           0 :   return rv;
     672             : }
     673             : 
     674             : void
     675           0 : StorageDBThread::ScheduleFlush()
     676             : {
     677           0 :   if (mDirtyEpoch) {
     678           0 :     return; // Already scheduled
     679             :   }
     680             : 
     681             :   // Must be non-zero to indicate we are scheduled
     682           0 :   mDirtyEpoch = PR_IntervalNow() | 1;
     683             : 
     684             :   // Wake the monitor from indefinite sleep...
     685           0 :   (mThreadObserver->GetMonitor()).Notify();
     686             : }
     687             : 
     688             : void
     689           0 : StorageDBThread::UnscheduleFlush()
     690             : {
     691             :   // We are just about to do the flush, drop flags
     692           0 :   mFlushImmediately = false;
     693           0 :   mDirtyEpoch = 0;
     694           0 : }
     695             : 
     696             : PRIntervalTime
     697           5 : StorageDBThread::TimeUntilFlush()
     698             : {
     699           5 :   if (mFlushImmediately) {
     700           0 :     return 0; // Do it now regardless the timeout.
     701             :   }
     702             : 
     703             :   static_assert(PR_INTERVAL_NO_TIMEOUT != 0,
     704             :       "PR_INTERVAL_NO_TIMEOUT must be non-zero");
     705             : 
     706           5 :   if (!mDirtyEpoch) {
     707           5 :     return PR_INTERVAL_NO_TIMEOUT; // No pending task...
     708             :   }
     709             : 
     710           0 :   static const PRIntervalTime kMaxAge = PR_MillisecondsToInterval(FLUSHING_INTERVAL_MS);
     711             : 
     712           0 :   PRIntervalTime now = PR_IntervalNow() | 1;
     713           0 :   PRIntervalTime age = now - mDirtyEpoch;
     714           0 :   if (age > kMaxAge) {
     715           0 :     return 0; // It is time.
     716             :   }
     717             : 
     718           0 :   return kMaxAge - age; // Time left, this is used to sleep the monitor
     719             : }
     720             : 
     721             : void
     722           0 : StorageDBThread::NotifyFlushCompletion()
     723             : {
     724             : #ifdef DOM_STORAGE_TESTS
     725           0 :   if (!NS_IsMainThread()) {
     726             :     RefPtr<nsRunnableMethod<StorageDBThread, void, false>> event =
     727           0 :       NewNonOwningRunnableMethod("dom::StorageDBThread::NotifyFlushCompletion",
     728             :                                  this,
     729           0 :                                  &StorageDBThread::NotifyFlushCompletion);
     730           0 :     NS_DispatchToMainThread(event);
     731           0 :     return;
     732             :   }
     733             : 
     734           0 :   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     735           0 :   if (obs) {
     736           0 :     obs->NotifyObservers(nullptr, "domstorage-test-flushed", nullptr);
     737             :   }
     738             : #endif
     739             : }
     740             : 
     741             : // Helper SQL function classes
     742             : 
     743             : namespace {
     744             : 
     745             : class OriginAttrsPatternMatchSQLFunction final : public mozIStorageFunction
     746             : {
     747             :   NS_DECL_ISUPPORTS
     748             :   NS_DECL_MOZISTORAGEFUNCTION
     749             : 
     750           0 :   explicit OriginAttrsPatternMatchSQLFunction(OriginAttributesPattern const& aPattern)
     751           0 :     : mPattern(aPattern) {}
     752             : 
     753             : private:
     754             :   OriginAttrsPatternMatchSQLFunction() = delete;
     755           0 :   ~OriginAttrsPatternMatchSQLFunction() {}
     756             : 
     757             :   OriginAttributesPattern mPattern;
     758             : };
     759             : 
     760           0 : NS_IMPL_ISUPPORTS(OriginAttrsPatternMatchSQLFunction, mozIStorageFunction)
     761             : 
     762             : NS_IMETHODIMP
     763           0 : OriginAttrsPatternMatchSQLFunction::OnFunctionCall(
     764             :     mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult)
     765             : {
     766             :   nsresult rv;
     767             : 
     768           0 :   nsAutoCString suffix;
     769           0 :   rv = aFunctionArguments->GetUTF8String(0, suffix);
     770           0 :   NS_ENSURE_SUCCESS(rv, rv);
     771             : 
     772           0 :   OriginAttributes oa;
     773           0 :   bool success = oa.PopulateFromSuffix(suffix);
     774           0 :   NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
     775           0 :   bool result = mPattern.Matches(oa);
     776             : 
     777           0 :   RefPtr<nsVariant> outVar(new nsVariant());
     778           0 :   rv = outVar->SetAsBool(result);
     779           0 :   NS_ENSURE_SUCCESS(rv, rv);
     780             : 
     781           0 :   outVar.forget(aResult);
     782           0 :   return NS_OK;
     783             : }
     784             : 
     785             : } // namespace
     786             : 
     787             : // StorageDBThread::DBOperation
     788             : 
     789           2 : StorageDBThread::DBOperation::DBOperation(const OperationType aType,
     790             :                                           LocalStorageCacheBridge* aCache,
     791             :                                           const nsAString& aKey,
     792           2 :                                           const nsAString& aValue)
     793             : : mType(aType)
     794             : , mCache(aCache)
     795             : , mKey(aKey)
     796           2 : , mValue(aValue)
     797             : {
     798           2 :   MOZ_ASSERT(mType == opPreload ||
     799             :              mType == opPreloadUrgent ||
     800             :              mType == opAddItem ||
     801             :              mType == opUpdateItem ||
     802             :              mType == opRemoveItem ||
     803             :              mType == opClear ||
     804             :              mType == opClearAll);
     805           2 :   MOZ_COUNT_CTOR(StorageDBThread::DBOperation);
     806           2 : }
     807             : 
     808           1 : StorageDBThread::DBOperation::DBOperation(const OperationType aType,
     809           1 :                                           StorageUsageBridge* aUsage)
     810             : : mType(aType)
     811           1 : , mUsage(aUsage)
     812             : {
     813           1 :   MOZ_ASSERT(mType == opGetUsage);
     814           1 :   MOZ_COUNT_CTOR(StorageDBThread::DBOperation);
     815           1 : }
     816             : 
     817           0 : StorageDBThread::DBOperation::DBOperation(const OperationType aType,
     818           0 :                                           const nsACString& aOriginNoSuffix)
     819             : : mType(aType)
     820             : , mCache(nullptr)
     821           0 : , mOrigin(aOriginNoSuffix)
     822             : {
     823           0 :   MOZ_ASSERT(mType == opClearMatchingOrigin);
     824           0 :   MOZ_COUNT_CTOR(StorageDBThread::DBOperation);
     825           0 : }
     826             : 
     827           0 : StorageDBThread::DBOperation::DBOperation(const OperationType aType,
     828           0 :                                           const OriginAttributesPattern& aOriginNoSuffix)
     829             : : mType(aType)
     830             : , mCache(nullptr)
     831           0 : , mOriginPattern(aOriginNoSuffix)
     832             : {
     833           0 :   MOZ_ASSERT(mType == opClearMatchingOriginAttributes);
     834           0 :   MOZ_COUNT_CTOR(StorageDBThread::DBOperation);
     835           0 : }
     836             : 
     837           6 : StorageDBThread::DBOperation::~DBOperation()
     838             : {
     839           3 :   MOZ_COUNT_DTOR(StorageDBThread::DBOperation);
     840           3 : }
     841             : 
     842             : const nsCString
     843           4 : StorageDBThread::DBOperation::OriginNoSuffix() const
     844             : {
     845           4 :   if (mCache) {
     846           4 :     return mCache->OriginNoSuffix();
     847             :   }
     848             : 
     849           0 :   return EmptyCString();
     850             : }
     851             : 
     852             : const nsCString
     853           4 : StorageDBThread::DBOperation::OriginSuffix() const
     854             : {
     855           4 :   if (mCache) {
     856           4 :     return mCache->OriginSuffix();
     857             :   }
     858             : 
     859           0 :   return EmptyCString();
     860             : }
     861             : 
     862             : const nsCString
     863           0 : StorageDBThread::DBOperation::Origin() const
     864             : {
     865           0 :   if (mCache) {
     866           0 :     return mCache->Origin();
     867             :   }
     868             : 
     869           0 :   return mOrigin;
     870             : }
     871             : 
     872             : const nsCString
     873           0 : StorageDBThread::DBOperation::Target() const
     874             : {
     875           0 :   switch (mType) {
     876             :     case opAddItem:
     877             :     case opUpdateItem:
     878             :     case opRemoveItem:
     879           0 :       return Origin() + NS_LITERAL_CSTRING("|") + NS_ConvertUTF16toUTF8(mKey);
     880             : 
     881             :     default:
     882           0 :       return Origin();
     883             :   }
     884             : }
     885             : 
     886             : void
     887           3 : StorageDBThread::DBOperation::PerformAndFinalize(StorageDBThread* aThread)
     888             : {
     889           3 :   Finalize(Perform(aThread));
     890           3 : }
     891             : 
     892             : nsresult
     893           3 : StorageDBThread::DBOperation::Perform(StorageDBThread* aThread)
     894             : {
     895             :   nsresult rv;
     896             : 
     897           3 :   switch (mType) {
     898             :   case opPreload:
     899             :   case opPreloadUrgent:
     900             :   {
     901             :     // Already loaded?
     902           2 :     if (mCache->Loaded()) {
     903           2 :       break;
     904             :     }
     905             : 
     906             :     StatementCache* statements;
     907           2 :     if (MOZ_UNLIKELY(NS_IsMainThread())) {
     908           0 :       statements = &aThread->mReaderStatements;
     909             :     } else {
     910           2 :       statements = &aThread->mWorkerStatements;
     911             :     }
     912             : 
     913             :     // OFFSET is an optimization when we have to do a sync load
     914             :     // and cache has already loaded some parts asynchronously.
     915             :     // It skips keys we have already loaded.
     916           4 :     nsCOMPtr<mozIStorageStatement> stmt = statements->GetCachedStatement(
     917             :         "SELECT key, value FROM webappsstore2 "
     918             :         "WHERE originAttributes = :originAttributes AND originKey = :originKey "
     919           2 :         "ORDER BY key LIMIT -1 OFFSET :offset");
     920           2 :     NS_ENSURE_STATE(stmt);
     921           2 :     mozStorageStatementScoper scope(stmt);
     922             : 
     923           8 :     rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
     924           6 :                                     mCache->OriginSuffix());
     925           2 :     NS_ENSURE_SUCCESS(rv, rv);
     926             : 
     927           8 :     rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originKey"),
     928           6 :                                     mCache->OriginNoSuffix());
     929           2 :     NS_ENSURE_SUCCESS(rv, rv);
     930             : 
     931           8 :     rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("offset"),
     932           6 :                                static_cast<int32_t>(mCache->LoadedCount()));
     933           2 :     NS_ENSURE_SUCCESS(rv, rv);
     934             : 
     935             :     bool exists;
     936           2 :     while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&exists)) && exists) {
     937           0 :       nsAutoString key;
     938           0 :       rv = stmt->GetString(0, key);
     939           0 :       NS_ENSURE_SUCCESS(rv, rv);
     940             : 
     941           0 :       nsAutoString value;
     942           0 :       rv = stmt->GetString(1, value);
     943           0 :       NS_ENSURE_SUCCESS(rv, rv);
     944             : 
     945           0 :       if (!mCache->LoadItem(key, value)) {
     946           0 :         break;
     947             :       }
     948             :     }
     949             : 
     950           2 :     mCache->LoadDone(NS_OK);
     951           2 :     break;
     952             :   }
     953             : 
     954             :   case opGetUsage:
     955             :   {
     956           2 :     nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
     957             :       "SELECT SUM(LENGTH(key) + LENGTH(value)) FROM webappsstore2 "
     958             :       "WHERE (originAttributes || ':' || originKey) LIKE :usageOrigin"
     959           1 :     );
     960           1 :     NS_ENSURE_STATE(stmt);
     961             : 
     962           1 :     mozStorageStatementScoper scope(stmt);
     963             : 
     964           4 :     rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("usageOrigin"),
     965           3 :                                     mUsage->OriginScope());
     966           1 :     NS_ENSURE_SUCCESS(rv, rv);
     967             : 
     968             :     bool exists;
     969           1 :     rv = stmt->ExecuteStep(&exists);
     970           1 :     NS_ENSURE_SUCCESS(rv, rv);
     971             : 
     972           1 :     int64_t usage = 0;
     973           1 :     if (exists) {
     974           1 :       rv = stmt->GetInt64(0, &usage);
     975           1 :       NS_ENSURE_SUCCESS(rv, rv);
     976             :     }
     977             : 
     978           1 :     mUsage->LoadUsage(usage);
     979           1 :     break;
     980             :   }
     981             : 
     982             :   case opAddItem:
     983             :   case opUpdateItem:
     984             :   {
     985           0 :     MOZ_ASSERT(!NS_IsMainThread());
     986             : 
     987           0 :     nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
     988             :       "INSERT OR REPLACE INTO webappsstore2 (originAttributes, originKey, scope, key, value) "
     989             :       "VALUES (:originAttributes, :originKey, :scope, :key, :value) "
     990           0 :     );
     991           0 :     NS_ENSURE_STATE(stmt);
     992             : 
     993           0 :     mozStorageStatementScoper scope(stmt);
     994             : 
     995           0 :     rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
     996           0 :                                     mCache->OriginSuffix());
     997           0 :     NS_ENSURE_SUCCESS(rv, rv);
     998           0 :     rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originKey"),
     999           0 :                                     mCache->OriginNoSuffix());
    1000           0 :     NS_ENSURE_SUCCESS(rv, rv);
    1001             :     // Filling the 'scope' column just for downgrade compatibility reasons
    1002           0 :     rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
    1003           0 :                                     Scheme0Scope(mCache));
    1004           0 :     NS_ENSURE_SUCCESS(rv, rv);
    1005           0 :     rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key"),
    1006           0 :                                 mKey);
    1007           0 :     NS_ENSURE_SUCCESS(rv, rv);
    1008           0 :     rv = stmt->BindStringByName(NS_LITERAL_CSTRING("value"),
    1009           0 :                                 mValue);
    1010           0 :     NS_ENSURE_SUCCESS(rv, rv);
    1011             : 
    1012           0 :     rv = stmt->Execute();
    1013           0 :     NS_ENSURE_SUCCESS(rv, rv);
    1014             : 
    1015           0 :     MonitorAutoLock monitor(aThread->mThreadObserver->GetMonitor());
    1016           0 :     aThread->mOriginsHavingData.PutEntry(Origin());
    1017           0 :     break;
    1018             :   }
    1019             : 
    1020             :   case opRemoveItem:
    1021             :   {
    1022           0 :     MOZ_ASSERT(!NS_IsMainThread());
    1023             : 
    1024           0 :     nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
    1025             :       "DELETE FROM webappsstore2 "
    1026             :       "WHERE originAttributes = :originAttributes AND originKey = :originKey "
    1027             :         "AND key = :key "
    1028           0 :     );
    1029           0 :     NS_ENSURE_STATE(stmt);
    1030           0 :     mozStorageStatementScoper scope(stmt);
    1031             : 
    1032           0 :     rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
    1033           0 :                                     mCache->OriginSuffix());
    1034           0 :     NS_ENSURE_SUCCESS(rv, rv);
    1035           0 :     rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originKey"),
    1036           0 :                                     mCache->OriginNoSuffix());
    1037           0 :     NS_ENSURE_SUCCESS(rv, rv);
    1038           0 :     rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key"),
    1039           0 :                                 mKey);
    1040           0 :     NS_ENSURE_SUCCESS(rv, rv);
    1041             : 
    1042           0 :     rv = stmt->Execute();
    1043           0 :     NS_ENSURE_SUCCESS(rv, rv);
    1044             : 
    1045           0 :     break;
    1046             :   }
    1047             : 
    1048             :   case opClear:
    1049             :   {
    1050           0 :     MOZ_ASSERT(!NS_IsMainThread());
    1051             : 
    1052           0 :     nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
    1053             :       "DELETE FROM webappsstore2 "
    1054             :       "WHERE originAttributes = :originAttributes AND originKey = :originKey"
    1055           0 :     );
    1056           0 :     NS_ENSURE_STATE(stmt);
    1057           0 :     mozStorageStatementScoper scope(stmt);
    1058             : 
    1059           0 :     rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
    1060           0 :                                     mCache->OriginSuffix());
    1061           0 :     NS_ENSURE_SUCCESS(rv, rv);
    1062           0 :     rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originKey"),
    1063           0 :                                     mCache->OriginNoSuffix());
    1064           0 :     NS_ENSURE_SUCCESS(rv, rv);
    1065             : 
    1066           0 :     rv = stmt->Execute();
    1067           0 :     NS_ENSURE_SUCCESS(rv, rv);
    1068             : 
    1069           0 :     MonitorAutoLock monitor(aThread->mThreadObserver->GetMonitor());
    1070           0 :     aThread->mOriginsHavingData.RemoveEntry(Origin());
    1071           0 :     break;
    1072             :   }
    1073             : 
    1074             :   case opClearAll:
    1075             :   {
    1076           0 :     MOZ_ASSERT(!NS_IsMainThread());
    1077             : 
    1078           0 :     nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
    1079             :       "DELETE FROM webappsstore2"
    1080           0 :     );
    1081           0 :     NS_ENSURE_STATE(stmt);
    1082           0 :     mozStorageStatementScoper scope(stmt);
    1083             : 
    1084           0 :     rv = stmt->Execute();
    1085           0 :     NS_ENSURE_SUCCESS(rv, rv);
    1086             : 
    1087           0 :     MonitorAutoLock monitor(aThread->mThreadObserver->GetMonitor());
    1088           0 :     aThread->mOriginsHavingData.Clear();
    1089           0 :     break;
    1090             :   }
    1091             : 
    1092             :   case opClearMatchingOrigin:
    1093             :   {
    1094           0 :     MOZ_ASSERT(!NS_IsMainThread());
    1095             : 
    1096           0 :     nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
    1097             :       "DELETE FROM webappsstore2"
    1098             :       " WHERE originKey GLOB :scope"
    1099           0 :     );
    1100           0 :     NS_ENSURE_STATE(stmt);
    1101           0 :     mozStorageStatementScoper scope(stmt);
    1102             : 
    1103           0 :     rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
    1104           0 :                                     mOrigin + NS_LITERAL_CSTRING("*"));
    1105           0 :     NS_ENSURE_SUCCESS(rv, rv);
    1106             : 
    1107           0 :     rv = stmt->Execute();
    1108           0 :     NS_ENSURE_SUCCESS(rv, rv);
    1109             : 
    1110             :     // No need to selectively clear mOriginsHavingData here.  That hashtable
    1111             :     // only prevents preload for scopes with no data.  Leaving a false record in
    1112             :     // it has a negligible effect on performance.
    1113           0 :     break;
    1114             :   }
    1115             : 
    1116             :   case opClearMatchingOriginAttributes:
    1117             :   {
    1118           0 :     MOZ_ASSERT(!NS_IsMainThread());
    1119             : 
    1120             :     // Register the ORIGIN_ATTRS_PATTERN_MATCH function, initialized with the
    1121             :     // pattern
    1122             :     nsCOMPtr<mozIStorageFunction> patternMatchFunction(
    1123           0 :       new OriginAttrsPatternMatchSQLFunction(mOriginPattern));
    1124             : 
    1125           0 :     rv = aThread->mWorkerConnection->CreateFunction(
    1126           0 :       NS_LITERAL_CSTRING("ORIGIN_ATTRS_PATTERN_MATCH"), 1, patternMatchFunction);
    1127           0 :     NS_ENSURE_SUCCESS(rv, rv);
    1128             : 
    1129           0 :     nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
    1130             :       "DELETE FROM webappsstore2"
    1131             :       " WHERE ORIGIN_ATTRS_PATTERN_MATCH(originAttributes)"
    1132           0 :     );
    1133             : 
    1134           0 :     if (stmt) {
    1135           0 :       mozStorageStatementScoper scope(stmt);
    1136           0 :       rv = stmt->Execute();
    1137             :     } else {
    1138           0 :       rv = NS_ERROR_UNEXPECTED;
    1139             :     }
    1140             : 
    1141             :     // Always remove the function
    1142           0 :     aThread->mWorkerConnection->RemoveFunction(
    1143           0 :       NS_LITERAL_CSTRING("ORIGIN_ATTRS_PATTERN_MATCH"));
    1144             : 
    1145           0 :     NS_ENSURE_SUCCESS(rv, rv);
    1146             : 
    1147             :     // No need to selectively clear mOriginsHavingData here.  That hashtable
    1148             :     // only prevents preload for scopes with no data.  Leaving a false record in
    1149             :     // it has a negligible effect on performance.
    1150           0 :     break;
    1151             :   }
    1152             : 
    1153             :   default:
    1154           0 :     NS_ERROR("Unknown task type");
    1155           0 :     break;
    1156             :   }
    1157             : 
    1158           3 :   return NS_OK;
    1159             : }
    1160             : 
    1161             : void
    1162           3 : StorageDBThread::DBOperation::Finalize(nsresult aRv)
    1163             : {
    1164           3 :   switch (mType) {
    1165             :   case opPreloadUrgent:
    1166             :   case opPreload:
    1167           2 :     if (NS_FAILED(aRv)) {
    1168             :       // When we are here, something failed when loading from the database.
    1169             :       // Notify that the storage is loaded to prevent deadlock of the main
    1170             :       // thread, even though it is actually empty or incomplete.
    1171           0 :       NS_WARNING("Failed to preload localStorage");
    1172             :     }
    1173             : 
    1174           2 :     mCache->LoadDone(aRv);
    1175           2 :     break;
    1176             : 
    1177             :   case opGetUsage:
    1178           1 :     if (NS_FAILED(aRv)) {
    1179           0 :       mUsage->LoadUsage(0);
    1180             :     }
    1181             : 
    1182           1 :     break;
    1183             : 
    1184             :   default:
    1185           0 :     if (NS_FAILED(aRv)) {
    1186             :       NS_WARNING("localStorage update/clear operation failed,"
    1187           0 :                  " data may not persist or clean up");
    1188             :     }
    1189             : 
    1190           0 :     break;
    1191             :   }
    1192           3 : }
    1193             : 
    1194             : // StorageDBThread::PendingOperations
    1195             : 
    1196           1 : StorageDBThread::PendingOperations::PendingOperations()
    1197           1 : : mFlushFailureCount(0)
    1198             : {
    1199           1 : }
    1200             : 
    1201             : bool
    1202           0 : StorageDBThread::PendingOperations::HasTasks() const
    1203             : {
    1204           0 :   return !!mUpdates.Count() || !!mClears.Count();
    1205             : }
    1206             : 
    1207             : namespace {
    1208             : 
    1209           0 : bool OriginPatternMatches(const nsACString& aOriginSuffix,
    1210             :                           const OriginAttributesPattern& aPattern)
    1211             : {
    1212           0 :   OriginAttributes oa;
    1213           0 :   DebugOnly<bool> rv = oa.PopulateFromSuffix(aOriginSuffix);
    1214           0 :   MOZ_ASSERT(rv);
    1215           0 :   return aPattern.Matches(oa);
    1216             : }
    1217             : 
    1218             : } // namespace
    1219             : 
    1220             : bool
    1221           0 : StorageDBThread::PendingOperations::CheckForCoalesceOpportunity(DBOperation* aNewOp,
    1222             :                                                                 DBOperation::OperationType aPendingType,
    1223             :                                                                 DBOperation::OperationType aNewType)
    1224             : {
    1225           0 :   if (aNewOp->Type() != aNewType) {
    1226           0 :     return false;
    1227             :   }
    1228             : 
    1229             :   StorageDBThread::DBOperation* pendingTask;
    1230           0 :   if (!mUpdates.Get(aNewOp->Target(), &pendingTask)) {
    1231           0 :     return false;
    1232             :   }
    1233             : 
    1234           0 :   if (pendingTask->Type() != aPendingType) {
    1235           0 :     return false;
    1236             :   }
    1237             : 
    1238           0 :   return true;
    1239             : }
    1240             : 
    1241             : void
    1242           0 : StorageDBThread::PendingOperations::Add(StorageDBThread::DBOperation* aOperation)
    1243             : {
    1244             :   // Optimize: when a key to remove has never been written to disk
    1245             :   // just bypass this operation.  A key is new when an operation scheduled
    1246             :   // to write it to the database is of type opAddItem.
    1247           0 :   if (CheckForCoalesceOpportunity(aOperation, DBOperation::opAddItem,
    1248             :                                   DBOperation::opRemoveItem)) {
    1249           0 :     mUpdates.Remove(aOperation->Target());
    1250           0 :     delete aOperation;
    1251           0 :     return;
    1252             :   }
    1253             : 
    1254             :   // Optimize: when changing a key that is new and has never been
    1255             :   // written to disk, keep type of the operation to store it at opAddItem.
    1256             :   // This allows optimization to just forget adding a new key when
    1257             :   // it is removed from the storage before flush.
    1258           0 :   if (CheckForCoalesceOpportunity(aOperation, DBOperation::opAddItem,
    1259             :                                   DBOperation::opUpdateItem)) {
    1260           0 :     aOperation->mType = DBOperation::opAddItem;
    1261             :   }
    1262             : 
    1263             :   // Optimize: to prevent lose of remove operation on a key when doing
    1264             :   // remove/set/remove on a previously existing key we have to change
    1265             :   // opAddItem to opUpdateItem on the new operation when there is opRemoveItem
    1266             :   // pending for the key.
    1267           0 :   if (CheckForCoalesceOpportunity(aOperation, DBOperation::opRemoveItem,
    1268             :                                   DBOperation::opAddItem)) {
    1269           0 :     aOperation->mType = DBOperation::opUpdateItem;
    1270             :   }
    1271             : 
    1272           0 :   switch (aOperation->Type())
    1273             :   {
    1274             :   // Operations on single keys
    1275             : 
    1276             :   case DBOperation::opAddItem:
    1277             :   case DBOperation::opUpdateItem:
    1278             :   case DBOperation::opRemoveItem:
    1279             :     // Override any existing operation for the target (=scope+key).
    1280           0 :     mUpdates.Put(aOperation->Target(), aOperation);
    1281           0 :     break;
    1282             : 
    1283             :   // Clear operations
    1284             : 
    1285             :   case DBOperation::opClear:
    1286             :   case DBOperation::opClearMatchingOrigin:
    1287             :   case DBOperation::opClearMatchingOriginAttributes:
    1288             :     // Drop all update (insert/remove) operations for equivavelent or matching
    1289             :     // scope.  We do this as an optimization as well as a must based on the
    1290             :     // logic, if we would not delete the update tasks, changes would have been
    1291             :     // stored to the database after clear operations have been executed.
    1292           0 :     for (auto iter = mUpdates.Iter(); !iter.Done(); iter.Next()) {
    1293           0 :       nsAutoPtr<DBOperation>& pendingTask = iter.Data();
    1294             : 
    1295           0 :       if (aOperation->Type() == DBOperation::opClear &&
    1296           0 :           (pendingTask->OriginNoSuffix() != aOperation->OriginNoSuffix() ||
    1297           0 :            pendingTask->OriginSuffix() != aOperation->OriginSuffix())) {
    1298           0 :         continue;
    1299             :       }
    1300             : 
    1301           0 :       if (aOperation->Type() == DBOperation::opClearMatchingOrigin &&
    1302           0 :           !StringBeginsWith(pendingTask->OriginNoSuffix(), aOperation->Origin())) {
    1303           0 :         continue;
    1304             :       }
    1305             : 
    1306           0 :       if (aOperation->Type() == DBOperation::opClearMatchingOriginAttributes &&
    1307           0 :           !OriginPatternMatches(pendingTask->OriginSuffix(), aOperation->OriginPattern())) {
    1308           0 :         continue;
    1309             :       }
    1310             : 
    1311           0 :       iter.Remove();
    1312             :     }
    1313             : 
    1314           0 :     mClears.Put(aOperation->Target(), aOperation);
    1315           0 :     break;
    1316             : 
    1317             :   case DBOperation::opClearAll:
    1318             :     // Drop simply everything, this is a super-operation.
    1319           0 :     mUpdates.Clear();
    1320           0 :     mClears.Clear();
    1321           0 :     mClears.Put(aOperation->Target(), aOperation);
    1322           0 :     break;
    1323             : 
    1324             :   default:
    1325           0 :     MOZ_ASSERT(false);
    1326             :     break;
    1327             :   }
    1328             : }
    1329             : 
    1330             : bool
    1331           0 : StorageDBThread::PendingOperations::Prepare()
    1332             : {
    1333             :   // Called under the lock
    1334             : 
    1335             :   // First collect clear operations and then updates, we can
    1336             :   // do this since whenever a clear operation for a scope is
    1337             :   // scheduled, we drop all updates matching that scope. So,
    1338             :   // all scope-related update operations we have here now were
    1339             :   // scheduled after the clear operations.
    1340           0 :   for (auto iter = mClears.Iter(); !iter.Done(); iter.Next()) {
    1341           0 :     mExecList.AppendElement(iter.Data().forget());
    1342             :   }
    1343           0 :   mClears.Clear();
    1344             : 
    1345           0 :   for (auto iter = mUpdates.Iter(); !iter.Done(); iter.Next()) {
    1346           0 :     mExecList.AppendElement(iter.Data().forget());
    1347             :   }
    1348           0 :   mUpdates.Clear();
    1349             : 
    1350           0 :   return !!mExecList.Length();
    1351             : }
    1352             : 
    1353             : nsresult
    1354           0 : StorageDBThread::PendingOperations::Execute(StorageDBThread* aThread)
    1355             : {
    1356             :   // Called outside the lock
    1357             : 
    1358           0 :   mozStorageTransaction transaction(aThread->mWorkerConnection, false);
    1359             : 
    1360             :   nsresult rv;
    1361             : 
    1362           0 :   for (uint32_t i = 0; i < mExecList.Length(); ++i) {
    1363           0 :     StorageDBThread::DBOperation* task = mExecList[i];
    1364           0 :     rv = task->Perform(aThread);
    1365           0 :     if (NS_FAILED(rv)) {
    1366           0 :       return rv;
    1367             :     }
    1368             :   }
    1369             : 
    1370           0 :   rv = transaction.Commit();
    1371           0 :   if (NS_FAILED(rv)) {
    1372           0 :     return rv;
    1373             :   }
    1374             : 
    1375           0 :   return NS_OK;
    1376             : }
    1377             : 
    1378             : bool
    1379           0 : StorageDBThread::PendingOperations::Finalize(nsresult aRv)
    1380             : {
    1381             :   // Called under the lock
    1382             : 
    1383             :   // The list is kept on a failure to retry it
    1384           0 :   if (NS_FAILED(aRv)) {
    1385             :     // XXX Followup: we may try to reopen the database and flush these
    1386             :     // pending tasks, however testing showed that even though I/O is actually
    1387             :     // broken some amount of operations is left in sqlite+system buffers and
    1388             :     // seems like successfully flushed to disk.
    1389             :     // Tested by removing a flash card and disconnecting from network while
    1390             :     // using a network drive on Windows system.
    1391           0 :     NS_WARNING("Flush operation on localStorage database failed");
    1392             : 
    1393           0 :     ++mFlushFailureCount;
    1394             : 
    1395           0 :     return mFlushFailureCount >= 5;
    1396             :   }
    1397             : 
    1398           0 :   mFlushFailureCount = 0;
    1399           0 :   mExecList.Clear();
    1400           0 :   return true;
    1401             : }
    1402             : 
    1403             : namespace {
    1404             : 
    1405             : bool
    1406           0 : FindPendingClearForOrigin(const nsACString& aOriginSuffix,
    1407             :                           const nsACString& aOriginNoSuffix,
    1408             :                           StorageDBThread::DBOperation* aPendingOperation)
    1409             : {
    1410           0 :   if (aPendingOperation->Type() == StorageDBThread::DBOperation::opClearAll) {
    1411           0 :     return true;
    1412             :   }
    1413             : 
    1414           0 :   if (aPendingOperation->Type() == StorageDBThread::DBOperation::opClear &&
    1415           0 :       aOriginNoSuffix == aPendingOperation->OriginNoSuffix() &&
    1416           0 :       aOriginSuffix == aPendingOperation->OriginSuffix()) {
    1417           0 :     return true;
    1418             :   }
    1419             : 
    1420           0 :   if (aPendingOperation->Type() == StorageDBThread::DBOperation::opClearMatchingOrigin &&
    1421           0 :       StringBeginsWith(aOriginNoSuffix, aPendingOperation->Origin())) {
    1422           0 :     return true;
    1423             :   }
    1424             : 
    1425           0 :   if (aPendingOperation->Type() == StorageDBThread::DBOperation::opClearMatchingOriginAttributes &&
    1426           0 :       OriginPatternMatches(aOriginSuffix, aPendingOperation->OriginPattern())) {
    1427           0 :     return true;
    1428             :   }
    1429             : 
    1430           0 :   return false;
    1431             : }
    1432             : 
    1433             : } // namespace
    1434             : 
    1435             : bool
    1436           2 : StorageDBThread::PendingOperations::IsOriginClearPending(const nsACString& aOriginSuffix,
    1437             :                                                          const nsACString& aOriginNoSuffix) const
    1438             : {
    1439             :   // Called under the lock
    1440             : 
    1441           2 :   for (auto iter = mClears.ConstIter(); !iter.Done(); iter.Next()) {
    1442           0 :     if (FindPendingClearForOrigin(aOriginSuffix, aOriginNoSuffix, iter.UserData())) {
    1443           0 :       return true;
    1444             :     }
    1445             :   }
    1446             : 
    1447           2 :   for (uint32_t i = 0; i < mExecList.Length(); ++i) {
    1448           0 :     if (FindPendingClearForOrigin(aOriginSuffix, aOriginNoSuffix, mExecList[i])) {
    1449           0 :       return true;
    1450             :     }
    1451             :   }
    1452             : 
    1453           2 :   return false;
    1454             : }
    1455             : 
    1456             : namespace {
    1457             : 
    1458             : bool
    1459           0 : FindPendingUpdateForOrigin(const nsACString& aOriginSuffix,
    1460             :                            const nsACString& aOriginNoSuffix,
    1461             :                            StorageDBThread::DBOperation* aPendingOperation)
    1462             : {
    1463           0 :   if ((aPendingOperation->Type() == StorageDBThread::DBOperation::opAddItem ||
    1464           0 :        aPendingOperation->Type() == StorageDBThread::DBOperation::opUpdateItem ||
    1465           0 :        aPendingOperation->Type() == StorageDBThread::DBOperation::opRemoveItem) &&
    1466           0 :        aOriginNoSuffix == aPendingOperation->OriginNoSuffix() &&
    1467           0 :        aOriginSuffix == aPendingOperation->OriginSuffix()) {
    1468           0 :     return true;
    1469             :   }
    1470             : 
    1471           0 :   return false;
    1472             : }
    1473             : 
    1474             : } // namespace
    1475             : 
    1476             : bool
    1477           2 : StorageDBThread::PendingOperations::IsOriginUpdatePending(const nsACString& aOriginSuffix,
    1478             :                                                           const nsACString& aOriginNoSuffix) const
    1479             : {
    1480             :   // Called under the lock
    1481             : 
    1482           2 :   for (auto iter = mUpdates.ConstIter(); !iter.Done(); iter.Next()) {
    1483           0 :     if (FindPendingUpdateForOrigin(aOriginSuffix, aOriginNoSuffix, iter.UserData())) {
    1484           0 :       return true;
    1485             :     }
    1486             :   }
    1487             : 
    1488           2 :   for (uint32_t i = 0; i < mExecList.Length(); ++i) {
    1489           0 :     if (FindPendingUpdateForOrigin(aOriginSuffix, aOriginNoSuffix, mExecList[i])) {
    1490           0 :       return true;
    1491             :     }
    1492             :   }
    1493             : 
    1494           2 :   return false;
    1495             : }
    1496             : 
    1497             : } // namespace dom
    1498             : } // namespace mozilla

Generated by: LCOV version 1.13