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