Bug 1833471 Part 1 - Implement @page pseudo-class parsing and rule handling r=emilio
[gecko.git] / storage / mozStorageService.cpp
blob6427fef6529fa2959100c3b5b1c7b82ae0f6961e
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"
10 #include "nsIFile.h"
11 #include "nsIFileURL.h"
12 #include "mozStorageService.h"
13 #include "mozStorageConnection.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"
26 #include "mozilla/intl/Collator.h"
27 #include "mozilla/intl/LocaleService.h"
29 #include "sqlite3.h"
30 #include "mozilla/AutoSQLiteLifetime.h"
32 #ifdef XP_WIN
33 // "windows.h" was included and it can #define lots of things we care about...
34 # undef CompareString
35 #endif
37 using mozilla::intl::Collator;
39 namespace mozilla::storage {
41 ////////////////////////////////////////////////////////////////////////////////
42 //// Memory Reporting
44 #ifdef MOZ_DMD
45 mozilla::Atomic<size_t> gSqliteMemoryUsed;
46 #endif
48 static int64_t StorageSQLiteDistinguishedAmount() {
49 return ::sqlite3_memory_used();
52 /**
53 * Passes a single SQLite memory statistic to a memory reporter callback.
55 * @param aHandleReport
56 * The callback.
57 * @param aData
58 * The data for the callback.
59 * @param aConn
60 * The SQLite connection.
61 * @param aPathHead
62 * Head of the path for the memory report.
63 * @param aKind
64 * The memory report statistic kind, one of "stmt", "cache" or
65 * "schema".
66 * @param aDesc
67 * The memory report description.
68 * @param aOption
69 * The SQLite constant for getting the measurement.
70 * @param aTotal
71 * The accumulator for the measurement.
73 static void ReportConn(nsIHandleReportCallback* aHandleReport,
74 nsISupports* aData, Connection* aConn,
75 const nsACString& aPathHead, const nsACString& aKind,
76 const nsACString& aDesc, int32_t aOption,
77 size_t* aTotal) {
78 nsCString path(aPathHead);
79 path.Append(aKind);
80 path.AppendLiteral("-used");
82 int32_t val = aConn->getSqliteRuntimeStatus(aOption);
83 aHandleReport->Callback(""_ns, path, nsIMemoryReporter::KIND_HEAP,
84 nsIMemoryReporter::UNITS_BYTES, int64_t(val), aDesc,
85 aData);
86 *aTotal += val;
89 // Warning: To get a Connection's measurements requires holding its lock.
90 // There may be a delay getting the lock if another thread is accessing the
91 // Connection. This isn't very nice if CollectReports is called from the main
92 // thread! But at the time of writing this function is only called when
93 // about:memory is loaded (not, for example, when telemetry pings occur) and
94 // any delays in that case aren't so bad.
95 NS_IMETHODIMP
96 Service::CollectReports(nsIHandleReportCallback* aHandleReport,
97 nsISupports* aData, bool aAnonymize) {
98 size_t totalConnSize = 0;
100 nsTArray<RefPtr<Connection>> connections;
101 getConnections(connections);
103 for (uint32_t i = 0; i < connections.Length(); i++) {
104 RefPtr<Connection>& conn = connections[i];
106 // Someone may have closed the Connection, in which case we skip it.
107 // Note that we have consumers of the synchronous API that are off the
108 // main-thread, like the DOM Cache and IndexedDB, and as such we must be
109 // sure that we have a connection.
110 MutexAutoLock lockedAsyncScope(conn->sharedAsyncExecutionMutex);
111 if (!conn->connectionReady()) {
112 continue;
115 nsCString pathHead("explicit/storage/sqlite/");
116 // This filename isn't privacy-sensitive, and so is never anonymized.
117 pathHead.Append(conn->getFilename());
118 pathHead.Append('/');
120 SQLiteMutexAutoLock lockedScope(conn->sharedDBMutex);
122 constexpr auto stmtDesc =
123 "Memory (approximate) used by all prepared statements used by "
124 "connections to this database."_ns;
125 ReportConn(aHandleReport, aData, conn, pathHead, "stmt"_ns, stmtDesc,
126 SQLITE_DBSTATUS_STMT_USED, &totalConnSize);
128 constexpr auto cacheDesc =
129 "Memory (approximate) used by all pager caches used by connections "
130 "to this database."_ns;
131 ReportConn(aHandleReport, aData, conn, pathHead, "cache"_ns, cacheDesc,
132 SQLITE_DBSTATUS_CACHE_USED_SHARED, &totalConnSize);
134 constexpr auto schemaDesc =
135 "Memory (approximate) used to store the schema for all databases "
136 "associated with connections to this database."_ns;
137 ReportConn(aHandleReport, aData, conn, pathHead, "schema"_ns, schemaDesc,
138 SQLITE_DBSTATUS_SCHEMA_USED, &totalConnSize);
141 #ifdef MOZ_DMD
142 if (::sqlite3_memory_used() != int64_t(gSqliteMemoryUsed)) {
143 NS_WARNING(
144 "memory consumption reported by SQLite doesn't match "
145 "our measurements");
147 #endif
150 int64_t other = static_cast<int64_t>(::sqlite3_memory_used() - totalConnSize);
152 MOZ_COLLECT_REPORT("explicit/storage/sqlite/other", KIND_HEAP, UNITS_BYTES,
153 other, "All unclassified sqlite memory.");
155 return NS_OK;
158 ////////////////////////////////////////////////////////////////////////////////
159 //// Service
161 NS_IMPL_ISUPPORTS(Service, mozIStorageService, nsIObserver, nsIMemoryReporter)
163 Service* Service::gService = nullptr;
165 already_AddRefed<Service> Service::getSingleton() {
166 if (gService) {
167 return do_AddRef(gService);
170 // The first reference to the storage service must be obtained on the
171 // main thread.
172 NS_ENSURE_TRUE(NS_IsMainThread(), nullptr);
173 RefPtr<Service> service = new Service();
174 if (NS_SUCCEEDED(service->initialize())) {
175 // Note: This is cleared in the Service destructor.
176 gService = service.get();
177 return service.forget();
180 return nullptr;
183 int Service::AutoVFSRegistration::Init(UniquePtr<sqlite3_vfs> aVFS) {
184 MOZ_ASSERT(!mVFS);
185 if (aVFS) {
186 mVFS = std::move(aVFS);
187 return sqlite3_vfs_register(mVFS.get(), 0);
189 NS_WARNING("Failed to register VFS");
190 return SQLITE_OK;
193 Service::AutoVFSRegistration::~AutoVFSRegistration() {
194 if (mVFS) {
195 int rc = sqlite3_vfs_unregister(mVFS.get());
196 if (rc != SQLITE_OK) {
197 NS_WARNING("Failed to unregister sqlite vfs wrapper.");
202 Service::Service()
203 : mMutex("Service::mMutex"),
204 mRegistrationMutex("Service::mRegistrationMutex"),
205 mConnections(),
206 mLastSensitivity(mozilla::intl::Collator::Sensitivity::Base) {}
208 Service::~Service() {
209 mozilla::UnregisterWeakMemoryReporter(this);
210 mozilla::UnregisterStorageSQLiteDistinguishedAmount();
212 gService = nullptr;
215 void Service::registerConnection(Connection* aConnection) {
216 mRegistrationMutex.AssertNotCurrentThreadOwns();
217 MutexAutoLock mutex(mRegistrationMutex);
218 (void)mConnections.AppendElement(aConnection);
221 void Service::unregisterConnection(Connection* aConnection) {
222 // If this is the last Connection it might be the only thing keeping Service
223 // alive. So ensure that Service is destroyed only after the Connection is
224 // cleanly unregistered and destroyed.
225 RefPtr<Service> kungFuDeathGrip(this);
226 RefPtr<Connection> forgettingRef;
228 mRegistrationMutex.AssertNotCurrentThreadOwns();
229 MutexAutoLock mutex(mRegistrationMutex);
231 for (uint32_t i = 0; i < mConnections.Length(); ++i) {
232 if (mConnections[i] == aConnection) {
233 // Because dropping the final reference can potentially result in
234 // spinning a nested event loop if the connection was not properly
235 // shutdown, we want to do that outside this loop so that we can finish
236 // mutating the array and drop our mutex.
237 forgettingRef = std::move(mConnections[i]);
238 mConnections.RemoveElementAt(i);
239 break;
244 MOZ_ASSERT(forgettingRef,
245 "Attempt to unregister unknown storage connection!");
247 // Do not proxy the release anywhere, just let this reference drop here. (We
248 // previously did proxy the release, but that was because we invoked Close()
249 // in the destructor and Close() likes to complain if it's not invoked on the
250 // opener event target, so it was essential that the last reference be dropped
251 // on the opener event target. We now enqueue Close() inside our caller,
252 // Release(), so it doesn't actually matter what thread our reference drops
253 // on.)
256 void Service::getConnections(
257 /* inout */ nsTArray<RefPtr<Connection>>& aConnections) {
258 mRegistrationMutex.AssertNotCurrentThreadOwns();
259 MutexAutoLock mutex(mRegistrationMutex);
260 aConnections.Clear();
261 aConnections.AppendElements(mConnections);
264 void Service::minimizeMemory() {
265 nsTArray<RefPtr<Connection>> connections;
266 getConnections(connections);
268 for (uint32_t i = 0; i < connections.Length(); i++) {
269 RefPtr<Connection> conn = connections[i];
270 // For non-main-thread owning/opening threads, we may be racing against them
271 // closing their connection or their thread. That's okay, see below.
272 if (!conn->connectionReady()) {
273 continue;
276 constexpr auto shrinkPragma = "PRAGMA shrink_memory"_ns;
278 if (!conn->operationSupported(Connection::SYNCHRONOUS)) {
279 // This is a mozIStorageAsyncConnection, it can only be used on the main
280 // thread, so we can do a straight API call.
281 nsCOMPtr<mozIStoragePendingStatement> ps;
282 DebugOnly<nsresult> rv = conn->ExecuteSimpleSQLAsync(
283 shrinkPragma, nullptr, getter_AddRefs(ps));
284 MOZ_ASSERT(NS_SUCCEEDED(rv), "Should have purged sqlite caches");
285 } else if (IsOnCurrentSerialEventTarget(conn->eventTargetOpenedOn)) {
286 if (conn->isAsyncExecutionThreadAvailable()) {
287 nsCOMPtr<mozIStoragePendingStatement> ps;
288 DebugOnly<nsresult> rv = conn->ExecuteSimpleSQLAsync(
289 shrinkPragma, nullptr, getter_AddRefs(ps));
290 MOZ_ASSERT(NS_SUCCEEDED(rv), "Should have purged sqlite caches");
291 } else {
292 conn->ExecuteSimpleSQL(shrinkPragma);
294 } else {
295 // We are on the wrong event target, the query should be executed on the
296 // opener event target, so we must dispatch to it.
297 // It's possible the connection is already closed or will be closed by the
298 // time our runnable runs. ExecuteSimpleSQL will safely return with a
299 // failure in that case. If the event target is shutting down or shut
300 // down, the dispatch will fail and that's okay.
301 nsCOMPtr<nsIRunnable> event = NewRunnableMethod<const nsCString>(
302 "Connection::ExecuteSimpleSQL", conn, &Connection::ExecuteSimpleSQL,
303 shrinkPragma);
304 Unused << conn->eventTargetOpenedOn->Dispatch(event, NS_DISPATCH_NORMAL);
309 UniquePtr<sqlite3_vfs> ConstructBaseVFS(bool);
310 const char* GetBaseVFSName(bool);
312 UniquePtr<sqlite3_vfs> ConstructQuotaVFS(const char* aBaseVFSName);
313 const char* GetQuotaVFSName();
315 UniquePtr<sqlite3_vfs> ConstructObfuscatingVFS(const char* aBaseVFSName);
317 UniquePtr<sqlite3_vfs> ConstructReadOnlyNoLockVFS();
319 static const char* sObserverTopics[] = {"memory-pressure",
320 "xpcom-shutdown-threads"};
322 nsresult Service::initialize() {
323 MOZ_ASSERT(NS_IsMainThread(), "Must be initialized on the main thread");
325 int rc = AutoSQLiteLifetime::getInitResult();
326 if (rc != SQLITE_OK) {
327 return convertResultCode(rc);
331 * The virtual file system hierarchy
333 * obfsvfs
337 * quotavfs
338 * / \
339 * / \
340 * / \
341 * / \
342 * / \
343 * base-vfs-excl base-vfs
344 * / \ / \
345 * / \ / \
346 * / \ / \
347 * unix-excl win32 unix win32
350 rc = mBaseSqliteVFS.Init(ConstructBaseVFS(false));
351 if (rc != SQLITE_OK) {
352 return convertResultCode(rc);
355 rc = mBaseExclSqliteVFS.Init(ConstructBaseVFS(true));
356 if (rc != SQLITE_OK) {
357 return convertResultCode(rc);
360 rc = mQuotaSqliteVFS.Init(ConstructQuotaVFS(
361 GetBaseVFSName(StaticPrefs::storage_sqlite_exclusiveLock_enabled())));
362 if (rc != SQLITE_OK) {
363 return convertResultCode(rc);
366 rc = mObfuscatingSqliteVFS.Init(ConstructObfuscatingVFS(GetQuotaVFSName()));
367 if (rc != SQLITE_OK) {
368 return convertResultCode(rc);
371 rc = mReadOnlyNoLockSqliteVFS.Init(ConstructReadOnlyNoLockVFS());
372 if (rc != SQLITE_OK) {
373 return convertResultCode(rc);
376 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
377 NS_ENSURE_TRUE(os, NS_ERROR_FAILURE);
379 for (auto& sObserverTopic : sObserverTopics) {
380 nsresult rv = os->AddObserver(this, sObserverTopic, false);
381 if (NS_WARN_IF(NS_FAILED(rv))) {
382 return rv;
386 mozilla::RegisterWeakMemoryReporter(this);
387 mozilla::RegisterStorageSQLiteDistinguishedAmount(
388 StorageSQLiteDistinguishedAmount);
390 return NS_OK;
393 int Service::localeCompareStrings(const nsAString& aStr1,
394 const nsAString& aStr2,
395 Collator::Sensitivity aSensitivity) {
396 // The mozilla::intl::Collator is not thread safe, since the Collator::Options
397 // can be changed.
398 MutexAutoLock mutex(mMutex);
400 Collator* collator = getCollator();
401 if (!collator) {
402 NS_ERROR("Storage service has no collation");
403 return 0;
406 if (aSensitivity != mLastSensitivity) {
407 Collator::Options options{};
408 options.sensitivity = aSensitivity;
409 auto result = mCollator->SetOptions(options);
411 if (result.isErr()) {
412 NS_WARNING("Could not configure the mozilla::intl::Collation.");
413 return 0;
415 mLastSensitivity = aSensitivity;
418 return collator->CompareStrings(aStr1, aStr2);
421 Collator* Service::getCollator() {
422 mMutex.AssertCurrentThreadOwns();
424 if (mCollator) {
425 return mCollator.get();
428 auto result = mozilla::intl::LocaleService::TryCreateComponent<Collator>();
429 if (result.isErr()) {
430 NS_WARNING("Could not create mozilla::intl::Collation.");
431 return nullptr;
434 mCollator = result.unwrap();
436 // Sort in a case-insensitive way, where "base" letters are considered
437 // equal, e.g: a = á, a = A, a ≠ b.
438 Collator::Options options{};
439 options.sensitivity = Collator::Sensitivity::Base;
440 auto optResult = mCollator->SetOptions(options);
442 if (optResult.isErr()) {
443 NS_WARNING("Could not configure the mozilla::intl::Collation.");
444 mCollator = nullptr;
445 return nullptr;
448 return mCollator.get();
451 ////////////////////////////////////////////////////////////////////////////////
452 //// mozIStorageService
454 NS_IMETHODIMP
455 Service::OpenSpecialDatabase(const nsACString& aStorageKey,
456 const nsACString& aName, uint32_t aConnectionFlags,
457 mozIStorageConnection** _connection) {
458 if (!aStorageKey.Equals(kMozStorageMemoryStorageKey)) {
459 return NS_ERROR_INVALID_ARG;
462 const bool interruptible =
463 aConnectionFlags & mozIStorageService::CONNECTION_INTERRUPTIBLE;
465 int flags = SQLITE_OPEN_READWRITE;
467 if (!aName.IsEmpty()) {
468 flags |= SQLITE_OPEN_URI;
471 RefPtr<Connection> msc =
472 new Connection(this, flags, Connection::SYNCHRONOUS,
473 kMozStorageMemoryStorageKey, interruptible);
474 const nsresult rv = msc->initialize(aStorageKey, aName);
475 NS_ENSURE_SUCCESS(rv, rv);
477 msc.forget(_connection);
478 return NS_OK;
481 namespace {
483 class AsyncInitDatabase final : public Runnable {
484 public:
485 AsyncInitDatabase(Connection* aConnection, nsIFile* aStorageFile,
486 int32_t aGrowthIncrement,
487 mozIStorageCompletionCallback* aCallback)
488 : Runnable("storage::AsyncInitDatabase"),
489 mConnection(aConnection),
490 mStorageFile(aStorageFile),
491 mGrowthIncrement(aGrowthIncrement),
492 mCallback(aCallback) {
493 MOZ_ASSERT(NS_IsMainThread());
496 NS_IMETHOD Run() override {
497 MOZ_ASSERT(!NS_IsMainThread());
498 nsresult rv = mConnection->initializeOnAsyncThread(mStorageFile);
499 if (NS_FAILED(rv)) {
500 return DispatchResult(rv, nullptr);
503 if (mGrowthIncrement >= 0) {
504 // Ignore errors. In the future, we might wish to log them.
505 (void)mConnection->SetGrowthIncrement(mGrowthIncrement, ""_ns);
508 return DispatchResult(
509 NS_OK, NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*, mConnection));
512 private:
513 nsresult DispatchResult(nsresult aStatus, nsISupports* aValue) {
514 RefPtr<CallbackComplete> event =
515 new CallbackComplete(aStatus, aValue, mCallback.forget());
516 return NS_DispatchToMainThread(event);
519 ~AsyncInitDatabase() {
520 NS_ReleaseOnMainThread("AsyncInitDatabase::mStorageFile",
521 mStorageFile.forget());
522 NS_ReleaseOnMainThread("AsyncInitDatabase::mConnection",
523 mConnection.forget());
525 // Generally, the callback will be released by CallbackComplete.
526 // However, if for some reason Run() is not executed, we still
527 // need to ensure that it is released here.
528 NS_ReleaseOnMainThread("AsyncInitDatabase::mCallback", mCallback.forget());
531 RefPtr<Connection> mConnection;
532 nsCOMPtr<nsIFile> mStorageFile;
533 int32_t mGrowthIncrement;
534 RefPtr<mozIStorageCompletionCallback> mCallback;
537 } // namespace
539 NS_IMETHODIMP
540 Service::OpenAsyncDatabase(nsIVariant* aDatabaseStore, uint32_t aOpenFlags,
541 uint32_t /* aConnectionFlags */,
542 mozIStorageCompletionCallback* aCallback) {
543 if (!NS_IsMainThread()) {
544 return NS_ERROR_NOT_SAME_THREAD;
546 NS_ENSURE_ARG(aDatabaseStore);
547 NS_ENSURE_ARG(aCallback);
549 const bool shared = aOpenFlags & mozIStorageService::OPEN_SHARED;
550 const bool ignoreLockingMode =
551 aOpenFlags & mozIStorageService::OPEN_IGNORE_LOCKING_MODE;
552 // Specifying ignoreLockingMode will force use of the readOnly flag:
553 const bool readOnly =
554 ignoreLockingMode || (aOpenFlags & mozIStorageService::OPEN_READONLY);
555 int flags = readOnly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_READWRITE;
557 nsCOMPtr<nsIFile> storageFile;
558 nsCOMPtr<nsISupports> dbStore;
559 nsresult rv = aDatabaseStore->GetAsISupports(getter_AddRefs(dbStore));
560 if (NS_SUCCEEDED(rv)) {
561 // Generally, aDatabaseStore holds the database nsIFile.
562 storageFile = do_QueryInterface(dbStore, &rv);
563 if (NS_FAILED(rv)) {
564 return NS_ERROR_INVALID_ARG;
567 nsCOMPtr<nsIFile> cloned;
568 rv = storageFile->Clone(getter_AddRefs(cloned));
569 MOZ_ASSERT(NS_SUCCEEDED(rv));
570 storageFile = std::move(cloned);
572 if (!readOnly) {
573 // Ensure that SQLITE_OPEN_CREATE is passed in for compatibility reasons.
574 flags |= SQLITE_OPEN_CREATE;
577 // Apply the shared-cache option.
578 flags |= shared ? SQLITE_OPEN_SHAREDCACHE : SQLITE_OPEN_PRIVATECACHE;
579 } else {
580 // Sometimes, however, it's a special database name.
581 nsAutoCString keyString;
582 rv = aDatabaseStore->GetAsACString(keyString);
583 if (NS_FAILED(rv) || !keyString.Equals(kMozStorageMemoryStorageKey)) {
584 return NS_ERROR_INVALID_ARG;
587 // Just fall through with nullptr storageFile, this will cause the storage
588 // connection to use a memory DB.
591 // Create connection on this thread, but initialize it on its helper thread.
592 nsAutoCString telemetryFilename;
593 if (!storageFile) {
594 telemetryFilename.Assign(kMozStorageMemoryStorageKey);
595 } else {
596 rv = storageFile->GetNativeLeafName(telemetryFilename);
597 NS_ENSURE_SUCCESS(rv, rv);
599 RefPtr<Connection> msc =
600 new Connection(this, flags, Connection::ASYNCHRONOUS, telemetryFilename,
601 /* interruptible */ true, ignoreLockingMode);
602 nsCOMPtr<nsIEventTarget> target = msc->getAsyncExecutionTarget();
603 MOZ_ASSERT(target,
604 "Cannot initialize a connection that has been closed already");
606 RefPtr<AsyncInitDatabase> asyncInit = new AsyncInitDatabase(
607 msc, storageFile, /* growthIncrement */ -1, aCallback);
608 return target->Dispatch(asyncInit, nsIEventTarget::DISPATCH_NORMAL);
611 NS_IMETHODIMP
612 Service::OpenDatabase(nsIFile* aDatabaseFile, uint32_t aConnectionFlags,
613 mozIStorageConnection** _connection) {
614 NS_ENSURE_ARG(aDatabaseFile);
616 const bool interruptible =
617 aConnectionFlags & mozIStorageService::CONNECTION_INTERRUPTIBLE;
619 // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
620 // reasons.
621 const int flags =
622 SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE | SQLITE_OPEN_CREATE;
623 nsAutoCString telemetryFilename;
624 nsresult rv = aDatabaseFile->GetNativeLeafName(telemetryFilename);
625 NS_ENSURE_SUCCESS(rv, rv);
626 RefPtr<Connection> msc = new Connection(this, flags, Connection::SYNCHRONOUS,
627 telemetryFilename, interruptible);
628 rv = msc->initialize(aDatabaseFile);
629 NS_ENSURE_SUCCESS(rv, rv);
631 msc.forget(_connection);
632 return NS_OK;
635 NS_IMETHODIMP
636 Service::OpenUnsharedDatabase(nsIFile* aDatabaseFile, uint32_t aConnectionFlags,
637 mozIStorageConnection** _connection) {
638 NS_ENSURE_ARG(aDatabaseFile);
640 const bool interruptible =
641 aConnectionFlags & mozIStorageService::CONNECTION_INTERRUPTIBLE;
643 // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
644 // reasons.
645 const int flags =
646 SQLITE_OPEN_READWRITE | SQLITE_OPEN_PRIVATECACHE | SQLITE_OPEN_CREATE;
647 nsAutoCString telemetryFilename;
648 nsresult rv = aDatabaseFile->GetNativeLeafName(telemetryFilename);
649 NS_ENSURE_SUCCESS(rv, rv);
650 RefPtr<Connection> msc = new Connection(this, flags, Connection::SYNCHRONOUS,
651 telemetryFilename, interruptible);
652 rv = msc->initialize(aDatabaseFile);
653 NS_ENSURE_SUCCESS(rv, rv);
655 msc.forget(_connection);
656 return NS_OK;
659 NS_IMETHODIMP
660 Service::OpenDatabaseWithFileURL(nsIFileURL* aFileURL,
661 const nsACString& aTelemetryFilename,
662 uint32_t aConnectionFlags,
663 mozIStorageConnection** _connection) {
664 NS_ENSURE_ARG(aFileURL);
666 const bool interruptible =
667 aConnectionFlags & mozIStorageService::CONNECTION_INTERRUPTIBLE;
669 // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
670 // reasons.
671 const int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE |
672 SQLITE_OPEN_CREATE | SQLITE_OPEN_URI;
673 nsresult rv;
674 nsAutoCString telemetryFilename;
675 if (!aTelemetryFilename.IsEmpty()) {
676 telemetryFilename = aTelemetryFilename;
677 } else {
678 nsCOMPtr<nsIFile> databaseFile;
679 rv = aFileURL->GetFile(getter_AddRefs(databaseFile));
680 NS_ENSURE_SUCCESS(rv, rv);
681 rv = databaseFile->GetNativeLeafName(telemetryFilename);
682 NS_ENSURE_SUCCESS(rv, rv);
684 RefPtr<Connection> msc = new Connection(this, flags, Connection::SYNCHRONOUS,
685 telemetryFilename, interruptible);
686 rv = msc->initialize(aFileURL);
687 NS_ENSURE_SUCCESS(rv, rv);
689 msc.forget(_connection);
690 return NS_OK;
693 NS_IMETHODIMP
694 Service::BackupDatabaseFile(nsIFile* aDBFile, const nsAString& aBackupFileName,
695 nsIFile* aBackupParentDirectory, nsIFile** backup) {
696 nsresult rv;
697 nsCOMPtr<nsIFile> parentDir = aBackupParentDirectory;
698 if (!parentDir) {
699 // This argument is optional, and defaults to the same parent directory
700 // as the current file.
701 rv = aDBFile->GetParent(getter_AddRefs(parentDir));
702 NS_ENSURE_SUCCESS(rv, rv);
705 nsCOMPtr<nsIFile> backupDB;
706 rv = parentDir->Clone(getter_AddRefs(backupDB));
707 NS_ENSURE_SUCCESS(rv, rv);
709 rv = backupDB->Append(aBackupFileName);
710 NS_ENSURE_SUCCESS(rv, rv);
712 rv = backupDB->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
713 NS_ENSURE_SUCCESS(rv, rv);
715 nsAutoString fileName;
716 rv = backupDB->GetLeafName(fileName);
717 NS_ENSURE_SUCCESS(rv, rv);
719 rv = backupDB->Remove(false);
720 NS_ENSURE_SUCCESS(rv, rv);
722 backupDB.forget(backup);
724 return aDBFile->CopyTo(parentDir, fileName);
727 ////////////////////////////////////////////////////////////////////////////////
728 //// nsIObserver
730 NS_IMETHODIMP
731 Service::Observe(nsISupports*, const char* aTopic, const char16_t*) {
732 if (strcmp(aTopic, "memory-pressure") == 0) {
733 minimizeMemory();
734 } else if (strcmp(aTopic, "xpcom-shutdown-threads") == 0) {
735 // The Service is kept alive by our strong observer references and
736 // references held by Connection instances. Since we're about to remove the
737 // former and then wait for the latter ones to go away, it behooves us to
738 // hold a strong reference to ourselves so our calls to getConnections() do
739 // not happen on a deleted object.
740 RefPtr<Service> kungFuDeathGrip = this;
742 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
744 for (auto& sObserverTopic : sObserverTopics) {
745 (void)os->RemoveObserver(this, sObserverTopic);
748 SpinEventLoopUntil("storage::Service::Observe(xpcom-shutdown-threads)"_ns,
749 [&]() -> bool {
750 // We must wait until all the closing connections are
751 // closed.
752 nsTArray<RefPtr<Connection>> connections;
753 getConnections(connections);
754 for (auto& conn : connections) {
755 if (conn->isClosing()) {
756 return false;
759 return true;
762 #ifdef DEBUG
763 nsTArray<RefPtr<Connection>> connections;
764 getConnections(connections);
765 for (uint32_t i = 0, n = connections.Length(); i < n; i++) {
766 if (!connections[i]->isClosed()) {
767 // getFilename is only the leaf name for the database file,
768 // so it shouldn't contain privacy-sensitive information.
769 CrashReporter::AnnotateCrashReport(
770 CrashReporter::Annotation::StorageConnectionNotClosed,
771 connections[i]->getFilename());
772 printf_stderr("Storage connection not closed: %s",
773 connections[i]->getFilename().get());
774 MOZ_CRASH();
777 #endif
780 return NS_OK;
783 } // namespace mozilla::storage