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/. */
7 #include "StorageDBThread.h"
9 #include "StorageCommon.h"
10 #include "StorageDBUpdater.h"
11 #include "StorageUtils.h"
12 #include "LocalStorageCache.h"
13 #include "LocalStorageManager.h"
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"
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"
40 // How long we collect write oprerations
41 // before they are flushed to the database
43 #define FLUSHING_INTERVAL_MS 5000
45 // Write Ahead Log's maximum size is 512KB
46 #define MAX_WAL_SIZE_BYTES 512 * 1024
48 // Current version of the database schema
49 #define CURRENT_SCHEMA_VERSION 2
51 namespace mozilla::dom
{
53 using namespace StorageUtils
;
57 StorageDBThread
* sStorageThread
[kPrivateBrowsingIdCount
] = {nullptr, nullptr};
59 // False until we shut the storage thread down.
60 bool sStorageThreadDown
[kPrivateBrowsingIdCount
] = {false, false};
66 StorageDBBridge::StorageDBBridge()
71 class StorageDBThread::InitHelper final
: public Runnable
{
72 nsCOMPtr
<nsIEventTarget
> mOwningThread
;
73 mozilla::Mutex mMutex MOZ_UNANNOTATED
;
74 mozilla::CondVar mCondVar
;
75 nsString mProfilePath
;
76 nsresult mMainThreadResultCode
;
81 : Runnable("dom::StorageDBThread::InitHelper"),
82 mOwningThread(GetCurrentEventTarget()),
83 mMutex("InitHelper::mMutex"),
84 mCondVar(mMutex
, "InitHelper::mCondVar"),
85 mMainThreadResultCode(NS_OK
),
88 // Because of the `sync Preload` IPC, we need to be able to synchronously
89 // initialize, which includes consulting and initializing
90 // some main-thread-only APIs. Bug 1386441 discusses improving this situation.
91 nsresult
SyncDispatchAndReturnProfilePath(nsAString
& aProfilePath
);
94 ~InitHelper() override
= default;
96 nsresult
RunOnMainThread();
101 class StorageDBThread::NoteBackgroundThreadRunnable final
: public Runnable
{
102 // Expected to be only 0 or 1.
103 const uint32_t mPrivateBrowsingId
;
104 nsCOMPtr
<nsIEventTarget
> mOwningThread
;
107 explicit NoteBackgroundThreadRunnable(const uint32_t aPrivateBrowsingId
)
108 : Runnable("dom::StorageDBThread::NoteBackgroundThreadRunnable"),
109 mPrivateBrowsingId(aPrivateBrowsingId
),
110 mOwningThread(GetCurrentEventTarget()) {
111 MOZ_RELEASE_ASSERT(aPrivateBrowsingId
< kPrivateBrowsingIdCount
);
115 ~NoteBackgroundThreadRunnable() override
= default;
120 StorageDBThread::StorageDBThread(const uint32_t aPrivateBrowsingId
)
122 mThreadObserver(new ThreadObserver()),
123 mStopIOThread(false),
124 mWALModeEnabled(false),
127 mWorkerStatements(mWorkerConnection
),
128 mReaderStatements(mReaderConnection
),
129 mFlushImmediately(false),
130 mPrivateBrowsingId(aPrivateBrowsingId
),
131 mPriorityCounter(0) {
132 MOZ_RELEASE_ASSERT(aPrivateBrowsingId
< kPrivateBrowsingIdCount
);
136 StorageDBThread
* StorageDBThread::Get(const uint32_t aPrivateBrowsingId
) {
137 ::mozilla::ipc::AssertIsOnBackgroundThread();
138 MOZ_RELEASE_ASSERT(aPrivateBrowsingId
< kPrivateBrowsingIdCount
);
140 return sStorageThread
[aPrivateBrowsingId
];
144 StorageDBThread
* StorageDBThread::GetOrCreate(
145 const nsString
& aProfilePath
, const uint32_t aPrivateBrowsingId
) {
146 ::mozilla::ipc::AssertIsOnBackgroundThread();
147 MOZ_RELEASE_ASSERT(aPrivateBrowsingId
< kPrivateBrowsingIdCount
);
149 StorageDBThread
*& storageThread
= sStorageThread
[aPrivateBrowsingId
];
150 if (storageThread
|| sStorageThreadDown
[aPrivateBrowsingId
]) {
151 // When sStorageThreadDown is at true, sStorageThread is null.
152 // Checking sStorageThreadDown flag here prevents reinitialization of
153 // the storage thread after shutdown.
154 return storageThread
;
157 auto newStorageThread
= MakeUnique
<StorageDBThread
>(aPrivateBrowsingId
);
159 nsresult rv
= newStorageThread
->Init(aProfilePath
);
160 if (NS_WARN_IF(NS_FAILED(rv
))) {
164 storageThread
= newStorageThread
.release();
166 return storageThread
;
170 nsresult
StorageDBThread::GetProfilePath(nsString
& aProfilePath
) {
171 MOZ_ASSERT(XRE_IsParentProcess());
172 MOZ_ASSERT(NS_IsMainThread());
174 // Need to determine location on the main thread, since
175 // NS_GetSpecialDirectory accesses the atom table that can
176 // only be accessed on the main thread.
177 nsCOMPtr
<nsIFile
> profileDir
;
178 nsresult rv
= NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR
,
179 getter_AddRefs(profileDir
));
180 if (NS_WARN_IF(NS_FAILED(rv
))) {
184 rv
= profileDir
->GetPath(aProfilePath
);
185 if (NS_WARN_IF(NS_FAILED(rv
))) {
189 // This service has to be started on the main thread currently.
190 nsCOMPtr
<mozIStorageService
> ss
=
191 do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID
, &rv
);
192 if (NS_WARN_IF(NS_FAILED(rv
))) {
199 nsresult
StorageDBThread::Init(const nsString
& aProfilePath
) {
200 ::mozilla::ipc::AssertIsOnBackgroundThread();
202 if (mPrivateBrowsingId
== 0) {
205 nsString profilePath
;
206 if (aProfilePath
.IsEmpty()) {
207 RefPtr
<InitHelper
> helper
= new InitHelper();
209 rv
= helper
->SyncDispatchAndReturnProfilePath(profilePath
);
210 if (NS_WARN_IF(NS_FAILED(rv
))) {
214 profilePath
= aProfilePath
;
217 mDatabaseFile
= do_CreateInstance(NS_LOCAL_FILE_CONTRACTID
, &rv
);
218 if (NS_WARN_IF(NS_FAILED(rv
))) {
222 rv
= mDatabaseFile
->InitWithPath(profilePath
);
223 if (NS_WARN_IF(NS_FAILED(rv
))) {
227 rv
= mDatabaseFile
->Append(u
"webappsstore.sqlite"_ns
);
228 NS_ENSURE_SUCCESS(rv
, rv
);
231 // Need to keep the lock to avoid setting mThread later then
232 // the thread body executes.
233 MonitorAutoLock
monitor(mThreadObserver
->GetMonitor());
235 mThread
= PR_CreateThread(PR_USER_THREAD
, &StorageDBThread::ThreadFunc
, this,
236 PR_PRIORITY_LOW
, PR_GLOBAL_THREAD
,
237 PR_JOINABLE_THREAD
, 262144);
239 return NS_ERROR_OUT_OF_MEMORY
;
242 RefPtr
<NoteBackgroundThreadRunnable
> runnable
=
243 new NoteBackgroundThreadRunnable(mPrivateBrowsingId
);
244 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable
));
249 nsresult
StorageDBThread::Shutdown() {
250 ::mozilla::ipc::AssertIsOnBackgroundThread();
253 return NS_ERROR_NOT_INITIALIZED
;
256 Telemetry::AutoTimer
<Telemetry::LOCALDOMSTORAGE_SHUTDOWN_DATABASE_MS
> timer
;
259 MonitorAutoLock
monitor(mThreadObserver
->GetMonitor());
261 // After we stop, no other operations can be accepted
262 mFlushImmediately
= true;
263 mStopIOThread
= true;
267 PR_JoinThread(mThread
);
273 void StorageDBThread::SyncPreload(LocalStorageCacheBridge
* aCache
,
275 AUTO_PROFILER_LABEL("StorageDBThread::SyncPreload", OTHER
);
276 if (!aForceSync
&& aCache
->LoadedCount()) {
277 // Preload already started for this cache, just wait for it to finish.
278 // LoadWait will exit after LoadDone on the cache has been called.
281 SetDefaultPriority();
285 // Bypass sync load when an update is pending in the queue to write, we would
286 // get incosistent data in the cache. Also don't allow sync main-thread
287 // preload when DB open and init is still pending on the background thread.
288 if (mDBReady
&& mWALModeEnabled
) {
291 MonitorAutoLock
monitor(mThreadObserver
->GetMonitor());
292 pendingTasks
= mPendingTasks
.IsOriginUpdatePending(
293 aCache
->OriginSuffix(), aCache
->OriginNoSuffix()) ||
294 mPendingTasks
.IsOriginClearPending(
295 aCache
->OriginSuffix(), aCache
->OriginNoSuffix());
299 // WAL is enabled, thus do the load synchronously on the main thread.
300 DBOperation
preload(DBOperation::opPreload
, aCache
);
301 preload
.PerformAndFinalize(this);
306 // Need to go asynchronously since WAL is not allowed or scheduled updates
307 // need to be flushed first.
308 // Schedule preload for this cache as the first operation.
310 InsertDBOp(MakeUnique
<DBOperation
>(DBOperation::opPreloadUrgent
, aCache
));
312 // LoadWait exits after LoadDone of the cache has been called.
313 if (NS_SUCCEEDED(rv
)) {
318 void StorageDBThread::AsyncFlush() {
319 MonitorAutoLock
monitor(mThreadObserver
->GetMonitor());
320 mFlushImmediately
= true;
324 bool StorageDBThread::ShouldPreloadOrigin(const nsACString
& aOrigin
) {
325 MonitorAutoLock
monitor(mThreadObserver
->GetMonitor());
326 return mOriginsHavingData
.Contains(aOrigin
);
329 void StorageDBThread::GetOriginsHavingData(nsTArray
<nsCString
>* aOrigins
) {
330 MonitorAutoLock
monitor(mThreadObserver
->GetMonitor());
331 AppendToArray(*aOrigins
, mOriginsHavingData
);
334 nsresult
StorageDBThread::InsertDBOp(
335 UniquePtr
<StorageDBThread::DBOperation
> aOperation
) {
336 MonitorAutoLock
monitor(mThreadObserver
->GetMonitor());
338 if (NS_FAILED(mStatus
)) {
339 MonitorAutoUnlock
unlock(mThreadObserver
->GetMonitor());
340 aOperation
->Finalize(mStatus
);
345 // Thread use after shutdown demanded.
347 return NS_ERROR_NOT_INITIALIZED
;
350 switch (aOperation
->Type()) {
351 case DBOperation::opPreload
:
352 case DBOperation::opPreloadUrgent
:
353 if (mPendingTasks
.IsOriginUpdatePending(aOperation
->OriginSuffix(),
354 aOperation
->OriginNoSuffix())) {
355 // If there is a pending update operation for the scope first do the
356 // flush before we preload the cache. This may happen in an extremely
357 // rare case when a child process throws away its cache before flush on
358 // the parent has finished. If we would preloaded the cache as a
359 // priority operation before the pending flush, we would have got an
360 // inconsistent cache content.
361 mFlushImmediately
= true;
362 } else if (mPendingTasks
.IsOriginClearPending(
363 aOperation
->OriginSuffix(),
364 aOperation
->OriginNoSuffix())) {
365 // The scope is scheduled to be cleared, so just quickly load as empty.
366 // We need to do this to prevent load of the DB data before the scope
367 // has actually been cleared from the database. Preloads are processed
368 // immediately before update and clear operations on the database that
369 // are flushed periodically in batches.
370 MonitorAutoUnlock
unlock(mThreadObserver
->GetMonitor());
371 aOperation
->Finalize(NS_OK
);
376 case DBOperation::opGetUsage
:
377 if (aOperation
->Type() == DBOperation::opPreloadUrgent
) {
378 SetHigherPriority(); // Dropped back after urgent preload execution
379 mPreloads
.InsertElementAt(0, aOperation
.release());
381 mPreloads
.AppendElement(aOperation
.release());
384 // Immediately start executing this.
389 // Update operations are first collected, coalesced and then flushed
390 // after a short time.
391 mPendingTasks
.Add(std::move(aOperation
));
400 void StorageDBThread::SetHigherPriority() {
402 PR_SetThreadPriority(mThread
, PR_PRIORITY_URGENT
);
405 void StorageDBThread::SetDefaultPriority() {
406 if (--mPriorityCounter
<= 0) {
407 PR_SetThreadPriority(mThread
, PR_PRIORITY_LOW
);
411 void StorageDBThread::ThreadFunc(void* aArg
) {
413 auto queue
= MakeRefPtr
<ThreadEventQueue
>(MakeUnique
<EventQueue
>());
414 Unused
<< nsThreadManager::get().CreateCurrentThread(
415 queue
, nsThread::NOT_MAIN_THREAD
);
418 AUTO_PROFILER_REGISTER_THREAD("localStorage DB");
419 NS_SetCurrentThreadName("localStorage DB");
420 mozilla::IOInterposer::RegisterCurrentThread();
422 StorageDBThread
* thread
= static_cast<StorageDBThread
*>(aArg
);
423 thread
->ThreadFunc();
424 mozilla::IOInterposer::UnregisterCurrentThread();
427 void StorageDBThread::ThreadFunc() {
428 nsresult rv
= InitDatabase();
430 MonitorAutoLock
lockMonitor(mThreadObserver
->GetMonitor());
434 mStopIOThread
= true;
438 // Create an nsIThread for the current PRThread, so we can observe runnables
440 nsCOMPtr
<nsIThread
> thread
= NS_GetCurrentThread();
441 nsCOMPtr
<nsIThreadInternal
> threadInternal
= do_QueryInterface(thread
);
442 MOZ_ASSERT(threadInternal
); // Should always succeed.
443 threadInternal
->SetObserver(mThreadObserver
);
445 while (MOZ_LIKELY(!mStopIOThread
|| mPreloads
.Length() ||
446 mPendingTasks
.HasTasks() ||
447 mThreadObserver
->HasPendingEvents())) {
448 // Process xpcom events first.
449 while (MOZ_UNLIKELY(mThreadObserver
->HasPendingEvents())) {
450 mThreadObserver
->ClearPendingEvents();
451 MonitorAutoUnlock
unlock(mThreadObserver
->GetMonitor());
454 rv
= thread
->ProcessNextEvent(false, &processedEvent
);
455 } while (NS_SUCCEEDED(rv
) && processedEvent
);
458 TimeDuration timeUntilFlush
= TimeUntilFlush();
459 if (MOZ_UNLIKELY(timeUntilFlush
.IsZero())) {
460 // Flush time is up or flush has been forced, do it now.
462 if (mPendingTasks
.Prepare()) {
464 MonitorAutoUnlock
unlockMonitor(mThreadObserver
->GetMonitor());
465 rv
= mPendingTasks
.Execute(this);
468 if (!mPendingTasks
.Finalize(rv
)) {
470 NS_WARNING("localStorage DB access broken");
473 NotifyFlushCompletion();
474 } else if (MOZ_LIKELY(mPreloads
.Length())) {
475 UniquePtr
<DBOperation
> op(mPreloads
[0]);
476 mPreloads
.RemoveElementAt(0);
478 MonitorAutoUnlock
unlockMonitor(mThreadObserver
->GetMonitor());
479 op
->PerformAndFinalize(this);
482 if (op
->Type() == DBOperation::opPreloadUrgent
) {
483 SetDefaultPriority(); // urgent preload unscheduled
485 } else if (MOZ_UNLIKELY(!mStopIOThread
)) {
486 AUTO_PROFILER_LABEL("StorageDBThread::ThreadFunc::Wait", IDLE
);
487 lockMonitor
.Wait(timeUntilFlush
);
491 mStatus
= ShutdownDatabase();
493 if (threadInternal
) {
494 threadInternal
->SetObserver(nullptr);
498 NS_IMPL_ISUPPORTS(StorageDBThread::ThreadObserver
, nsIThreadObserver
)
501 StorageDBThread::ThreadObserver::OnDispatchedEvent() {
502 MonitorAutoLock
lock(mMonitor
);
503 mHasPendingEvents
= true;
509 StorageDBThread::ThreadObserver::OnProcessNextEvent(nsIThreadInternal
* aThread
,
515 StorageDBThread::ThreadObserver::AfterProcessNextEvent(
516 nsIThreadInternal
* aThread
, bool eventWasProcessed
) {
520 nsresult
StorageDBThread::OpenDatabaseConnection() {
523 MOZ_ASSERT(!NS_IsMainThread());
525 nsCOMPtr
<mozIStorageService
> service
=
526 do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID
, &rv
);
527 NS_ENSURE_SUCCESS(rv
, rv
);
529 if (mPrivateBrowsingId
== 0) {
530 MOZ_ASSERT(mDatabaseFile
);
532 rv
= service
->OpenUnsharedDatabase(mDatabaseFile
,
533 mozIStorageService::CONNECTION_DEFAULT
,
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 mozIStorageService::CONNECTION_DEFAULT
,
541 getter_AddRefs(mWorkerConnection
));
544 MOZ_ASSERT(mPrivateBrowsingId
== 1);
546 rv
= service
->OpenSpecialDatabase(kMozStorageMemoryStorageKey
,
548 mozIStorageService::CONNECTION_DEFAULT
,
549 getter_AddRefs(mWorkerConnection
));
551 NS_ENSURE_SUCCESS(rv
, rv
);
556 nsresult
StorageDBThread::OpenAndUpdateDatabase() {
559 // Here we are on the worker thread. This opens the worker connection.
560 MOZ_ASSERT(!NS_IsMainThread());
562 rv
= OpenDatabaseConnection();
563 NS_ENSURE_SUCCESS(rv
, rv
);
565 // SQLite doesn't support WAL journals for in-memory databases.
566 if (mPrivateBrowsingId
== 0) {
567 rv
= TryJournalMode();
568 NS_ENSURE_SUCCESS(rv
, rv
);
574 nsresult
StorageDBThread::InitDatabase() {
577 // Here we are on the worker thread. This opens the worker connection.
578 MOZ_ASSERT(!NS_IsMainThread());
580 rv
= OpenAndUpdateDatabase();
581 NS_ENSURE_SUCCESS(rv
, rv
);
583 rv
= StorageDBUpdater::Update(mWorkerConnection
);
585 if (mPrivateBrowsingId
== 0) {
586 // Update has failed, rather throw the database away and try
587 // opening and setting it up again.
588 rv
= mWorkerConnection
->Close();
589 mWorkerConnection
= nullptr;
590 NS_ENSURE_SUCCESS(rv
, rv
);
592 rv
= mDatabaseFile
->Remove(false);
593 NS_ENSURE_SUCCESS(rv
, rv
);
595 rv
= OpenAndUpdateDatabase();
597 NS_ENSURE_SUCCESS(rv
, rv
);
600 // Create a read-only clone
601 (void)mWorkerConnection
->Clone(true, getter_AddRefs(mReaderConnection
));
602 NS_ENSURE_TRUE(mReaderConnection
, NS_ERROR_FAILURE
);
604 // Database open and all initiation operation are done. Switching this flag
605 // to true allow main thread to read directly from the database. If we would
606 // allow this sooner, we would have opened a window where main thread read
607 // might operate on a totally broken and incosistent database.
610 // List scopes having any stored data
611 nsCOMPtr
<mozIStorageStatement
> stmt
;
612 // Note: result of this select must match StorageManager::CreateOrigin()
613 rv
= mWorkerConnection
->CreateStatement(
614 nsLiteralCString("SELECT DISTINCT originAttributes || ':' || originKey "
615 "FROM webappsstore2"),
616 getter_AddRefs(stmt
));
617 NS_ENSURE_SUCCESS(rv
, rv
);
618 mozStorageStatementScoper
scope(stmt
);
621 while (NS_SUCCEEDED(rv
= stmt
->ExecuteStep(&exists
)) && exists
) {
622 nsAutoCString foundOrigin
;
623 rv
= stmt
->GetUTF8String(0, foundOrigin
);
624 NS_ENSURE_SUCCESS(rv
, rv
);
626 MonitorAutoLock
monitor(mThreadObserver
->GetMonitor());
627 mOriginsHavingData
.Insert(foundOrigin
);
633 nsresult
StorageDBThread::SetJournalMode(bool aIsWal
) {
636 nsAutoCString
stmtString(MOZ_STORAGE_UNIQUIFY_QUERY_STR
637 "PRAGMA journal_mode = ");
639 stmtString
.AppendLiteral("wal");
641 stmtString
.AppendLiteral("truncate");
644 nsCOMPtr
<mozIStorageStatement
> stmt
;
645 rv
= mWorkerConnection
->CreateStatement(stmtString
, getter_AddRefs(stmt
));
646 NS_ENSURE_SUCCESS(rv
, rv
);
647 mozStorageStatementScoper
scope(stmt
);
649 bool hasResult
= false;
650 rv
= stmt
->ExecuteStep(&hasResult
);
651 NS_ENSURE_SUCCESS(rv
, rv
);
653 return NS_ERROR_FAILURE
;
656 nsAutoCString journalMode
;
657 rv
= stmt
->GetUTF8String(0, journalMode
);
658 NS_ENSURE_SUCCESS(rv
, rv
);
659 if ((aIsWal
&& !journalMode
.EqualsLiteral("wal")) ||
660 (!aIsWal
&& !journalMode
.EqualsLiteral("truncate"))) {
661 return NS_ERROR_FAILURE
;
667 nsresult
StorageDBThread::TryJournalMode() {
670 rv
= SetJournalMode(true);
672 mWALModeEnabled
= false;
674 rv
= SetJournalMode(false);
675 NS_ENSURE_SUCCESS(rv
, rv
);
677 mWALModeEnabled
= true;
679 rv
= ConfigureWALBehavior();
680 NS_ENSURE_SUCCESS(rv
, rv
);
686 nsresult
StorageDBThread::ConfigureWALBehavior() {
687 // Get the DB's page size
688 nsCOMPtr
<mozIStorageStatement
> stmt
;
689 nsresult rv
= mWorkerConnection
->CreateStatement(
690 nsLiteralCString(MOZ_STORAGE_UNIQUIFY_QUERY_STR
"PRAGMA page_size"),
691 getter_AddRefs(stmt
));
692 NS_ENSURE_SUCCESS(rv
, rv
);
694 bool hasResult
= false;
695 rv
= stmt
->ExecuteStep(&hasResult
);
696 NS_ENSURE_TRUE(NS_SUCCEEDED(rv
) && hasResult
, NS_ERROR_FAILURE
);
698 int32_t pageSize
= 0;
699 rv
= stmt
->GetInt32(0, &pageSize
);
700 NS_ENSURE_TRUE(NS_SUCCEEDED(rv
) && pageSize
> 0, NS_ERROR_UNEXPECTED
);
702 // Set the threshold for auto-checkpointing the WAL.
703 // We don't want giant logs slowing down reads & shutdown.
704 int32_t thresholdInPages
=
705 static_cast<int32_t>(MAX_WAL_SIZE_BYTES
/ pageSize
);
706 nsAutoCString
thresholdPragma("PRAGMA wal_autocheckpoint = ");
707 thresholdPragma
.AppendInt(thresholdInPages
);
708 rv
= mWorkerConnection
->ExecuteSimpleSQL(thresholdPragma
);
709 NS_ENSURE_SUCCESS(rv
, rv
);
711 // Set the maximum WAL log size to reduce footprint on mobile (large empty
712 // WAL files will be truncated)
713 nsAutoCString
journalSizePragma("PRAGMA journal_size_limit = ");
714 // bug 600307: mak recommends setting this to 3 times the auto-checkpoint
716 journalSizePragma
.AppendInt(MAX_WAL_SIZE_BYTES
* 3);
717 rv
= mWorkerConnection
->ExecuteSimpleSQL(journalSizePragma
);
718 NS_ENSURE_SUCCESS(rv
, rv
);
723 nsresult
StorageDBThread::ShutdownDatabase() {
724 // Has to be called on the worker thread.
725 MOZ_ASSERT(!NS_IsMainThread());
727 nsresult rv
= mStatus
;
731 // Finalize the cached statements.
732 mReaderStatements
.FinalizeStatements();
733 mWorkerStatements
.FinalizeStatements();
735 if (mReaderConnection
) {
736 // No need to sync access to mReaderConnection since the main thread
737 // is right now joining this thread, unable to execute any events.
738 mReaderConnection
->Close();
739 mReaderConnection
= nullptr;
742 if (mWorkerConnection
) {
743 rv
= mWorkerConnection
->Close();
744 mWorkerConnection
= nullptr;
750 void StorageDBThread::ScheduleFlush() {
752 return; // Already scheduled
755 // Must be non-zero to indicate we are scheduled
756 mDirtyEpoch
= TimeStamp::Now();
758 // Wake the monitor from indefinite sleep...
759 (mThreadObserver
->GetMonitor()).Notify();
762 void StorageDBThread::UnscheduleFlush() {
763 // We are just about to do the flush, drop flags
764 mFlushImmediately
= false;
765 mDirtyEpoch
= TimeStamp();
768 TimeDuration
StorageDBThread::TimeUntilFlush() {
769 if (mFlushImmediately
) {
770 return 0; // Do it now regardless the timeout.
774 return TimeDuration::Forever(); // No pending task...
777 TimeStamp now
= TimeStamp::Now();
778 TimeDuration age
= now
- mDirtyEpoch
;
779 static const TimeDuration kMaxAge
=
780 TimeDuration::FromMilliseconds(FLUSHING_INTERVAL_MS
);
782 return 0; // It is time.
785 return kMaxAge
- age
; // Time left. This is used to sleep the monitor.
788 void StorageDBThread::NotifyFlushCompletion() {
789 #ifdef DOM_STORAGE_TESTS
790 if (!NS_IsMainThread()) {
791 RefPtr
<nsRunnableMethod
<StorageDBThread
, void, false>> event
=
792 NewNonOwningRunnableMethod(
793 "dom::StorageDBThread::NotifyFlushCompletion", this,
794 &StorageDBThread::NotifyFlushCompletion
);
795 NS_DispatchToMainThread(event
);
799 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
801 obs
->NotifyObservers(nullptr, "domstorage-test-flushed", nullptr);
806 // Helper SQL function classes
810 class OriginAttrsPatternMatchSQLFunction final
: public mozIStorageFunction
{
812 NS_DECL_MOZISTORAGEFUNCTION
814 explicit OriginAttrsPatternMatchSQLFunction(
815 OriginAttributesPattern
const& aPattern
)
816 : mPattern(aPattern
) {}
819 OriginAttrsPatternMatchSQLFunction() = delete;
820 ~OriginAttrsPatternMatchSQLFunction() = default;
822 OriginAttributesPattern mPattern
;
825 NS_IMPL_ISUPPORTS(OriginAttrsPatternMatchSQLFunction
, mozIStorageFunction
)
828 OriginAttrsPatternMatchSQLFunction::OnFunctionCall(
829 mozIStorageValueArray
* aFunctionArguments
, nsIVariant
** aResult
) {
832 nsAutoCString suffix
;
833 rv
= aFunctionArguments
->GetUTF8String(0, suffix
);
834 NS_ENSURE_SUCCESS(rv
, rv
);
837 bool success
= oa
.PopulateFromSuffix(suffix
);
838 NS_ENSURE_TRUE(success
, NS_ERROR_FAILURE
);
839 bool result
= mPattern
.Matches(oa
);
841 RefPtr
<nsVariant
> outVar(new nsVariant());
842 rv
= outVar
->SetAsBool(result
);
843 NS_ENSURE_SUCCESS(rv
, rv
);
845 outVar
.forget(aResult
);
851 // StorageDBThread::DBOperation
853 StorageDBThread::DBOperation::DBOperation(const OperationType aType
,
854 LocalStorageCacheBridge
* aCache
,
855 const nsAString
& aKey
,
856 const nsAString
& aValue
)
857 : mType(aType
), mCache(aCache
), mKey(aKey
), mValue(aValue
) {
858 MOZ_ASSERT(mType
== opPreload
|| mType
== opPreloadUrgent
||
859 mType
== opAddItem
|| mType
== opUpdateItem
||
860 mType
== opRemoveItem
|| mType
== opClear
|| mType
== opClearAll
);
861 MOZ_COUNT_CTOR(StorageDBThread::DBOperation
);
864 StorageDBThread::DBOperation::DBOperation(const OperationType aType
,
865 StorageUsageBridge
* aUsage
)
866 : mType(aType
), mUsage(aUsage
) {
867 MOZ_ASSERT(mType
== opGetUsage
);
868 MOZ_COUNT_CTOR(StorageDBThread::DBOperation
);
871 StorageDBThread::DBOperation::DBOperation(const OperationType aType
,
872 const nsACString
& aOriginNoSuffix
)
873 : mType(aType
), mCache(nullptr), mOrigin(aOriginNoSuffix
) {
874 MOZ_ASSERT(mType
== opClearMatchingOrigin
);
875 MOZ_COUNT_CTOR(StorageDBThread::DBOperation
);
878 StorageDBThread::DBOperation::DBOperation(
879 const OperationType aType
, const OriginAttributesPattern
& aOriginNoSuffix
)
880 : mType(aType
), mCache(nullptr), mOriginPattern(aOriginNoSuffix
) {
881 MOZ_ASSERT(mType
== opClearMatchingOriginAttributes
);
882 MOZ_COUNT_CTOR(StorageDBThread::DBOperation
);
885 StorageDBThread::DBOperation::~DBOperation() {
886 MOZ_COUNT_DTOR(StorageDBThread::DBOperation
);
889 const nsCString
StorageDBThread::DBOperation::OriginNoSuffix() const {
891 return mCache
->OriginNoSuffix();
897 const nsCString
StorageDBThread::DBOperation::OriginSuffix() const {
899 return mCache
->OriginSuffix();
905 const nsCString
StorageDBThread::DBOperation::Origin() const {
907 return mCache
->Origin();
913 const nsCString
StorageDBThread::DBOperation::Target() const {
918 return Origin() + "|"_ns
+ NS_ConvertUTF16toUTF8(mKey
);
925 void StorageDBThread::DBOperation::PerformAndFinalize(
926 StorageDBThread
* aThread
) {
927 Finalize(Perform(aThread
));
930 nsresult
StorageDBThread::DBOperation::Perform(StorageDBThread
* aThread
) {
935 case opPreloadUrgent
: {
937 if (mCache
->Loaded()) {
941 StatementCache
* statements
;
942 if (MOZ_UNLIKELY(::mozilla::ipc::IsOnBackgroundThread())) {
943 statements
= &aThread
->mReaderStatements
;
945 statements
= &aThread
->mWorkerStatements
;
948 // OFFSET is an optimization when we have to do a sync load
949 // and cache has already loaded some parts asynchronously.
950 // It skips keys we have already loaded.
951 nsCOMPtr
<mozIStorageStatement
> stmt
= statements
->GetCachedStatement(
952 "SELECT key, value FROM webappsstore2 "
953 "WHERE originAttributes = :originAttributes AND originKey = "
955 "ORDER BY key LIMIT -1 OFFSET :offset");
956 NS_ENSURE_STATE(stmt
);
957 mozStorageStatementScoper
scope(stmt
);
959 rv
= stmt
->BindUTF8StringByName("originAttributes"_ns
,
960 mCache
->OriginSuffix());
961 NS_ENSURE_SUCCESS(rv
, rv
);
963 rv
= stmt
->BindUTF8StringByName("originKey"_ns
, mCache
->OriginNoSuffix());
964 NS_ENSURE_SUCCESS(rv
, rv
);
966 rv
= stmt
->BindInt32ByName("offset"_ns
,
967 static_cast<int32_t>(mCache
->LoadedCount()));
968 NS_ENSURE_SUCCESS(rv
, rv
);
971 while (NS_SUCCEEDED(rv
= stmt
->ExecuteStep(&exists
)) && exists
) {
973 rv
= stmt
->GetString(0, key
);
974 NS_ENSURE_SUCCESS(rv
, rv
);
977 rv
= stmt
->GetString(1, value
);
978 NS_ENSURE_SUCCESS(rv
, rv
);
980 if (!mCache
->LoadItem(key
, value
)) {
984 // The loop condition's call to ExecuteStep() may have terminated because
985 // !NS_SUCCEEDED(), we need an early return to cover that case. This also
986 // covers success cases as well, but that's inductively safe.
987 NS_ENSURE_SUCCESS(rv
, rv
);
992 // Bug 1676410 fixed a regression caused by bug 1165214. However, it
993 // turns out that 100% correct checking of the eTLD+1 usage is not
994 // possible to recover easily, see bug 1683299.
996 // This is how it should be done, but due to other problems like lack
997 // of usage synchronization between content processes, we temporarily
998 // disabled the matching using "%".
1000 nsCOMPtr
<mozIStorageStatement
> stmt
=
1001 aThread
->mWorkerStatements
.GetCachedStatement(
1002 "SELECT SUM(LENGTH(key) + LENGTH(value)) FROM webappsstore2 "
1003 "WHERE (originAttributes || ':' || originKey) LIKE :usageOrigin "
1005 NS_ENSURE_STATE(stmt
);
1007 mozStorageStatementScoper
scope(stmt
);
1009 // The database schema is built around cleverly reversing domain names
1010 // (the "originKey") so that we can efficiently group usage by eTLD+1.
1011 // "foo.example.org" has an eTLD+1 of ".example.org". They reverse to
1012 // "gro.elpmaxe.oof" and "gro.elpmaxe." respectively, noting that the
1013 // reversed eTLD+1 is a prefix of its reversed sub-domain. To this end,
1014 // we can calculate all of the usage for an eTLD+1 by summing up all the
1015 // rows which have the reversed eTLD+1 as a prefix. In SQL we can
1016 // accomplish this using LIKE which provides for case-insensitive
1017 // matching with "_" as a single-character wildcard match and "%" any
1018 // sequence of zero or more characters. So by suffixing the reversed
1019 // eTLD+1 and using "%" we get our case-insensitive (domain names are
1020 // case-insensitive) matching. Note that although legal domain names
1021 // don't include "_" or "%", file origins can include them, so we need
1022 // to escape our OriginScope for correctness.
1023 nsAutoCString originScopeEscaped
;
1024 rv
= stmt
->EscapeUTF8StringForLIKE(mUsage
->OriginScope(), '\\',
1025 originScopeEscaped
);
1026 NS_ENSURE_SUCCESS(rv
, rv
);
1028 rv
= stmt
->BindUTF8StringByName("usageOrigin"_ns
,
1029 originScopeEscaped
+ "%"_ns
);
1030 NS_ENSURE_SUCCESS(rv
, rv
);
1032 // This is the code before bug 1676410 and bug 1676973. The returned
1033 // usage will be zero in most of the cases, but due to lack of usage
1034 // synchronization between content processes we have to live with this
1035 // semi-broken behaviour because it causes less harm than the matching
1038 nsCOMPtr
<mozIStorageStatement
> stmt
=
1039 aThread
->mWorkerStatements
.GetCachedStatement(
1040 "SELECT SUM(LENGTH(key) + LENGTH(value)) FROM webappsstore2 "
1041 "WHERE (originAttributes || ':' || originKey) LIKE :usageOrigin");
1042 NS_ENSURE_STATE(stmt
);
1044 mozStorageStatementScoper
scope(stmt
);
1046 rv
= stmt
->BindUTF8StringByName("usageOrigin"_ns
, mUsage
->OriginScope());
1047 NS_ENSURE_SUCCESS(rv
, rv
);
1051 rv
= stmt
->ExecuteStep(&exists
);
1052 NS_ENSURE_SUCCESS(rv
, rv
);
1056 rv
= stmt
->GetInt64(0, &usage
);
1057 NS_ENSURE_SUCCESS(rv
, rv
);
1060 mUsage
->LoadUsage(usage
);
1065 case opUpdateItem
: {
1066 MOZ_ASSERT(!NS_IsMainThread());
1068 nsCOMPtr
<mozIStorageStatement
> stmt
=
1069 aThread
->mWorkerStatements
.GetCachedStatement(
1070 "INSERT OR REPLACE INTO webappsstore2 (originAttributes, "
1071 "originKey, scope, key, value) "
1072 "VALUES (:originAttributes, :originKey, :scope, :key, :value) ");
1073 NS_ENSURE_STATE(stmt
);
1075 mozStorageStatementScoper
scope(stmt
);
1077 rv
= stmt
->BindUTF8StringByName("originAttributes"_ns
,
1078 mCache
->OriginSuffix());
1079 NS_ENSURE_SUCCESS(rv
, rv
);
1080 rv
= stmt
->BindUTF8StringByName("originKey"_ns
, mCache
->OriginNoSuffix());
1081 NS_ENSURE_SUCCESS(rv
, rv
);
1082 // Filling the 'scope' column just for downgrade compatibility reasons
1083 rv
= stmt
->BindUTF8StringByName(
1085 Scheme0Scope(mCache
->OriginSuffix(), mCache
->OriginNoSuffix()));
1086 NS_ENSURE_SUCCESS(rv
, rv
);
1087 rv
= stmt
->BindStringByName("key"_ns
, mKey
);
1088 NS_ENSURE_SUCCESS(rv
, rv
);
1089 rv
= stmt
->BindStringByName("value"_ns
, mValue
);
1090 NS_ENSURE_SUCCESS(rv
, rv
);
1092 rv
= stmt
->Execute();
1093 NS_ENSURE_SUCCESS(rv
, rv
);
1095 MonitorAutoLock
monitor(aThread
->mThreadObserver
->GetMonitor());
1096 aThread
->mOriginsHavingData
.Insert(Origin());
1100 case opRemoveItem
: {
1101 MOZ_ASSERT(!NS_IsMainThread());
1103 nsCOMPtr
<mozIStorageStatement
> stmt
=
1104 aThread
->mWorkerStatements
.GetCachedStatement(
1105 "DELETE FROM webappsstore2 "
1106 "WHERE originAttributes = :originAttributes AND originKey = "
1109 NS_ENSURE_STATE(stmt
);
1110 mozStorageStatementScoper
scope(stmt
);
1112 rv
= stmt
->BindUTF8StringByName("originAttributes"_ns
,
1113 mCache
->OriginSuffix());
1114 NS_ENSURE_SUCCESS(rv
, rv
);
1115 rv
= stmt
->BindUTF8StringByName("originKey"_ns
, mCache
->OriginNoSuffix());
1116 NS_ENSURE_SUCCESS(rv
, rv
);
1117 rv
= stmt
->BindStringByName("key"_ns
, mKey
);
1118 NS_ENSURE_SUCCESS(rv
, rv
);
1120 rv
= stmt
->Execute();
1121 NS_ENSURE_SUCCESS(rv
, rv
);
1127 MOZ_ASSERT(!NS_IsMainThread());
1129 nsCOMPtr
<mozIStorageStatement
> stmt
=
1130 aThread
->mWorkerStatements
.GetCachedStatement(
1131 "DELETE FROM webappsstore2 "
1132 "WHERE originAttributes = :originAttributes AND originKey = "
1134 NS_ENSURE_STATE(stmt
);
1135 mozStorageStatementScoper
scope(stmt
);
1137 rv
= stmt
->BindUTF8StringByName("originAttributes"_ns
,
1138 mCache
->OriginSuffix());
1139 NS_ENSURE_SUCCESS(rv
, rv
);
1140 rv
= stmt
->BindUTF8StringByName("originKey"_ns
, mCache
->OriginNoSuffix());
1141 NS_ENSURE_SUCCESS(rv
, rv
);
1143 rv
= stmt
->Execute();
1144 NS_ENSURE_SUCCESS(rv
, rv
);
1146 MonitorAutoLock
monitor(aThread
->mThreadObserver
->GetMonitor());
1147 aThread
->mOriginsHavingData
.Remove(Origin());
1152 MOZ_ASSERT(!NS_IsMainThread());
1154 nsCOMPtr
<mozIStorageStatement
> stmt
=
1155 aThread
->mWorkerStatements
.GetCachedStatement(
1156 "DELETE FROM webappsstore2");
1157 NS_ENSURE_STATE(stmt
);
1158 mozStorageStatementScoper
scope(stmt
);
1160 rv
= stmt
->Execute();
1161 NS_ENSURE_SUCCESS(rv
, rv
);
1163 MonitorAutoLock
monitor(aThread
->mThreadObserver
->GetMonitor());
1164 aThread
->mOriginsHavingData
.Clear();
1168 case opClearMatchingOrigin
: {
1169 MOZ_ASSERT(!NS_IsMainThread());
1171 nsCOMPtr
<mozIStorageStatement
> stmt
=
1172 aThread
->mWorkerStatements
.GetCachedStatement(
1173 "DELETE FROM webappsstore2"
1174 " WHERE originKey GLOB :scope");
1175 NS_ENSURE_STATE(stmt
);
1176 mozStorageStatementScoper
scope(stmt
);
1178 rv
= stmt
->BindUTF8StringByName("scope"_ns
, mOrigin
+ "*"_ns
);
1179 NS_ENSURE_SUCCESS(rv
, rv
);
1181 rv
= stmt
->Execute();
1182 NS_ENSURE_SUCCESS(rv
, rv
);
1184 // No need to selectively clear mOriginsHavingData here. That hashtable
1185 // only prevents preload for scopes with no data. Leaving a false record
1186 // in it has a negligible effect on performance.
1190 case opClearMatchingOriginAttributes
: {
1191 MOZ_ASSERT(!NS_IsMainThread());
1193 // Register the ORIGIN_ATTRS_PATTERN_MATCH function, initialized with the
1195 nsCOMPtr
<mozIStorageFunction
> patternMatchFunction(
1196 new OriginAttrsPatternMatchSQLFunction(mOriginPattern
));
1198 rv
= aThread
->mWorkerConnection
->CreateFunction(
1199 "ORIGIN_ATTRS_PATTERN_MATCH"_ns
, 1, patternMatchFunction
);
1200 NS_ENSURE_SUCCESS(rv
, rv
);
1202 nsCOMPtr
<mozIStorageStatement
> stmt
=
1203 aThread
->mWorkerStatements
.GetCachedStatement(
1204 "DELETE FROM webappsstore2"
1205 " WHERE ORIGIN_ATTRS_PATTERN_MATCH(originAttributes)");
1208 mozStorageStatementScoper
scope(stmt
);
1209 rv
= stmt
->Execute();
1211 rv
= NS_ERROR_UNEXPECTED
;
1214 // Always remove the function
1215 aThread
->mWorkerConnection
->RemoveFunction(
1216 "ORIGIN_ATTRS_PATTERN_MATCH"_ns
);
1218 NS_ENSURE_SUCCESS(rv
, rv
);
1220 // No need to selectively clear mOriginsHavingData here. That hashtable
1221 // only prevents preload for scopes with no data. Leaving a false record
1222 // in it has a negligible effect on performance.
1227 NS_ERROR("Unknown task type");
1234 void StorageDBThread::DBOperation::Finalize(nsresult aRv
) {
1236 case opPreloadUrgent
:
1238 if (NS_FAILED(aRv
)) {
1239 // When we are here, something failed when loading from the database.
1240 // Notify that the storage is loaded to prevent deadlock of the main
1241 // thread, even though it is actually empty or incomplete.
1242 NS_WARNING("Failed to preload localStorage");
1245 mCache
->LoadDone(aRv
);
1249 if (NS_FAILED(aRv
)) {
1250 mUsage
->LoadUsage(0);
1256 if (NS_FAILED(aRv
)) {
1258 "localStorage update/clear operation failed,"
1259 " data may not persist or clean up");
1266 // StorageDBThread::PendingOperations
1268 StorageDBThread::PendingOperations::PendingOperations()
1269 : mFlushFailureCount(0) {}
1271 bool StorageDBThread::PendingOperations::HasTasks() const {
1272 return !!mUpdates
.Count() || !!mClears
.Count();
1277 bool OriginPatternMatches(const nsACString
& aOriginSuffix
,
1278 const OriginAttributesPattern
& aPattern
) {
1279 OriginAttributes oa
;
1280 DebugOnly
<bool> rv
= oa
.PopulateFromSuffix(aOriginSuffix
);
1282 return aPattern
.Matches(oa
);
1287 bool StorageDBThread::PendingOperations::CheckForCoalesceOpportunity(
1288 DBOperation
* aNewOp
, DBOperation::OperationType aPendingType
,
1289 DBOperation::OperationType aNewType
) {
1290 if (aNewOp
->Type() != aNewType
) {
1294 StorageDBThread::DBOperation
* pendingTask
;
1295 if (!mUpdates
.Get(aNewOp
->Target(), &pendingTask
)) {
1299 if (pendingTask
->Type() != aPendingType
) {
1306 void StorageDBThread::PendingOperations::Add(
1307 UniquePtr
<StorageDBThread::DBOperation
> aOperation
) {
1308 // Optimize: when a key to remove has never been written to disk
1309 // just bypass this operation. A key is new when an operation scheduled
1310 // to write it to the database is of type opAddItem.
1311 if (CheckForCoalesceOpportunity(aOperation
.get(), DBOperation::opAddItem
,
1312 DBOperation::opRemoveItem
)) {
1313 mUpdates
.Remove(aOperation
->Target());
1317 // Optimize: when changing a key that is new and has never been
1318 // written to disk, keep type of the operation to store it at opAddItem.
1319 // This allows optimization to just forget adding a new key when
1320 // it is removed from the storage before flush.
1321 if (CheckForCoalesceOpportunity(aOperation
.get(), DBOperation::opAddItem
,
1322 DBOperation::opUpdateItem
)) {
1323 aOperation
->mType
= DBOperation::opAddItem
;
1326 // Optimize: to prevent lose of remove operation on a key when doing
1327 // remove/set/remove on a previously existing key we have to change
1328 // opAddItem to opUpdateItem on the new operation when there is opRemoveItem
1329 // pending for the key.
1330 if (CheckForCoalesceOpportunity(aOperation
.get(), DBOperation::opRemoveItem
,
1331 DBOperation::opAddItem
)) {
1332 aOperation
->mType
= DBOperation::opUpdateItem
;
1335 switch (aOperation
->Type()) {
1336 // Operations on single keys
1338 case DBOperation::opAddItem
:
1339 case DBOperation::opUpdateItem
:
1340 case DBOperation::opRemoveItem
:
1341 // Override any existing operation for the target (=scope+key).
1342 mUpdates
.InsertOrUpdate(aOperation
->Target(), std::move(aOperation
));
1347 case DBOperation::opClear
:
1348 case DBOperation::opClearMatchingOrigin
:
1349 case DBOperation::opClearMatchingOriginAttributes
:
1350 // Drop all update (insert/remove) operations for equivavelent or matching
1351 // scope. We do this as an optimization as well as a must based on the
1352 // logic, if we would not delete the update tasks, changes would have been
1353 // stored to the database after clear operations have been executed.
1354 for (auto iter
= mUpdates
.Iter(); !iter
.Done(); iter
.Next()) {
1355 const auto& pendingTask
= iter
.Data();
1357 if (aOperation
->Type() == DBOperation::opClear
&&
1358 (pendingTask
->OriginNoSuffix() != aOperation
->OriginNoSuffix() ||
1359 pendingTask
->OriginSuffix() != aOperation
->OriginSuffix())) {
1363 if (aOperation
->Type() == DBOperation::opClearMatchingOrigin
&&
1364 !StringBeginsWith(pendingTask
->OriginNoSuffix(),
1365 aOperation
->Origin())) {
1369 if (aOperation
->Type() ==
1370 DBOperation::opClearMatchingOriginAttributes
&&
1371 !OriginPatternMatches(pendingTask
->OriginSuffix(),
1372 aOperation
->OriginPattern())) {
1379 mClears
.InsertOrUpdate(aOperation
->Target(), std::move(aOperation
));
1382 case DBOperation::opClearAll
:
1383 // Drop simply everything, this is a super-operation.
1386 mClears
.InsertOrUpdate(aOperation
->Target(), std::move(aOperation
));
1395 bool StorageDBThread::PendingOperations::Prepare() {
1396 // Called under the lock
1398 // First collect clear operations and then updates, we can
1399 // do this since whenever a clear operation for a scope is
1400 // scheduled, we drop all updates matching that scope. So,
1401 // all scope-related update operations we have here now were
1402 // scheduled after the clear operations.
1403 for (auto iter
= mClears
.Iter(); !iter
.Done(); iter
.Next()) {
1404 mExecList
.AppendElement(std::move(iter
.Data()));
1408 for (auto iter
= mUpdates
.Iter(); !iter
.Done(); iter
.Next()) {
1409 mExecList
.AppendElement(std::move(iter
.Data()));
1413 return !!mExecList
.Length();
1416 nsresult
StorageDBThread::PendingOperations::Execute(StorageDBThread
* aThread
) {
1417 // Called outside the lock
1419 mozStorageTransaction
transaction(aThread
->mWorkerConnection
, false);
1421 nsresult rv
= transaction
.Start();
1422 if (NS_FAILED(rv
)) {
1426 for (uint32_t i
= 0; i
< mExecList
.Length(); ++i
) {
1427 const auto& task
= mExecList
[i
];
1428 rv
= task
->Perform(aThread
);
1429 if (NS_FAILED(rv
)) {
1434 rv
= transaction
.Commit();
1435 if (NS_FAILED(rv
)) {
1442 bool StorageDBThread::PendingOperations::Finalize(nsresult aRv
) {
1443 // Called under the lock
1445 // The list is kept on a failure to retry it
1446 if (NS_FAILED(aRv
)) {
1447 // XXX Followup: we may try to reopen the database and flush these
1448 // pending tasks, however testing showed that even though I/O is actually
1449 // broken some amount of operations is left in sqlite+system buffers and
1450 // seems like successfully flushed to disk.
1451 // Tested by removing a flash card and disconnecting from network while
1452 // using a network drive on Windows system.
1453 NS_WARNING("Flush operation on localStorage database failed");
1455 ++mFlushFailureCount
;
1457 return mFlushFailureCount
>= 5;
1460 mFlushFailureCount
= 0;
1467 bool FindPendingClearForOrigin(
1468 const nsACString
& aOriginSuffix
, const nsACString
& aOriginNoSuffix
,
1469 StorageDBThread::DBOperation
* aPendingOperation
) {
1470 if (aPendingOperation
->Type() == StorageDBThread::DBOperation::opClearAll
) {
1474 if (aPendingOperation
->Type() == StorageDBThread::DBOperation::opClear
&&
1475 aOriginNoSuffix
== aPendingOperation
->OriginNoSuffix() &&
1476 aOriginSuffix
== aPendingOperation
->OriginSuffix()) {
1480 if (aPendingOperation
->Type() ==
1481 StorageDBThread::DBOperation::opClearMatchingOrigin
&&
1482 StringBeginsWith(aOriginNoSuffix
, aPendingOperation
->Origin())) {
1486 if (aPendingOperation
->Type() ==
1487 StorageDBThread::DBOperation::opClearMatchingOriginAttributes
&&
1488 OriginPatternMatches(aOriginSuffix
, aPendingOperation
->OriginPattern())) {
1497 bool StorageDBThread::PendingOperations::IsOriginClearPending(
1498 const nsACString
& aOriginSuffix
, const nsACString
& aOriginNoSuffix
) const {
1499 // Called under the lock
1501 for (const auto& clear
: mClears
.Values()) {
1502 if (FindPendingClearForOrigin(aOriginSuffix
, aOriginNoSuffix
,
1508 for (uint32_t i
= 0; i
< mExecList
.Length(); ++i
) {
1509 if (FindPendingClearForOrigin(aOriginSuffix
, aOriginNoSuffix
,
1510 mExecList
[i
].get())) {
1520 bool FindPendingUpdateForOrigin(
1521 const nsACString
& aOriginSuffix
, const nsACString
& aOriginNoSuffix
,
1522 StorageDBThread::DBOperation
* aPendingOperation
) {
1523 if ((aPendingOperation
->Type() == StorageDBThread::DBOperation::opAddItem
||
1524 aPendingOperation
->Type() ==
1525 StorageDBThread::DBOperation::opUpdateItem
||
1526 aPendingOperation
->Type() ==
1527 StorageDBThread::DBOperation::opRemoveItem
) &&
1528 aOriginNoSuffix
== aPendingOperation
->OriginNoSuffix() &&
1529 aOriginSuffix
== aPendingOperation
->OriginSuffix()) {
1538 bool StorageDBThread::PendingOperations::IsOriginUpdatePending(
1539 const nsACString
& aOriginSuffix
, const nsACString
& aOriginNoSuffix
) const {
1540 // Called under the lock
1542 for (const auto& update
: mUpdates
.Values()) {
1543 if (FindPendingUpdateForOrigin(aOriginSuffix
, aOriginNoSuffix
,
1549 for (uint32_t i
= 0; i
< mExecList
.Length(); ++i
) {
1550 if (FindPendingUpdateForOrigin(aOriginSuffix
, aOriginNoSuffix
,
1551 mExecList
[i
].get())) {
1559 nsresult
StorageDBThread::InitHelper::SyncDispatchAndReturnProfilePath(
1560 nsAString
& aProfilePath
) {
1561 ::mozilla::ipc::AssertIsOnBackgroundThread();
1563 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
1565 mozilla::MutexAutoLock
autolock(mMutex
);
1570 if (NS_WARN_IF(NS_FAILED(mMainThreadResultCode
))) {
1571 return mMainThreadResultCode
;
1574 aProfilePath
= mProfilePath
;
1579 StorageDBThread::InitHelper::Run() {
1580 MOZ_ASSERT(NS_IsMainThread());
1582 nsresult rv
= GetProfilePath(mProfilePath
);
1583 if (NS_WARN_IF(NS_FAILED(rv
))) {
1584 mMainThreadResultCode
= rv
;
1587 mozilla::MutexAutoLock
lock(mMutex
);
1588 MOZ_ASSERT(mWaiting
);
1597 StorageDBThread::NoteBackgroundThreadRunnable::Run() {
1598 MOZ_ASSERT(NS_IsMainThread());
1600 StorageObserver
* observer
= StorageObserver::Self();
1601 MOZ_ASSERT(observer
);
1603 observer
->NoteBackgroundThread(mPrivateBrowsingId
, mOwningThread
);
1609 StorageDBThread::ShutdownRunnable::Run() {
1610 if (NS_IsMainThread()) {
1616 ::mozilla::ipc::AssertIsOnBackgroundThread();
1617 MOZ_RELEASE_ASSERT(mPrivateBrowsingId
< kPrivateBrowsingIdCount
);
1619 StorageDBThread
*& storageThread
= sStorageThread
[mPrivateBrowsingId
];
1620 if (storageThread
) {
1621 sStorageThreadDown
[mPrivateBrowsingId
] = true;
1623 storageThread
->Shutdown();
1625 delete storageThread
;
1626 storageThread
= nullptr;
1629 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
1634 } // namespace mozilla::dom