Bug 1632310 [wpt PR 23186] - Add test for computed versus resolved style., a=testonly
[gecko.git] / storage / mozStorageService.cpp
blob92397c77de7d4d7068d11ee4f8b9bc2f4ef8b97a
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"
10 #include "mozStorageService.h"
11 #include "mozStorageConnection.h"
12 #include "nsCollationCID.h"
13 #include "nsComponentManagerUtils.h"
14 #include "nsEmbedCID.h"
15 #include "nsExceptionHandler.h"
16 #include "nsThreadUtils.h"
17 #include "mozStoragePrivateHelpers.h"
18 #include "nsIObserverService.h"
19 #include "nsIPropertyBag2.h"
20 #include "mozilla/Services.h"
21 #include "mozilla/Preferences.h"
22 #include "mozilla/LateWriteChecks.h"
23 #include "mozIStorageCompletionCallback.h"
24 #include "mozIStoragePendingStatement.h"
26 #include "sqlite3.h"
27 #include "mozilla/AutoSQLiteLifetime.h"
29 #ifdef XP_WIN
30 // "windows.h" was included and it can #define lots of things we care about...
31 # undef CompareString
32 #endif
34 ////////////////////////////////////////////////////////////////////////////////
35 //// Defines
37 #define PREF_TS_SYNCHRONOUS "toolkit.storage.synchronous"
38 #define PREF_TS_SYNCHRONOUS_DEFAULT 1
40 #define PREF_TS_PAGESIZE "toolkit.storage.pageSize"
42 // This value must be kept in sync with the value of SQLITE_DEFAULT_PAGE_SIZE in
43 // third_party/sqlite3/src/Makefile.in.
44 #define PREF_TS_PAGESIZE_DEFAULT 32768
46 namespace mozilla {
47 namespace storage {
49 ////////////////////////////////////////////////////////////////////////////////
50 //// Memory Reporting
52 #ifdef MOZ_DMD
53 mozilla::Atomic<size_t> gSqliteMemoryUsed;
54 #endif
56 static int64_t StorageSQLiteDistinguishedAmount() {
57 return ::sqlite3_memory_used();
60 /**
61 * Passes a single SQLite memory statistic to a memory reporter callback.
63 * @param aHandleReport
64 * The callback.
65 * @param aData
66 * The data for the callback.
67 * @param aConn
68 * The SQLite connection.
69 * @param aPathHead
70 * Head of the path for the memory report.
71 * @param aKind
72 * The memory report statistic kind, one of "stmt", "cache" or
73 * "schema".
74 * @param aDesc
75 * The memory report description.
76 * @param aOption
77 * The SQLite constant for getting the measurement.
78 * @param aTotal
79 * The accumulator for the measurement.
81 static void ReportConn(nsIHandleReportCallback* aHandleReport,
82 nsISupports* aData, Connection* aConn,
83 const nsACString& aPathHead, const nsACString& aKind,
84 const nsACString& aDesc, int32_t aOption,
85 size_t* aTotal) {
86 nsCString path(aPathHead);
87 path.Append(aKind);
88 path.AppendLiteral("-used");
90 int32_t val = aConn->getSqliteRuntimeStatus(aOption);
91 aHandleReport->Callback(EmptyCString(), path, nsIMemoryReporter::KIND_HEAP,
92 nsIMemoryReporter::UNITS_BYTES, int64_t(val), aDesc,
93 aData);
94 *aTotal += val;
97 // Warning: To get a Connection's measurements requires holding its lock.
98 // There may be a delay getting the lock if another thread is accessing the
99 // Connection. This isn't very nice if CollectReports is called from the main
100 // thread! But at the time of writing this function is only called when
101 // about:memory is loaded (not, for example, when telemetry pings occur) and
102 // any delays in that case aren't so bad.
103 NS_IMETHODIMP
104 Service::CollectReports(nsIHandleReportCallback* aHandleReport,
105 nsISupports* aData, bool aAnonymize) {
106 size_t totalConnSize = 0;
108 nsTArray<RefPtr<Connection>> connections;
109 getConnections(connections);
111 for (uint32_t i = 0; i < connections.Length(); i++) {
112 RefPtr<Connection>& conn = connections[i];
114 // Someone may have closed the Connection, in which case we skip it.
115 // Note that we have consumers of the synchronous API that are off the
116 // main-thread, like the DOM Cache and IndexedDB, and as such we must be
117 // sure that we have a connection.
118 MutexAutoLock lockedAsyncScope(conn->sharedAsyncExecutionMutex);
119 if (!conn->connectionReady()) {
120 continue;
123 nsCString pathHead("explicit/storage/sqlite/");
124 // This filename isn't privacy-sensitive, and so is never anonymized.
125 pathHead.Append(conn->getFilename());
126 pathHead.Append('/');
128 SQLiteMutexAutoLock lockedScope(conn->sharedDBMutex);
130 NS_NAMED_LITERAL_CSTRING(
131 stmtDesc,
132 "Memory (approximate) used by all prepared statements used by "
133 "connections to this database.");
134 ReportConn(aHandleReport, aData, conn, pathHead,
135 NS_LITERAL_CSTRING("stmt"), stmtDesc,
136 SQLITE_DBSTATUS_STMT_USED, &totalConnSize);
138 NS_NAMED_LITERAL_CSTRING(
139 cacheDesc,
140 "Memory (approximate) used by all pager caches used by connections "
141 "to this database.");
142 ReportConn(aHandleReport, aData, conn, pathHead,
143 NS_LITERAL_CSTRING("cache"), cacheDesc,
144 SQLITE_DBSTATUS_CACHE_USED_SHARED, &totalConnSize);
146 NS_NAMED_LITERAL_CSTRING(
147 schemaDesc,
148 "Memory (approximate) used to store the schema for all databases "
149 "associated with connections to this database.");
150 ReportConn(aHandleReport, aData, conn, pathHead,
151 NS_LITERAL_CSTRING("schema"), schemaDesc,
152 SQLITE_DBSTATUS_SCHEMA_USED, &totalConnSize);
155 #ifdef MOZ_DMD
156 if (::sqlite3_memory_used() != int64_t(gSqliteMemoryUsed)) {
157 NS_WARNING(
158 "memory consumption reported by SQLite doesn't match "
159 "our measurements");
161 #endif
164 int64_t other = ::sqlite3_memory_used() - totalConnSize;
166 MOZ_COLLECT_REPORT("explicit/storage/sqlite/other", KIND_HEAP, UNITS_BYTES,
167 other, "All unclassified sqlite memory.");
169 return NS_OK;
172 ////////////////////////////////////////////////////////////////////////////////
173 //// Service
175 NS_IMPL_ISUPPORTS(Service, mozIStorageService, nsIObserver, nsIMemoryReporter)
177 Service* Service::gService = nullptr;
179 already_AddRefed<Service> Service::getSingleton() {
180 if (gService) {
181 return do_AddRef(gService);
184 // The first reference to the storage service must be obtained on the
185 // main thread.
186 NS_ENSURE_TRUE(NS_IsMainThread(), nullptr);
187 RefPtr<Service> service = new Service();
188 if (NS_SUCCEEDED(service->initialize())) {
189 // Note: This is cleared in the Service destructor.
190 gService = service.get();
191 return service.forget();
194 return nullptr;
197 int32_t Service::sSynchronousPref;
199 // static
200 int32_t Service::getSynchronousPref() { return sSynchronousPref; }
202 int32_t Service::sDefaultPageSize = PREF_TS_PAGESIZE_DEFAULT;
204 Service::Service()
205 : mMutex("Service::mMutex"),
206 mSqliteVFS(nullptr),
207 mRegistrationMutex("Service::mRegistrationMutex"),
208 mConnections() {}
210 Service::~Service() {
211 mozilla::UnregisterWeakMemoryReporter(this);
212 mozilla::UnregisterStorageSQLiteDistinguishedAmount();
214 int rc = sqlite3_vfs_unregister(mSqliteVFS);
215 if (rc != SQLITE_OK) NS_WARNING("Failed to unregister sqlite vfs wrapper.");
217 gService = nullptr;
218 delete mSqliteVFS;
219 mSqliteVFS = nullptr;
222 void Service::registerConnection(Connection* aConnection) {
223 mRegistrationMutex.AssertNotCurrentThreadOwns();
224 MutexAutoLock mutex(mRegistrationMutex);
225 (void)mConnections.AppendElement(aConnection);
228 void Service::unregisterConnection(Connection* aConnection) {
229 // If this is the last Connection it might be the only thing keeping Service
230 // alive. So ensure that Service is destroyed only after the Connection is
231 // cleanly unregistered and destroyed.
232 RefPtr<Service> kungFuDeathGrip(this);
233 RefPtr<Connection> forgettingRef;
235 mRegistrationMutex.AssertNotCurrentThreadOwns();
236 MutexAutoLock mutex(mRegistrationMutex);
238 for (uint32_t i = 0; i < mConnections.Length(); ++i) {
239 if (mConnections[i] == aConnection) {
240 // Because dropping the final reference can potentially result in
241 // spinning a nested event loop if the connection was not properly
242 // shutdown, we want to do that outside this loop so that we can finish
243 // mutating the array and drop our mutex.
244 forgettingRef = std::move(mConnections[i]);
245 mConnections.RemoveElementAt(i);
246 break;
251 MOZ_ASSERT(forgettingRef,
252 "Attempt to unregister unknown storage connection!");
254 // Do not proxy the release anywhere, just let this reference drop here. (We
255 // previously did proxy the release, but that was because we invoked Close()
256 // in the destructor and Close() likes to complain if it's not invoked on the
257 // opener thread, so it was essential that the last reference be dropped on
258 // the opener thread. We now enqueue Close() inside our caller, Release(), so
259 // it doesn't actually matter what thread our reference drops on.)
262 void Service::getConnections(
263 /* inout */ nsTArray<RefPtr<Connection>>& aConnections) {
264 mRegistrationMutex.AssertNotCurrentThreadOwns();
265 MutexAutoLock mutex(mRegistrationMutex);
266 aConnections.Clear();
267 aConnections.AppendElements(mConnections);
270 void Service::minimizeMemory() {
271 nsTArray<RefPtr<Connection>> connections;
272 getConnections(connections);
274 for (uint32_t i = 0; i < connections.Length(); i++) {
275 RefPtr<Connection> conn = connections[i];
276 // For non-main-thread owning/opening threads, we may be racing against them
277 // closing their connection or their thread. That's okay, see below.
278 if (!conn->connectionReady()) {
279 continue;
282 NS_NAMED_LITERAL_CSTRING(shrinkPragma, "PRAGMA shrink_memory");
283 bool onOpenedThread = false;
285 if (!conn->operationSupported(Connection::SYNCHRONOUS)) {
286 // This is a mozIStorageAsyncConnection, it can only be used on the main
287 // thread, so we can do a straight API call.
288 nsCOMPtr<mozIStoragePendingStatement> ps;
289 DebugOnly<nsresult> rv = conn->ExecuteSimpleSQLAsync(
290 shrinkPragma, nullptr, getter_AddRefs(ps));
291 MOZ_ASSERT(NS_SUCCEEDED(rv), "Should have purged sqlite caches");
292 } else if (NS_SUCCEEDED(
293 conn->threadOpenedOn->IsOnCurrentThread(&onOpenedThread)) &&
294 onOpenedThread) {
295 if (conn->isAsyncExecutionThreadAvailable()) {
296 nsCOMPtr<mozIStoragePendingStatement> ps;
297 DebugOnly<nsresult> rv = conn->ExecuteSimpleSQLAsync(
298 shrinkPragma, nullptr, getter_AddRefs(ps));
299 MOZ_ASSERT(NS_SUCCEEDED(rv), "Should have purged sqlite caches");
300 } else {
301 conn->ExecuteSimpleSQL(shrinkPragma);
303 } else {
304 // We are on the wrong thread, the query should be executed on the
305 // opener thread, so we must dispatch to it.
306 // It's possible the connection is already closed or will be closed by the
307 // time our runnable runs. ExecuteSimpleSQL will safely return with a
308 // failure in that case. If the thread is shutting down or shut down, the
309 // dispatch will fail and that's okay.
310 nsCOMPtr<nsIRunnable> event = NewRunnableMethod<const nsCString>(
311 "Connection::ExecuteSimpleSQL", conn, &Connection::ExecuteSimpleSQL,
312 shrinkPragma);
313 Unused << conn->threadOpenedOn->Dispatch(event, NS_DISPATCH_NORMAL);
318 sqlite3_vfs* ConstructTelemetryVFS();
319 const char* GetVFSName();
321 static const char* sObserverTopics[] = {"memory-pressure",
322 "xpcom-shutdown-threads"};
324 nsresult Service::initialize() {
325 MOZ_ASSERT(NS_IsMainThread(), "Must be initialized on the main thread");
327 int rc = AutoSQLiteLifetime::getInitResult();
328 if (rc != SQLITE_OK) return convertResultCode(rc);
330 mSqliteVFS = ConstructTelemetryVFS();
331 if (mSqliteVFS) {
332 rc = sqlite3_vfs_register(mSqliteVFS, 0);
333 if (rc != SQLITE_OK) return convertResultCode(rc);
334 } else {
335 NS_WARNING("Failed to register telemetry VFS");
338 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
339 NS_ENSURE_TRUE(os, NS_ERROR_FAILURE);
341 for (size_t i = 0; i < ArrayLength(sObserverTopics); ++i) {
342 nsresult rv = os->AddObserver(this, sObserverTopics[i], false);
343 if (NS_WARN_IF(NS_FAILED(rv))) {
344 return rv;
348 // We need to obtain the toolkit.storage.synchronous preferences on the main
349 // thread because the preference service can only be accessed there. This
350 // is cached in the service for all future Open[Unshared]Database calls.
351 sSynchronousPref =
352 Preferences::GetInt(PREF_TS_SYNCHRONOUS, PREF_TS_SYNCHRONOUS_DEFAULT);
354 // We need to obtain the toolkit.storage.pageSize preferences on the main
355 // thread because the preference service can only be accessed there. This
356 // is cached in the service for all future Open[Unshared]Database calls.
357 sDefaultPageSize =
358 Preferences::GetInt(PREF_TS_PAGESIZE, PREF_TS_PAGESIZE_DEFAULT);
360 mozilla::RegisterWeakMemoryReporter(this);
361 mozilla::RegisterStorageSQLiteDistinguishedAmount(
362 StorageSQLiteDistinguishedAmount);
364 return NS_OK;
367 int Service::localeCompareStrings(const nsAString& aStr1,
368 const nsAString& aStr2,
369 int32_t aComparisonStrength) {
370 // The implementation of nsICollation.CompareString() is platform-dependent.
371 // On Linux it's not thread-safe. It may not be on Windows and OS X either,
372 // but it's more difficult to tell. We therefore synchronize this method.
373 MutexAutoLock mutex(mMutex);
375 nsICollation* coll = getLocaleCollation();
376 if (!coll) {
377 NS_ERROR("Storage service has no collation");
378 return 0;
381 int32_t res;
382 nsresult rv = coll->CompareString(aComparisonStrength, aStr1, aStr2, &res);
383 if (NS_FAILED(rv)) {
384 NS_ERROR("Collation compare string failed");
385 return 0;
388 return res;
391 nsICollation* Service::getLocaleCollation() {
392 mMutex.AssertCurrentThreadOwns();
394 if (mLocaleCollation) return mLocaleCollation;
396 nsCOMPtr<nsICollationFactory> collFact =
397 do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID);
398 if (!collFact) {
399 NS_WARNING("Could not create collation factory");
400 return nullptr;
403 nsresult rv = collFact->CreateCollation(getter_AddRefs(mLocaleCollation));
404 if (NS_FAILED(rv)) {
405 NS_WARNING("Could not create collation");
406 return nullptr;
409 return mLocaleCollation;
412 ////////////////////////////////////////////////////////////////////////////////
413 //// mozIStorageService
415 NS_IMETHODIMP
416 Service::OpenSpecialDatabase(const char* aStorageKey,
417 mozIStorageConnection** _connection) {
418 nsresult rv;
420 nsCOMPtr<nsIFile> storageFile;
421 if (::strcmp(aStorageKey, "memory") == 0) {
422 // just fall through with nullptr storageFile, this will cause the storage
423 // connection to use a memory DB.
424 } else {
425 return NS_ERROR_INVALID_ARG;
428 RefPtr<Connection> msc =
429 new Connection(this, SQLITE_OPEN_READWRITE, Connection::SYNCHRONOUS);
431 rv = storageFile ? msc->initialize(storageFile) : msc->initialize();
432 NS_ENSURE_SUCCESS(rv, rv);
434 msc.forget(_connection);
435 return NS_OK;
438 namespace {
440 class AsyncInitDatabase final : public Runnable {
441 public:
442 AsyncInitDatabase(Connection* aConnection, nsIFile* aStorageFile,
443 int32_t aGrowthIncrement,
444 mozIStorageCompletionCallback* aCallback)
445 : Runnable("storage::AsyncInitDatabase"),
446 mConnection(aConnection),
447 mStorageFile(aStorageFile),
448 mGrowthIncrement(aGrowthIncrement),
449 mCallback(aCallback) {
450 MOZ_ASSERT(NS_IsMainThread());
453 NS_IMETHOD Run() override {
454 MOZ_ASSERT(!NS_IsMainThread());
455 nsresult rv = mConnection->initializeOnAsyncThread(mStorageFile);
456 if (NS_FAILED(rv)) {
457 return DispatchResult(rv, nullptr);
460 if (mGrowthIncrement >= 0) {
461 // Ignore errors. In the future, we might wish to log them.
462 (void)mConnection->SetGrowthIncrement(mGrowthIncrement, EmptyCString());
465 return DispatchResult(
466 NS_OK, NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*, mConnection));
469 private:
470 nsresult DispatchResult(nsresult aStatus, nsISupports* aValue) {
471 RefPtr<CallbackComplete> event =
472 new CallbackComplete(aStatus, aValue, mCallback.forget());
473 return NS_DispatchToMainThread(event);
476 ~AsyncInitDatabase() {
477 NS_ReleaseOnMainThread("AsyncInitDatabase::mStorageFile",
478 mStorageFile.forget());
479 NS_ReleaseOnMainThread("AsyncInitDatabase::mConnection",
480 mConnection.forget());
482 // Generally, the callback will be released by CallbackComplete.
483 // However, if for some reason Run() is not executed, we still
484 // need to ensure that it is released here.
485 NS_ReleaseOnMainThread("AsyncInitDatabase::mCallback", mCallback.forget());
488 RefPtr<Connection> mConnection;
489 nsCOMPtr<nsIFile> mStorageFile;
490 int32_t mGrowthIncrement;
491 RefPtr<mozIStorageCompletionCallback> mCallback;
494 } // namespace
496 NS_IMETHODIMP
497 Service::OpenAsyncDatabase(nsIVariant* aDatabaseStore,
498 nsIPropertyBag2* aOptions,
499 mozIStorageCompletionCallback* aCallback) {
500 if (!NS_IsMainThread()) {
501 return NS_ERROR_NOT_SAME_THREAD;
503 NS_ENSURE_ARG(aDatabaseStore);
504 NS_ENSURE_ARG(aCallback);
506 nsresult rv;
507 bool shared = false;
508 bool readOnly = false;
509 bool ignoreLockingMode = false;
510 int32_t growthIncrement = -1;
512 #define FAIL_IF_SET_BUT_INVALID(rv) \
513 if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) { \
514 return NS_ERROR_INVALID_ARG; \
517 // Deal with options first:
518 if (aOptions) {
519 rv = aOptions->GetPropertyAsBool(NS_LITERAL_STRING("readOnly"), &readOnly);
520 FAIL_IF_SET_BUT_INVALID(rv);
522 rv = aOptions->GetPropertyAsBool(NS_LITERAL_STRING("ignoreLockingMode"),
523 &ignoreLockingMode);
524 FAIL_IF_SET_BUT_INVALID(rv);
525 // Specifying ignoreLockingMode will force use of the readOnly flag:
526 if (ignoreLockingMode) {
527 readOnly = true;
530 rv = aOptions->GetPropertyAsBool(NS_LITERAL_STRING("shared"), &shared);
531 FAIL_IF_SET_BUT_INVALID(rv);
533 // NB: we re-set to -1 if we don't have a storage file later on.
534 rv = aOptions->GetPropertyAsInt32(NS_LITERAL_STRING("growthIncrement"),
535 &growthIncrement);
536 FAIL_IF_SET_BUT_INVALID(rv);
538 int flags = readOnly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_READWRITE;
540 nsCOMPtr<nsIFile> storageFile;
541 nsCOMPtr<nsISupports> dbStore;
542 rv = aDatabaseStore->GetAsISupports(getter_AddRefs(dbStore));
543 if (NS_SUCCEEDED(rv)) {
544 // Generally, aDatabaseStore holds the database nsIFile.
545 storageFile = do_QueryInterface(dbStore, &rv);
546 if (NS_FAILED(rv)) {
547 return NS_ERROR_INVALID_ARG;
550 nsCOMPtr<nsIFile> cloned;
551 rv = storageFile->Clone(getter_AddRefs(cloned));
552 MOZ_ASSERT(NS_SUCCEEDED(rv));
553 storageFile = std::move(cloned);
555 if (!readOnly) {
556 // Ensure that SQLITE_OPEN_CREATE is passed in for compatibility reasons.
557 flags |= SQLITE_OPEN_CREATE;
560 // Apply the shared-cache option.
561 flags |= shared ? SQLITE_OPEN_SHAREDCACHE : SQLITE_OPEN_PRIVATECACHE;
562 } else {
563 // Sometimes, however, it's a special database name.
564 nsAutoCString keyString;
565 rv = aDatabaseStore->GetAsACString(keyString);
566 if (NS_FAILED(rv) || !keyString.EqualsLiteral("memory")) {
567 return NS_ERROR_INVALID_ARG;
570 // Just fall through with nullptr storageFile, this will cause the storage
571 // connection to use a memory DB.
574 if (!storageFile && growthIncrement >= 0) {
575 return NS_ERROR_INVALID_ARG;
578 // Create connection on this thread, but initialize it on its helper thread.
579 RefPtr<Connection> msc =
580 new Connection(this, flags, Connection::ASYNCHRONOUS, ignoreLockingMode);
581 nsCOMPtr<nsIEventTarget> target = msc->getAsyncExecutionTarget();
582 MOZ_ASSERT(target,
583 "Cannot initialize a connection that has been closed already");
585 RefPtr<AsyncInitDatabase> asyncInit =
586 new AsyncInitDatabase(msc, storageFile, growthIncrement, aCallback);
587 return target->Dispatch(asyncInit, nsIEventTarget::DISPATCH_NORMAL);
590 NS_IMETHODIMP
591 Service::OpenDatabase(nsIFile* aDatabaseFile,
592 mozIStorageConnection** _connection) {
593 NS_ENSURE_ARG(aDatabaseFile);
595 // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
596 // reasons.
597 int flags =
598 SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE | SQLITE_OPEN_CREATE;
599 RefPtr<Connection> msc = new Connection(this, flags, Connection::SYNCHRONOUS);
601 nsresult rv = msc->initialize(aDatabaseFile);
602 NS_ENSURE_SUCCESS(rv, rv);
604 msc.forget(_connection);
605 return NS_OK;
608 NS_IMETHODIMP
609 Service::OpenUnsharedDatabase(nsIFile* aDatabaseFile,
610 mozIStorageConnection** _connection) {
611 NS_ENSURE_ARG(aDatabaseFile);
613 // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
614 // reasons.
615 int flags =
616 SQLITE_OPEN_READWRITE | SQLITE_OPEN_PRIVATECACHE | SQLITE_OPEN_CREATE;
617 RefPtr<Connection> msc = new Connection(this, flags, Connection::SYNCHRONOUS);
619 nsresult rv = msc->initialize(aDatabaseFile);
620 NS_ENSURE_SUCCESS(rv, rv);
622 msc.forget(_connection);
623 return NS_OK;
626 NS_IMETHODIMP
627 Service::OpenDatabaseWithFileURL(nsIFileURL* aFileURL,
628 mozIStorageConnection** _connection) {
629 NS_ENSURE_ARG(aFileURL);
631 // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
632 // reasons.
633 int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE |
634 SQLITE_OPEN_CREATE | SQLITE_OPEN_URI;
635 RefPtr<Connection> msc = new Connection(this, flags, Connection::SYNCHRONOUS);
637 nsresult rv = msc->initialize(aFileURL);
638 NS_ENSURE_SUCCESS(rv, rv);
640 msc.forget(_connection);
641 return NS_OK;
644 NS_IMETHODIMP
645 Service::BackupDatabaseFile(nsIFile* aDBFile, const nsAString& aBackupFileName,
646 nsIFile* aBackupParentDirectory, nsIFile** backup) {
647 nsresult rv;
648 nsCOMPtr<nsIFile> parentDir = aBackupParentDirectory;
649 if (!parentDir) {
650 // This argument is optional, and defaults to the same parent directory
651 // as the current file.
652 rv = aDBFile->GetParent(getter_AddRefs(parentDir));
653 NS_ENSURE_SUCCESS(rv, rv);
656 nsCOMPtr<nsIFile> backupDB;
657 rv = parentDir->Clone(getter_AddRefs(backupDB));
658 NS_ENSURE_SUCCESS(rv, rv);
660 rv = backupDB->Append(aBackupFileName);
661 NS_ENSURE_SUCCESS(rv, rv);
663 rv = backupDB->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
664 NS_ENSURE_SUCCESS(rv, rv);
666 nsAutoString fileName;
667 rv = backupDB->GetLeafName(fileName);
668 NS_ENSURE_SUCCESS(rv, rv);
670 rv = backupDB->Remove(false);
671 NS_ENSURE_SUCCESS(rv, rv);
673 backupDB.forget(backup);
675 return aDBFile->CopyTo(parentDir, fileName);
678 ////////////////////////////////////////////////////////////////////////////////
679 //// nsIObserver
681 NS_IMETHODIMP
682 Service::Observe(nsISupports*, const char* aTopic, const char16_t*) {
683 if (strcmp(aTopic, "memory-pressure") == 0) {
684 minimizeMemory();
685 } else if (strcmp(aTopic, "xpcom-shutdown-threads") == 0) {
686 // The Service is kept alive by our strong observer references and
687 // references held by Connection instances. Since we're about to remove the
688 // former and then wait for the latter ones to go away, it behooves us to
689 // hold a strong reference to ourselves so our calls to getConnections() do
690 // not happen on a deleted object.
691 RefPtr<Service> kungFuDeathGrip = this;
693 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
695 for (size_t i = 0; i < ArrayLength(sObserverTopics); ++i) {
696 (void)os->RemoveObserver(this, sObserverTopics[i]);
699 SpinEventLoopUntil([&]() -> bool {
700 // We must wait until all the closing connections are closed.
701 nsTArray<RefPtr<Connection>> connections;
702 getConnections(connections);
703 for (auto& conn : connections) {
704 if (conn->isClosing()) {
705 return false;
708 return true;
711 #ifdef DEBUG
712 nsTArray<RefPtr<Connection>> connections;
713 getConnections(connections);
714 for (uint32_t i = 0, n = connections.Length(); i < n; i++) {
715 if (!connections[i]->isClosed()) {
716 // getFilename is only the leaf name for the database file,
717 // so it shouldn't contain privacy-sensitive information.
718 CrashReporter::AnnotateCrashReport(
719 CrashReporter::Annotation::StorageConnectionNotClosed,
720 connections[i]->getFilename());
721 printf_stderr("Storage connection not closed: %s",
722 connections[i]->getFilename().get());
723 MOZ_CRASH();
726 #endif
729 return NS_OK;
732 } // namespace storage
733 } // namespace mozilla