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