Bug 1696969 [wpt PR 27896] - Allow fuzzy matching for replaced-element-003, a=testonly
[gecko.git] / storage / mozStorageService.cpp
blob57a87dec0dce38454fd54de4a0406bf13a4f9d78
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
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 "mozilla/Attributes.h"
8 #include "mozilla/DebugOnly.h"
9 #include "mozilla/SpinEventLoopUntil.h"
11 #include "mozStorageService.h"
12 #include "mozStorageConnection.h"
13 #include "nsCollationCID.h"
14 #include "nsComponentManagerUtils.h"
15 #include "nsEmbedCID.h"
16 #include "nsExceptionHandler.h"
17 #include "nsThreadUtils.h"
18 #include "mozStoragePrivateHelpers.h"
19 #include "nsIObserverService.h"
20 #include "nsIPropertyBag2.h"
21 #include "mozilla/Services.h"
22 #include "mozilla/LateWriteChecks.h"
23 #include "mozIStorageCompletionCallback.h"
24 #include "mozIStoragePendingStatement.h"
25 #include "mozilla/StaticPrefs_storage.h"
27 #include "sqlite3.h"
28 #include "mozilla/AutoSQLiteLifetime.h"
30 #ifdef XP_WIN
31 // "windows.h" was included and it can #define lots of things we care about...
32 # undef CompareString
33 #endif
35 namespace mozilla {
36 namespace storage {
38 ////////////////////////////////////////////////////////////////////////////////
39 //// Memory Reporting
41 #ifdef MOZ_DMD
42 mozilla::Atomic<size_t> gSqliteMemoryUsed;
43 #endif
45 static int64_t StorageSQLiteDistinguishedAmount() {
46 return ::sqlite3_memory_used();
49 /**
50 * Passes a single SQLite memory statistic to a memory reporter callback.
52 * @param aHandleReport
53 * The callback.
54 * @param aData
55 * The data for the callback.
56 * @param aConn
57 * The SQLite connection.
58 * @param aPathHead
59 * Head of the path for the memory report.
60 * @param aKind
61 * The memory report statistic kind, one of "stmt", "cache" or
62 * "schema".
63 * @param aDesc
64 * The memory report description.
65 * @param aOption
66 * The SQLite constant for getting the measurement.
67 * @param aTotal
68 * The accumulator for the measurement.
70 static void ReportConn(nsIHandleReportCallback* aHandleReport,
71 nsISupports* aData, Connection* aConn,
72 const nsACString& aPathHead, const nsACString& aKind,
73 const nsACString& aDesc, int32_t aOption,
74 size_t* aTotal) {
75 nsCString path(aPathHead);
76 path.Append(aKind);
77 path.AppendLiteral("-used");
79 int32_t val = aConn->getSqliteRuntimeStatus(aOption);
80 aHandleReport->Callback(""_ns, path, nsIMemoryReporter::KIND_HEAP,
81 nsIMemoryReporter::UNITS_BYTES, int64_t(val), aDesc,
82 aData);
83 *aTotal += val;
86 // Warning: To get a Connection's measurements requires holding its lock.
87 // There may be a delay getting the lock if another thread is accessing the
88 // Connection. This isn't very nice if CollectReports is called from the main
89 // thread! But at the time of writing this function is only called when
90 // about:memory is loaded (not, for example, when telemetry pings occur) and
91 // any delays in that case aren't so bad.
92 NS_IMETHODIMP
93 Service::CollectReports(nsIHandleReportCallback* aHandleReport,
94 nsISupports* aData, bool aAnonymize) {
95 size_t totalConnSize = 0;
97 nsTArray<RefPtr<Connection>> connections;
98 getConnections(connections);
100 for (uint32_t i = 0; i < connections.Length(); i++) {
101 RefPtr<Connection>& conn = connections[i];
103 // Someone may have closed the Connection, in which case we skip it.
104 // Note that we have consumers of the synchronous API that are off the
105 // main-thread, like the DOM Cache and IndexedDB, and as such we must be
106 // sure that we have a connection.
107 MutexAutoLock lockedAsyncScope(conn->sharedAsyncExecutionMutex);
108 if (!conn->connectionReady()) {
109 continue;
112 nsCString pathHead("explicit/storage/sqlite/");
113 // This filename isn't privacy-sensitive, and so is never anonymized.
114 pathHead.Append(conn->getFilename());
115 pathHead.Append('/');
117 SQLiteMutexAutoLock lockedScope(conn->sharedDBMutex);
119 constexpr auto stmtDesc =
120 "Memory (approximate) used by all prepared statements used by "
121 "connections to this database."_ns;
122 ReportConn(aHandleReport, aData, conn, pathHead, "stmt"_ns, stmtDesc,
123 SQLITE_DBSTATUS_STMT_USED, &totalConnSize);
125 constexpr auto cacheDesc =
126 "Memory (approximate) used by all pager caches used by connections "
127 "to this database."_ns;
128 ReportConn(aHandleReport, aData, conn, pathHead, "cache"_ns, cacheDesc,
129 SQLITE_DBSTATUS_CACHE_USED_SHARED, &totalConnSize);
131 constexpr auto schemaDesc =
132 "Memory (approximate) used to store the schema for all databases "
133 "associated with connections to this database."_ns;
134 ReportConn(aHandleReport, aData, conn, pathHead, "schema"_ns, schemaDesc,
135 SQLITE_DBSTATUS_SCHEMA_USED, &totalConnSize);
138 #ifdef MOZ_DMD
139 if (::sqlite3_memory_used() != int64_t(gSqliteMemoryUsed)) {
140 NS_WARNING(
141 "memory consumption reported by SQLite doesn't match "
142 "our measurements");
144 #endif
147 int64_t other = ::sqlite3_memory_used() - totalConnSize;
149 MOZ_COLLECT_REPORT("explicit/storage/sqlite/other", KIND_HEAP, UNITS_BYTES,
150 other, "All unclassified sqlite memory.");
152 return NS_OK;
155 ////////////////////////////////////////////////////////////////////////////////
156 //// Service
158 NS_IMPL_ISUPPORTS(Service, mozIStorageService, nsIObserver, nsIMemoryReporter)
160 Service* Service::gService = nullptr;
162 already_AddRefed<Service> Service::getSingleton() {
163 if (gService) {
164 return do_AddRef(gService);
167 // The first reference to the storage service must be obtained on the
168 // main thread.
169 NS_ENSURE_TRUE(NS_IsMainThread(), nullptr);
170 RefPtr<Service> service = new Service();
171 if (NS_SUCCEEDED(service->initialize())) {
172 // Note: This is cleared in the Service destructor.
173 gService = service.get();
174 return service.forget();
177 return nullptr;
180 int Service::AutoVFSRegistration::Init(UniquePtr<sqlite3_vfs> aVFS) {
181 MOZ_ASSERT(!mVFS);
182 if (aVFS) {
183 mVFS = std::move(aVFS);
184 return sqlite3_vfs_register(mVFS.get(), 0);
186 NS_WARNING("Failed to register VFS");
187 return SQLITE_OK;
190 Service::AutoVFSRegistration::~AutoVFSRegistration() {
191 if (mVFS) {
192 int rc = sqlite3_vfs_unregister(mVFS.get());
193 if (rc != SQLITE_OK) {
194 NS_WARNING("Failed to unregister sqlite vfs wrapper.");
199 Service::Service()
200 : mMutex("Service::mMutex"),
201 mRegistrationMutex("Service::mRegistrationMutex"),
202 mConnections() {}
204 Service::~Service() {
205 mozilla::UnregisterWeakMemoryReporter(this);
206 mozilla::UnregisterStorageSQLiteDistinguishedAmount();
208 gService = nullptr;
211 void Service::registerConnection(Connection* aConnection) {
212 mRegistrationMutex.AssertNotCurrentThreadOwns();
213 MutexAutoLock mutex(mRegistrationMutex);
214 (void)mConnections.AppendElement(aConnection);
217 void Service::unregisterConnection(Connection* aConnection) {
218 // If this is the last Connection it might be the only thing keeping Service
219 // alive. So ensure that Service is destroyed only after the Connection is
220 // cleanly unregistered and destroyed.
221 RefPtr<Service> kungFuDeathGrip(this);
222 RefPtr<Connection> forgettingRef;
224 mRegistrationMutex.AssertNotCurrentThreadOwns();
225 MutexAutoLock mutex(mRegistrationMutex);
227 for (uint32_t i = 0; i < mConnections.Length(); ++i) {
228 if (mConnections[i] == aConnection) {
229 // Because dropping the final reference can potentially result in
230 // spinning a nested event loop if the connection was not properly
231 // shutdown, we want to do that outside this loop so that we can finish
232 // mutating the array and drop our mutex.
233 forgettingRef = std::move(mConnections[i]);
234 mConnections.RemoveElementAt(i);
235 break;
240 MOZ_ASSERT(forgettingRef,
241 "Attempt to unregister unknown storage connection!");
243 // Do not proxy the release anywhere, just let this reference drop here. (We
244 // previously did proxy the release, but that was because we invoked Close()
245 // in the destructor and Close() likes to complain if it's not invoked on the
246 // opener thread, so it was essential that the last reference be dropped on
247 // the opener thread. We now enqueue Close() inside our caller, Release(), so
248 // it doesn't actually matter what thread our reference drops on.)
251 void Service::getConnections(
252 /* inout */ nsTArray<RefPtr<Connection>>& aConnections) {
253 mRegistrationMutex.AssertNotCurrentThreadOwns();
254 MutexAutoLock mutex(mRegistrationMutex);
255 aConnections.Clear();
256 aConnections.AppendElements(mConnections);
259 void Service::minimizeMemory() {
260 nsTArray<RefPtr<Connection>> connections;
261 getConnections(connections);
263 for (uint32_t i = 0; i < connections.Length(); i++) {
264 RefPtr<Connection> conn = connections[i];
265 // For non-main-thread owning/opening threads, we may be racing against them
266 // closing their connection or their thread. That's okay, see below.
267 if (!conn->connectionReady()) {
268 continue;
271 constexpr auto shrinkPragma = "PRAGMA shrink_memory"_ns;
272 bool onOpenedThread = false;
274 if (!conn->operationSupported(Connection::SYNCHRONOUS)) {
275 // This is a mozIStorageAsyncConnection, it can only be used on the main
276 // thread, so we can do a straight API call.
277 nsCOMPtr<mozIStoragePendingStatement> ps;
278 DebugOnly<nsresult> rv = conn->ExecuteSimpleSQLAsync(
279 shrinkPragma, nullptr, getter_AddRefs(ps));
280 MOZ_ASSERT(NS_SUCCEEDED(rv), "Should have purged sqlite caches");
281 } else if (NS_SUCCEEDED(
282 conn->threadOpenedOn->IsOnCurrentThread(&onOpenedThread)) &&
283 onOpenedThread) {
284 if (conn->isAsyncExecutionThreadAvailable()) {
285 nsCOMPtr<mozIStoragePendingStatement> ps;
286 DebugOnly<nsresult> rv = conn->ExecuteSimpleSQLAsync(
287 shrinkPragma, nullptr, getter_AddRefs(ps));
288 MOZ_ASSERT(NS_SUCCEEDED(rv), "Should have purged sqlite caches");
289 } else {
290 conn->ExecuteSimpleSQL(shrinkPragma);
292 } else {
293 // We are on the wrong thread, the query should be executed on the
294 // opener thread, so we must dispatch to it.
295 // It's possible the connection is already closed or will be closed by the
296 // time our runnable runs. ExecuteSimpleSQL will safely return with a
297 // failure in that case. If the thread is shutting down or shut down, the
298 // dispatch will fail and that's okay.
299 nsCOMPtr<nsIRunnable> event = NewRunnableMethod<const nsCString>(
300 "Connection::ExecuteSimpleSQL", conn, &Connection::ExecuteSimpleSQL,
301 shrinkPragma);
302 Unused << conn->threadOpenedOn->Dispatch(event, NS_DISPATCH_NORMAL);
307 UniquePtr<sqlite3_vfs> ConstructTelemetryVFS(bool);
308 const char* GetTelemetryVFSName(bool);
310 UniquePtr<sqlite3_vfs> ConstructObfuscatingVFS(const char* aBaseVFSName);
312 static const char* sObserverTopics[] = {"memory-pressure",
313 "xpcom-shutdown-threads"};
315 nsresult Service::initialize() {
316 MOZ_ASSERT(NS_IsMainThread(), "Must be initialized on the main thread");
318 int rc = AutoSQLiteLifetime::getInitResult();
319 if (rc != SQLITE_OK) {
320 return convertResultCode(rc);
323 rc = mTelemetrySqliteVFS.Init(ConstructTelemetryVFS(false));
324 if (rc != SQLITE_OK) {
325 return convertResultCode(rc);
328 rc = mTelemetryExclSqliteVFS.Init(ConstructTelemetryVFS(true));
329 if (rc != SQLITE_OK) {
330 return convertResultCode(rc);
333 rc = mObfuscatingSqliteVFS.Init(ConstructObfuscatingVFS(GetTelemetryVFSName(
334 StaticPrefs::storage_sqlite_exclusiveLock_enabled())));
335 if (rc != SQLITE_OK) {
336 return convertResultCode(rc);
339 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
340 NS_ENSURE_TRUE(os, NS_ERROR_FAILURE);
342 for (size_t i = 0; i < ArrayLength(sObserverTopics); ++i) {
343 nsresult rv = os->AddObserver(this, sObserverTopics[i], false);
344 if (NS_WARN_IF(NS_FAILED(rv))) {
345 return rv;
349 mozilla::RegisterWeakMemoryReporter(this);
350 mozilla::RegisterStorageSQLiteDistinguishedAmount(
351 StorageSQLiteDistinguishedAmount);
353 return NS_OK;
356 int Service::localeCompareStrings(const nsAString& aStr1,
357 const nsAString& aStr2,
358 int32_t aComparisonStrength) {
359 // The implementation of nsICollation.CompareString() is platform-dependent.
360 // On Linux it's not thread-safe. It may not be on Windows and OS X either,
361 // but it's more difficult to tell. We therefore synchronize this method.
362 MutexAutoLock mutex(mMutex);
364 nsICollation* coll = getLocaleCollation();
365 if (!coll) {
366 NS_ERROR("Storage service has no collation");
367 return 0;
370 int32_t res;
371 nsresult rv = coll->CompareString(aComparisonStrength, aStr1, aStr2, &res);
372 if (NS_FAILED(rv)) {
373 NS_ERROR("Collation compare string failed");
374 return 0;
377 return res;
380 nsICollation* Service::getLocaleCollation() {
381 mMutex.AssertCurrentThreadOwns();
383 if (mLocaleCollation) return mLocaleCollation;
385 nsCOMPtr<nsICollationFactory> collFact =
386 do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID);
387 if (!collFact) {
388 NS_WARNING("Could not create collation factory");
389 return nullptr;
392 nsresult rv = collFact->CreateCollation(getter_AddRefs(mLocaleCollation));
393 if (NS_FAILED(rv)) {
394 NS_WARNING("Could not create collation");
395 return nullptr;
398 return mLocaleCollation;
401 ////////////////////////////////////////////////////////////////////////////////
402 //// mozIStorageService
404 NS_IMETHODIMP
405 Service::OpenSpecialDatabase(const nsACString& aStorageKey,
406 const nsACString& aName,
407 mozIStorageConnection** _connection) {
408 if (!aStorageKey.Equals(kMozStorageMemoryStorageKey)) {
409 return NS_ERROR_INVALID_ARG;
412 int flags = SQLITE_OPEN_READWRITE;
414 if (!aName.IsEmpty()) {
415 flags |= SQLITE_OPEN_URI;
418 RefPtr<Connection> msc = new Connection(this, flags, Connection::SYNCHRONOUS);
420 nsresult rv = msc->initialize(aStorageKey, aName);
421 NS_ENSURE_SUCCESS(rv, rv);
423 msc.forget(_connection);
424 return NS_OK;
427 namespace {
429 class AsyncInitDatabase final : public Runnable {
430 public:
431 AsyncInitDatabase(Connection* aConnection, nsIFile* aStorageFile,
432 int32_t aGrowthIncrement,
433 mozIStorageCompletionCallback* aCallback)
434 : Runnable("storage::AsyncInitDatabase"),
435 mConnection(aConnection),
436 mStorageFile(aStorageFile),
437 mGrowthIncrement(aGrowthIncrement),
438 mCallback(aCallback) {
439 MOZ_ASSERT(NS_IsMainThread());
442 NS_IMETHOD Run() override {
443 MOZ_ASSERT(!NS_IsMainThread());
444 nsresult rv = mConnection->initializeOnAsyncThread(mStorageFile);
445 if (NS_FAILED(rv)) {
446 return DispatchResult(rv, nullptr);
449 if (mGrowthIncrement >= 0) {
450 // Ignore errors. In the future, we might wish to log them.
451 (void)mConnection->SetGrowthIncrement(mGrowthIncrement, ""_ns);
454 return DispatchResult(
455 NS_OK, NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*, mConnection));
458 private:
459 nsresult DispatchResult(nsresult aStatus, nsISupports* aValue) {
460 RefPtr<CallbackComplete> event =
461 new CallbackComplete(aStatus, aValue, mCallback.forget());
462 return NS_DispatchToMainThread(event);
465 ~AsyncInitDatabase() {
466 NS_ReleaseOnMainThread("AsyncInitDatabase::mStorageFile",
467 mStorageFile.forget());
468 NS_ReleaseOnMainThread("AsyncInitDatabase::mConnection",
469 mConnection.forget());
471 // Generally, the callback will be released by CallbackComplete.
472 // However, if for some reason Run() is not executed, we still
473 // need to ensure that it is released here.
474 NS_ReleaseOnMainThread("AsyncInitDatabase::mCallback", mCallback.forget());
477 RefPtr<Connection> mConnection;
478 nsCOMPtr<nsIFile> mStorageFile;
479 int32_t mGrowthIncrement;
480 RefPtr<mozIStorageCompletionCallback> mCallback;
483 } // namespace
485 NS_IMETHODIMP
486 Service::OpenAsyncDatabase(nsIVariant* aDatabaseStore,
487 nsIPropertyBag2* aOptions,
488 mozIStorageCompletionCallback* aCallback) {
489 if (!NS_IsMainThread()) {
490 return NS_ERROR_NOT_SAME_THREAD;
492 NS_ENSURE_ARG(aDatabaseStore);
493 NS_ENSURE_ARG(aCallback);
495 nsresult rv;
496 bool shared = false;
497 bool readOnly = false;
498 bool ignoreLockingMode = false;
499 int32_t growthIncrement = -1;
501 #define FAIL_IF_SET_BUT_INVALID(rv) \
502 if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) { \
503 return NS_ERROR_INVALID_ARG; \
506 // Deal with options first:
507 if (aOptions) {
508 rv = aOptions->GetPropertyAsBool(u"readOnly"_ns, &readOnly);
509 FAIL_IF_SET_BUT_INVALID(rv);
511 rv = aOptions->GetPropertyAsBool(u"ignoreLockingMode"_ns,
512 &ignoreLockingMode);
513 FAIL_IF_SET_BUT_INVALID(rv);
514 // Specifying ignoreLockingMode will force use of the readOnly flag:
515 if (ignoreLockingMode) {
516 readOnly = true;
519 rv = aOptions->GetPropertyAsBool(u"shared"_ns, &shared);
520 FAIL_IF_SET_BUT_INVALID(rv);
522 // NB: we re-set to -1 if we don't have a storage file later on.
523 rv = aOptions->GetPropertyAsInt32(u"growthIncrement"_ns, &growthIncrement);
524 FAIL_IF_SET_BUT_INVALID(rv);
526 int flags = readOnly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_READWRITE;
528 nsCOMPtr<nsIFile> storageFile;
529 nsCOMPtr<nsISupports> dbStore;
530 rv = aDatabaseStore->GetAsISupports(getter_AddRefs(dbStore));
531 if (NS_SUCCEEDED(rv)) {
532 // Generally, aDatabaseStore holds the database nsIFile.
533 storageFile = do_QueryInterface(dbStore, &rv);
534 if (NS_FAILED(rv)) {
535 return NS_ERROR_INVALID_ARG;
538 nsCOMPtr<nsIFile> cloned;
539 rv = storageFile->Clone(getter_AddRefs(cloned));
540 MOZ_ASSERT(NS_SUCCEEDED(rv));
541 storageFile = std::move(cloned);
543 if (!readOnly) {
544 // Ensure that SQLITE_OPEN_CREATE is passed in for compatibility reasons.
545 flags |= SQLITE_OPEN_CREATE;
548 // Apply the shared-cache option.
549 flags |= shared ? SQLITE_OPEN_SHAREDCACHE : SQLITE_OPEN_PRIVATECACHE;
550 } else {
551 // Sometimes, however, it's a special database name.
552 nsAutoCString keyString;
553 rv = aDatabaseStore->GetAsACString(keyString);
554 if (NS_FAILED(rv) || !keyString.Equals(kMozStorageMemoryStorageKey)) {
555 return NS_ERROR_INVALID_ARG;
558 // Just fall through with nullptr storageFile, this will cause the storage
559 // connection to use a memory DB.
562 if (!storageFile && growthIncrement >= 0) {
563 return NS_ERROR_INVALID_ARG;
566 // Create connection on this thread, but initialize it on its helper thread.
567 RefPtr<Connection> msc =
568 new Connection(this, flags, Connection::ASYNCHRONOUS, ignoreLockingMode);
569 nsCOMPtr<nsIEventTarget> target = msc->getAsyncExecutionTarget();
570 MOZ_ASSERT(target,
571 "Cannot initialize a connection that has been closed already");
573 RefPtr<AsyncInitDatabase> asyncInit =
574 new AsyncInitDatabase(msc, storageFile, growthIncrement, aCallback);
575 return target->Dispatch(asyncInit, nsIEventTarget::DISPATCH_NORMAL);
578 NS_IMETHODIMP
579 Service::OpenDatabase(nsIFile* aDatabaseFile,
580 mozIStorageConnection** _connection) {
581 NS_ENSURE_ARG(aDatabaseFile);
583 // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
584 // reasons.
585 int flags =
586 SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE | SQLITE_OPEN_CREATE;
587 RefPtr<Connection> msc = new Connection(this, flags, Connection::SYNCHRONOUS);
589 nsresult rv = msc->initialize(aDatabaseFile);
590 NS_ENSURE_SUCCESS(rv, rv);
592 msc.forget(_connection);
593 return NS_OK;
596 NS_IMETHODIMP
597 Service::OpenUnsharedDatabase(nsIFile* aDatabaseFile,
598 mozIStorageConnection** _connection) {
599 NS_ENSURE_ARG(aDatabaseFile);
601 // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
602 // reasons.
603 int flags =
604 SQLITE_OPEN_READWRITE | SQLITE_OPEN_PRIVATECACHE | SQLITE_OPEN_CREATE;
605 RefPtr<Connection> msc = new Connection(this, flags, Connection::SYNCHRONOUS);
607 nsresult rv = msc->initialize(aDatabaseFile);
608 NS_ENSURE_SUCCESS(rv, rv);
610 msc.forget(_connection);
611 return NS_OK;
614 NS_IMETHODIMP
615 Service::OpenDatabaseWithFileURL(nsIFileURL* aFileURL,
616 const nsACString& aTelemetryFilename,
617 mozIStorageConnection** _connection) {
618 NS_ENSURE_ARG(aFileURL);
620 // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
621 // reasons.
622 int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE |
623 SQLITE_OPEN_CREATE | SQLITE_OPEN_URI;
624 RefPtr<Connection> msc = new Connection(this, flags, Connection::SYNCHRONOUS);
626 nsresult rv = msc->initialize(aFileURL, aTelemetryFilename);
627 NS_ENSURE_SUCCESS(rv, rv);
629 msc.forget(_connection);
630 return NS_OK;
633 NS_IMETHODIMP
634 Service::BackupDatabaseFile(nsIFile* aDBFile, const nsAString& aBackupFileName,
635 nsIFile* aBackupParentDirectory, nsIFile** backup) {
636 nsresult rv;
637 nsCOMPtr<nsIFile> parentDir = aBackupParentDirectory;
638 if (!parentDir) {
639 // This argument is optional, and defaults to the same parent directory
640 // as the current file.
641 rv = aDBFile->GetParent(getter_AddRefs(parentDir));
642 NS_ENSURE_SUCCESS(rv, rv);
645 nsCOMPtr<nsIFile> backupDB;
646 rv = parentDir->Clone(getter_AddRefs(backupDB));
647 NS_ENSURE_SUCCESS(rv, rv);
649 rv = backupDB->Append(aBackupFileName);
650 NS_ENSURE_SUCCESS(rv, rv);
652 rv = backupDB->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
653 NS_ENSURE_SUCCESS(rv, rv);
655 nsAutoString fileName;
656 rv = backupDB->GetLeafName(fileName);
657 NS_ENSURE_SUCCESS(rv, rv);
659 rv = backupDB->Remove(false);
660 NS_ENSURE_SUCCESS(rv, rv);
662 backupDB.forget(backup);
664 return aDBFile->CopyTo(parentDir, fileName);
667 ////////////////////////////////////////////////////////////////////////////////
668 //// nsIObserver
670 NS_IMETHODIMP
671 Service::Observe(nsISupports*, const char* aTopic, const char16_t*) {
672 if (strcmp(aTopic, "memory-pressure") == 0) {
673 minimizeMemory();
674 } else if (strcmp(aTopic, "xpcom-shutdown-threads") == 0) {
675 // The Service is kept alive by our strong observer references and
676 // references held by Connection instances. Since we're about to remove the
677 // former and then wait for the latter ones to go away, it behooves us to
678 // hold a strong reference to ourselves so our calls to getConnections() do
679 // not happen on a deleted object.
680 RefPtr<Service> kungFuDeathGrip = this;
682 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
684 for (size_t i = 0; i < ArrayLength(sObserverTopics); ++i) {
685 (void)os->RemoveObserver(this, sObserverTopics[i]);
688 SpinEventLoopUntil([&]() -> bool {
689 // We must wait until all the closing connections are closed.
690 nsTArray<RefPtr<Connection>> connections;
691 getConnections(connections);
692 for (auto& conn : connections) {
693 if (conn->isClosing()) {
694 return false;
697 return true;
700 #ifdef DEBUG
701 nsTArray<RefPtr<Connection>> connections;
702 getConnections(connections);
703 for (uint32_t i = 0, n = connections.Length(); i < n; i++) {
704 if (!connections[i]->isClosed()) {
705 // getFilename is only the leaf name for the database file,
706 // so it shouldn't contain privacy-sensitive information.
707 CrashReporter::AnnotateCrashReport(
708 CrashReporter::Annotation::StorageConnectionNotClosed,
709 connections[i]->getFilename());
710 printf_stderr("Storage connection not closed: %s",
711 connections[i]->getFilename().get());
712 MOZ_CRASH();
715 #endif
718 return NS_OK;
721 } // namespace storage
722 } // namespace mozilla