Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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 "prinit.h"
13 #include "nsAutoPtr.h"
14 #include "nsCollationCID.h"
15 #include "nsEmbedCID.h"
16 #include "nsThreadUtils.h"
17 #include "mozStoragePrivateHelpers.h"
18 #include "nsILocale.h"
19 #include "nsILocaleService.h"
20 #include "nsIXPConnect.h"
21 #include "nsIObserverService.h"
22 #include "nsIPropertyBag2.h"
23 #include "mozilla/Services.h"
24 #include "mozilla/Preferences.h"
25 #include "mozilla/LateWriteChecks.h"
26 #include "mozIStorageCompletionCallback.h"
27 #include "mozIStoragePendingStatement.h"
29 #include "sqlite3.h"
31 #ifdef SQLITE_OS_WIN
32 // "windows.h" was included and it can #define lots of things we care about...
33 #undef CompareString
34 #endif
36 #include "nsIPromptService.h"
38 #ifdef MOZ_STORAGE_MEMORY
39 # include "mozmemory.h"
40 # ifdef MOZ_DMD
41 # include "DMD.h"
42 # endif
43 #endif
45 ////////////////////////////////////////////////////////////////////////////////
46 //// Defines
48 #define PREF_TS_SYNCHRONOUS "toolkit.storage.synchronous"
49 #define PREF_TS_SYNCHRONOUS_DEFAULT 1
51 #define PREF_TS_PAGESIZE "toolkit.storage.pageSize"
53 // This value must be kept in sync with the value of SQLITE_DEFAULT_PAGE_SIZE in
54 // db/sqlite3/src/Makefile.in.
55 #define PREF_TS_PAGESIZE_DEFAULT 32768
57 namespace mozilla {
58 namespace storage {
60 ////////////////////////////////////////////////////////////////////////////////
61 //// Memory Reporting
63 #ifdef MOZ_DMD
64 static mozilla::Atomic<size_t> gSqliteMemoryUsed;
65 #endif
67 static int64_t
68 StorageSQLiteDistinguishedAmount()
69 {
70 return ::sqlite3_memory_used();
71 }
73 /**
74 * Passes a single SQLite memory statistic to a memory reporter callback.
75 *
76 * @param aHandleReport
77 * The callback.
78 * @param aData
79 * The data for the callback.
80 * @param aConn
81 * The SQLite connection.
82 * @param aPathHead
83 * Head of the path for the memory report.
84 * @param aKind
85 * The memory report statistic kind, one of "stmt", "cache" or
86 * "schema".
87 * @param aDesc
88 * The memory report description.
89 * @param aOption
90 * The SQLite constant for getting the measurement.
91 * @param aTotal
92 * The accumulator for the measurement.
93 */
94 nsresult
95 ReportConn(nsIHandleReportCallback *aHandleReport,
96 nsISupports *aData,
97 Connection *aConn,
98 const nsACString &aPathHead,
99 const nsACString &aKind,
100 const nsACString &aDesc,
101 int32_t aOption,
102 size_t *aTotal)
103 {
104 nsCString path(aPathHead);
105 path.Append(aKind);
106 path.AppendLiteral("-used");
108 int32_t val = aConn->getSqliteRuntimeStatus(aOption);
109 nsresult rv = aHandleReport->Callback(EmptyCString(), path,
110 nsIMemoryReporter::KIND_HEAP,
111 nsIMemoryReporter::UNITS_BYTES,
112 int64_t(val), aDesc, aData);
113 NS_ENSURE_SUCCESS(rv, rv);
114 *aTotal += val;
116 return NS_OK;
117 }
119 // Warning: To get a Connection's measurements requires holding its lock.
120 // There may be a delay getting the lock if another thread is accessing the
121 // Connection. This isn't very nice if CollectReports is called from the main
122 // thread! But at the time of writing this function is only called when
123 // about:memory is loaded (not, for example, when telemetry pings occur) and
124 // any delays in that case aren't so bad.
125 NS_IMETHODIMP
126 Service::CollectReports(nsIHandleReportCallback *aHandleReport,
127 nsISupports *aData)
128 {
129 nsresult rv;
130 size_t totalConnSize = 0;
131 {
132 nsTArray<nsRefPtr<Connection> > connections;
133 getConnections(connections);
135 for (uint32_t i = 0; i < connections.Length(); i++) {
136 nsRefPtr<Connection> &conn = connections[i];
138 // Someone may have closed the Connection, in which case we skip it.
139 bool isReady;
140 (void)conn->GetConnectionReady(&isReady);
141 if (!isReady) {
142 continue;
143 }
145 nsCString pathHead("explicit/storage/sqlite/");
146 pathHead.Append(conn->getFilename());
147 pathHead.AppendLiteral("/");
149 SQLiteMutexAutoLock lockedScope(conn->sharedDBMutex);
151 NS_NAMED_LITERAL_CSTRING(stmtDesc,
152 "Memory (approximate) used by all prepared statements used by "
153 "connections to this database.");
154 rv = ReportConn(aHandleReport, aData, conn, pathHead,
155 NS_LITERAL_CSTRING("stmt"), stmtDesc,
156 SQLITE_DBSTATUS_STMT_USED, &totalConnSize);
157 NS_ENSURE_SUCCESS(rv, rv);
159 NS_NAMED_LITERAL_CSTRING(cacheDesc,
160 "Memory (approximate) used by all pager caches used by connections "
161 "to this database.");
162 rv = ReportConn(aHandleReport, aData, conn, pathHead,
163 NS_LITERAL_CSTRING("cache"), cacheDesc,
164 SQLITE_DBSTATUS_CACHE_USED, &totalConnSize);
165 NS_ENSURE_SUCCESS(rv, rv);
167 NS_NAMED_LITERAL_CSTRING(schemaDesc,
168 "Memory (approximate) used to store the schema for all databases "
169 "associated with connections to this database.");
170 rv = ReportConn(aHandleReport, aData, conn, pathHead,
171 NS_LITERAL_CSTRING("schema"), schemaDesc,
172 SQLITE_DBSTATUS_SCHEMA_USED, &totalConnSize);
173 NS_ENSURE_SUCCESS(rv, rv);
174 }
176 #ifdef MOZ_DMD
177 if (::sqlite3_memory_used() != int64_t(gSqliteMemoryUsed)) {
178 NS_WARNING("memory consumption reported by SQLite doesn't match "
179 "our measurements");
180 }
181 #endif
182 }
184 int64_t other = ::sqlite3_memory_used() - totalConnSize;
186 rv = aHandleReport->Callback(
187 EmptyCString(),
188 NS_LITERAL_CSTRING("explicit/storage/sqlite/other"),
189 KIND_HEAP, UNITS_BYTES, other,
190 NS_LITERAL_CSTRING("All unclassified sqlite memory."),
191 aData);
192 NS_ENSURE_SUCCESS(rv, rv);
194 return NS_OK;
195 }
197 ////////////////////////////////////////////////////////////////////////////////
198 //// Service
200 NS_IMPL_ISUPPORTS(
201 Service,
202 mozIStorageService,
203 nsIObserver,
204 nsIMemoryReporter
205 )
207 Service *Service::gService = nullptr;
209 Service *
210 Service::getSingleton()
211 {
212 if (gService) {
213 NS_ADDREF(gService);
214 return gService;
215 }
217 // Ensure that we are using the same version of SQLite that we compiled with
218 // or newer. Our configure check ensures we are using a new enough version
219 // at compile time.
220 if (SQLITE_VERSION_NUMBER > ::sqlite3_libversion_number()) {
221 nsCOMPtr<nsIPromptService> ps(do_GetService(NS_PROMPTSERVICE_CONTRACTID));
222 if (ps) {
223 nsAutoString title, message;
224 title.AppendLiteral("SQLite Version Error");
225 message.AppendLiteral("The application has been updated, but your version "
226 "of SQLite is too old and the application cannot "
227 "run.");
228 (void)ps->Alert(nullptr, title.get(), message.get());
229 }
230 ::PR_Abort();
231 }
233 // The first reference to the storage service must be obtained on the
234 // main thread.
235 NS_ENSURE_TRUE(NS_IsMainThread(), nullptr);
236 gService = new Service();
237 if (gService) {
238 NS_ADDREF(gService);
239 if (NS_FAILED(gService->initialize()))
240 NS_RELEASE(gService);
241 }
243 return gService;
244 }
246 nsIXPConnect *Service::sXPConnect = nullptr;
248 // static
249 already_AddRefed<nsIXPConnect>
250 Service::getXPConnect()
251 {
252 NS_PRECONDITION(NS_IsMainThread(),
253 "Must only get XPConnect on the main thread!");
254 NS_PRECONDITION(gService,
255 "Can not get XPConnect without an instance of our service!");
257 // If we've been shutdown, sXPConnect will be null. To prevent leaks, we do
258 // not cache the service after this point.
259 nsCOMPtr<nsIXPConnect> xpc(sXPConnect);
260 if (!xpc)
261 xpc = do_GetService(nsIXPConnect::GetCID());
262 NS_ASSERTION(xpc, "Could not get XPConnect!");
263 return xpc.forget();
264 }
266 int32_t Service::sSynchronousPref;
268 // static
269 int32_t
270 Service::getSynchronousPref()
271 {
272 return sSynchronousPref;
273 }
275 int32_t Service::sDefaultPageSize = PREF_TS_PAGESIZE_DEFAULT;
277 Service::Service()
278 : mMutex("Service::mMutex")
279 , mSqliteVFS(nullptr)
280 , mRegistrationMutex("Service::mRegistrationMutex")
281 , mConnections()
282 {
283 }
285 Service::~Service()
286 {
287 mozilla::UnregisterWeakMemoryReporter(this);
288 mozilla::UnregisterStorageSQLiteDistinguishedAmount();
290 int rc = sqlite3_vfs_unregister(mSqliteVFS);
291 if (rc != SQLITE_OK)
292 NS_WARNING("Failed to unregister sqlite vfs wrapper.");
294 // Shutdown the sqlite3 API. Warn if shutdown did not turn out okay, but
295 // there is nothing actionable we can do in that case.
296 rc = ::sqlite3_shutdown();
297 if (rc != SQLITE_OK)
298 NS_WARNING("sqlite3 did not shutdown cleanly.");
300 DebugOnly<bool> shutdownObserved = !sXPConnect;
301 NS_ASSERTION(shutdownObserved, "Shutdown was not observed!");
303 gService = nullptr;
304 delete mSqliteVFS;
305 mSqliteVFS = nullptr;
306 }
308 void
309 Service::registerConnection(Connection *aConnection)
310 {
311 mRegistrationMutex.AssertNotCurrentThreadOwns();
312 MutexAutoLock mutex(mRegistrationMutex);
313 (void)mConnections.AppendElement(aConnection);
314 }
316 void
317 Service::unregisterConnection(Connection *aConnection)
318 {
319 // If this is the last Connection it might be the only thing keeping Service
320 // alive. So ensure that Service is destroyed only after the Connection is
321 // cleanly unregistered and destroyed.
322 nsRefPtr<Service> kungFuDeathGrip(this);
323 {
324 mRegistrationMutex.AssertNotCurrentThreadOwns();
325 MutexAutoLock mutex(mRegistrationMutex);
326 DebugOnly<bool> removed = mConnections.RemoveElement(aConnection);
327 // Assert if we try to unregister a non-existent connection.
328 MOZ_ASSERT(removed);
329 }
330 }
332 void
333 Service::getConnections(/* inout */ nsTArray<nsRefPtr<Connection> >& aConnections)
334 {
335 mRegistrationMutex.AssertNotCurrentThreadOwns();
336 MutexAutoLock mutex(mRegistrationMutex);
337 aConnections.Clear();
338 aConnections.AppendElements(mConnections);
339 }
341 void
342 Service::minimizeMemory()
343 {
344 nsTArray<nsRefPtr<Connection> > connections;
345 getConnections(connections);
347 for (uint32_t i = 0; i < connections.Length(); i++) {
348 nsRefPtr<Connection> conn = connections[i];
349 if (conn->connectionReady()) {
350 NS_NAMED_LITERAL_CSTRING(shrinkPragma, "PRAGMA shrink_memory");
351 nsCOMPtr<mozIStorageConnection> syncConn = do_QueryInterface(
352 NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*, conn));
353 DebugOnly<nsresult> rv;
355 if (!syncConn) {
356 nsCOMPtr<mozIStoragePendingStatement> ps;
357 rv = connections[i]->ExecuteSimpleSQLAsync(shrinkPragma, nullptr,
358 getter_AddRefs(ps));
359 } else {
360 rv = connections[i]->ExecuteSimpleSQL(shrinkPragma);
361 }
363 MOZ_ASSERT(NS_SUCCEEDED(rv),
364 "Should have been able to purge sqlite caches");
365 }
366 }
367 }
369 void
370 Service::shutdown()
371 {
372 NS_IF_RELEASE(sXPConnect);
373 }
375 sqlite3_vfs *ConstructTelemetryVFS();
377 #ifdef MOZ_STORAGE_MEMORY
379 namespace {
381 // By default, SQLite tracks the size of all its heap blocks by adding an extra
382 // 8 bytes at the start of the block to hold the size. Unfortunately, this
383 // causes a lot of 2^N-sized allocations to be rounded up by jemalloc
384 // allocator, wasting memory. For example, a request for 1024 bytes has 8
385 // bytes added, becoming a request for 1032 bytes, and jemalloc rounds this up
386 // to 2048 bytes, wasting 1012 bytes. (See bug 676189 for more details.)
387 //
388 // So we register jemalloc as the malloc implementation, which avoids this
389 // 8-byte overhead, and thus a lot of waste. This requires us to provide a
390 // function, sqliteMemRoundup(), which computes the actual size that will be
391 // allocated for a given request. SQLite uses this function before all
392 // allocations, and may be able to use any excess bytes caused by the rounding.
393 //
394 // Note: the wrappers for moz_malloc, moz_realloc and moz_malloc_usable_size
395 // are necessary because the sqlite_mem_methods type signatures differ slightly
396 // from the standard ones -- they use int instead of size_t. But we don't need
397 // a wrapper for moz_free.
399 #ifdef MOZ_DMD
401 // sqlite does its own memory accounting, and we use its numbers in our memory
402 // reporters. But we don't want sqlite's heap blocks to show up in DMD's
403 // output as unreported, so we mark them as reported when they're allocated and
404 // mark them as unreported when they are freed.
405 //
406 // In other words, we are marking all sqlite heap blocks as reported even
407 // though we're not reporting them ourselves. Instead we're trusting that
408 // sqlite is fully and correctly accounting for all of its heap blocks via its
409 // own memory accounting. Well, we don't have to trust it entirely, because
410 // it's easy to keep track (while doing this DMD-specific marking) of exactly
411 // how much memory SQLite is using. And we can compare that against what
412 // SQLite reports it is using.
414 MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC(SqliteMallocSizeOfOnAlloc)
415 MOZ_DEFINE_MALLOC_SIZE_OF_ON_FREE(SqliteMallocSizeOfOnFree)
417 #endif
419 static void *sqliteMemMalloc(int n)
420 {
421 void* p = ::moz_malloc(n);
422 #ifdef MOZ_DMD
423 gSqliteMemoryUsed += SqliteMallocSizeOfOnAlloc(p);
424 #endif
425 return p;
426 }
428 static void sqliteMemFree(void *p)
429 {
430 #ifdef MOZ_DMD
431 gSqliteMemoryUsed -= SqliteMallocSizeOfOnFree(p);
432 #endif
433 ::moz_free(p);
434 }
436 static void *sqliteMemRealloc(void *p, int n)
437 {
438 #ifdef MOZ_DMD
439 gSqliteMemoryUsed -= SqliteMallocSizeOfOnFree(p);
440 void *pnew = ::moz_realloc(p, n);
441 if (pnew) {
442 gSqliteMemoryUsed += SqliteMallocSizeOfOnAlloc(pnew);
443 } else {
444 // realloc failed; undo the SqliteMallocSizeOfOnFree from above
445 gSqliteMemoryUsed += SqliteMallocSizeOfOnAlloc(p);
446 }
447 return pnew;
448 #else
449 return ::moz_realloc(p, n);
450 #endif
451 }
453 static int sqliteMemSize(void *p)
454 {
455 return ::moz_malloc_usable_size(p);
456 }
458 static int sqliteMemRoundup(int n)
459 {
460 n = malloc_good_size(n);
462 // jemalloc can return blocks of size 2 and 4, but SQLite requires that all
463 // allocations be 8-aligned. So we round up sub-8 requests to 8. This
464 // wastes a small amount of memory but is obviously safe.
465 return n <= 8 ? 8 : n;
466 }
468 static int sqliteMemInit(void *p)
469 {
470 return 0;
471 }
473 static void sqliteMemShutdown(void *p)
474 {
475 }
477 const sqlite3_mem_methods memMethods = {
478 &sqliteMemMalloc,
479 &sqliteMemFree,
480 &sqliteMemRealloc,
481 &sqliteMemSize,
482 &sqliteMemRoundup,
483 &sqliteMemInit,
484 &sqliteMemShutdown,
485 nullptr
486 };
488 } // anonymous namespace
490 #endif // MOZ_STORAGE_MEMORY
492 static const char* sObserverTopics[] = {
493 "memory-pressure",
494 "xpcom-shutdown",
495 "xpcom-shutdown-threads"
496 };
498 nsresult
499 Service::initialize()
500 {
501 MOZ_ASSERT(NS_IsMainThread(), "Must be initialized on the main thread");
503 int rc;
505 #ifdef MOZ_STORAGE_MEMORY
506 rc = ::sqlite3_config(SQLITE_CONFIG_MALLOC, &memMethods);
507 if (rc != SQLITE_OK)
508 return convertResultCode(rc);
509 #endif
511 // Explicitly initialize sqlite3. Although this is implicitly called by
512 // various sqlite3 functions (and the sqlite3_open calls in our case),
513 // the documentation suggests calling this directly. So we do.
514 rc = ::sqlite3_initialize();
515 if (rc != SQLITE_OK)
516 return convertResultCode(rc);
518 mSqliteVFS = ConstructTelemetryVFS();
519 if (mSqliteVFS) {
520 rc = sqlite3_vfs_register(mSqliteVFS, 1);
521 if (rc != SQLITE_OK)
522 return convertResultCode(rc);
523 } else {
524 NS_WARNING("Failed to register telemetry VFS");
525 }
527 // Register for xpcom-shutdown so we can cleanup after ourselves. The
528 // observer service can only be used on the main thread.
529 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
530 NS_ENSURE_TRUE(os, NS_ERROR_FAILURE);
532 for (size_t i = 0; i < ArrayLength(sObserverTopics); ++i) {
533 nsresult rv = os->AddObserver(this, sObserverTopics[i], false);
534 if (NS_WARN_IF(NS_FAILED(rv))) {
535 return rv;
536 }
537 }
539 // We cache XPConnect for our language helpers. XPConnect can only be
540 // used on the main thread.
541 (void)CallGetService(nsIXPConnect::GetCID(), &sXPConnect);
543 // We need to obtain the toolkit.storage.synchronous preferences on the main
544 // thread because the preference service can only be accessed there. This
545 // is cached in the service for all future Open[Unshared]Database calls.
546 sSynchronousPref =
547 Preferences::GetInt(PREF_TS_SYNCHRONOUS, PREF_TS_SYNCHRONOUS_DEFAULT);
549 // We need to obtain the toolkit.storage.pageSize preferences on the main
550 // thread because the preference service can only be accessed there. This
551 // is cached in the service for all future Open[Unshared]Database calls.
552 sDefaultPageSize =
553 Preferences::GetInt(PREF_TS_PAGESIZE, PREF_TS_PAGESIZE_DEFAULT);
555 mozilla::RegisterWeakMemoryReporter(this);
556 mozilla::RegisterStorageSQLiteDistinguishedAmount(StorageSQLiteDistinguishedAmount);
558 return NS_OK;
559 }
561 int
562 Service::localeCompareStrings(const nsAString &aStr1,
563 const nsAString &aStr2,
564 int32_t aComparisonStrength)
565 {
566 // The implementation of nsICollation.CompareString() is platform-dependent.
567 // On Linux it's not thread-safe. It may not be on Windows and OS X either,
568 // but it's more difficult to tell. We therefore synchronize this method.
569 MutexAutoLock mutex(mMutex);
571 nsICollation *coll = getLocaleCollation();
572 if (!coll) {
573 NS_ERROR("Storage service has no collation");
574 return 0;
575 }
577 int32_t res;
578 nsresult rv = coll->CompareString(aComparisonStrength, aStr1, aStr2, &res);
579 if (NS_FAILED(rv)) {
580 NS_ERROR("Collation compare string failed");
581 return 0;
582 }
584 return res;
585 }
587 nsICollation *
588 Service::getLocaleCollation()
589 {
590 mMutex.AssertCurrentThreadOwns();
592 if (mLocaleCollation)
593 return mLocaleCollation;
595 nsCOMPtr<nsILocaleService> svc(do_GetService(NS_LOCALESERVICE_CONTRACTID));
596 if (!svc) {
597 NS_WARNING("Could not get locale service");
598 return nullptr;
599 }
601 nsCOMPtr<nsILocale> appLocale;
602 nsresult rv = svc->GetApplicationLocale(getter_AddRefs(appLocale));
603 if (NS_FAILED(rv)) {
604 NS_WARNING("Could not get application locale");
605 return nullptr;
606 }
608 nsCOMPtr<nsICollationFactory> collFact =
609 do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID);
610 if (!collFact) {
611 NS_WARNING("Could not create collation factory");
612 return nullptr;
613 }
615 rv = collFact->CreateCollation(appLocale, getter_AddRefs(mLocaleCollation));
616 if (NS_FAILED(rv)) {
617 NS_WARNING("Could not create collation");
618 return nullptr;
619 }
621 return mLocaleCollation;
622 }
624 ////////////////////////////////////////////////////////////////////////////////
625 //// mozIStorageService
628 NS_IMETHODIMP
629 Service::OpenSpecialDatabase(const char *aStorageKey,
630 mozIStorageConnection **_connection)
631 {
632 nsresult rv;
634 nsCOMPtr<nsIFile> storageFile;
635 if (::strcmp(aStorageKey, "memory") == 0) {
636 // just fall through with nullptr storageFile, this will cause the storage
637 // connection to use a memory DB.
638 }
639 else {
640 return NS_ERROR_INVALID_ARG;
641 }
643 nsRefPtr<Connection> msc = new Connection(this, SQLITE_OPEN_READWRITE, false);
645 rv = storageFile ? msc->initialize(storageFile) : msc->initialize();
646 NS_ENSURE_SUCCESS(rv, rv);
648 msc.forget(_connection);
649 return NS_OK;
651 }
653 namespace {
655 class AsyncInitDatabase MOZ_FINAL : public nsRunnable
656 {
657 public:
658 AsyncInitDatabase(Connection* aConnection,
659 nsIFile* aStorageFile,
660 int32_t aGrowthIncrement,
661 mozIStorageCompletionCallback* aCallback)
662 : mConnection(aConnection)
663 , mStorageFile(aStorageFile)
664 , mGrowthIncrement(aGrowthIncrement)
665 , mCallback(aCallback)
666 {
667 MOZ_ASSERT(NS_IsMainThread());
668 }
670 NS_IMETHOD Run()
671 {
672 MOZ_ASSERT(!NS_IsMainThread());
673 nsresult rv = mStorageFile ? mConnection->initialize(mStorageFile)
674 : mConnection->initialize();
675 if (NS_FAILED(rv)) {
676 return DispatchResult(rv, nullptr);
677 }
679 if (mGrowthIncrement >= 0) {
680 // Ignore errors. In the future, we might wish to log them.
681 (void)mConnection->SetGrowthIncrement(mGrowthIncrement, EmptyCString());
682 }
684 return DispatchResult(NS_OK, NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*,
685 mConnection));
686 }
688 private:
689 nsresult DispatchResult(nsresult aStatus, nsISupports* aValue) {
690 nsRefPtr<CallbackComplete> event =
691 new CallbackComplete(aStatus,
692 aValue,
693 mCallback.forget());
694 return NS_DispatchToMainThread(event);
695 }
697 ~AsyncInitDatabase()
698 {
699 nsCOMPtr<nsIThread> thread;
700 DebugOnly<nsresult> rv = NS_GetMainThread(getter_AddRefs(thread));
701 MOZ_ASSERT(NS_SUCCEEDED(rv));
702 (void)NS_ProxyRelease(thread, mStorageFile);
704 // Handle ambiguous nsISupports inheritance.
705 Connection *rawConnection = nullptr;
706 mConnection.swap(rawConnection);
707 (void)NS_ProxyRelease(thread, NS_ISUPPORTS_CAST(mozIStorageConnection *,
708 rawConnection));
710 // Generally, the callback will be released by CallbackComplete.
711 // However, if for some reason Run() is not executed, we still
712 // need to ensure that it is released here.
713 mozIStorageCompletionCallback *rawCallback = nullptr;
714 mCallback.swap(rawCallback);
715 (void)NS_ProxyRelease(thread, rawCallback);
716 }
718 nsRefPtr<Connection> mConnection;
719 nsCOMPtr<nsIFile> mStorageFile;
720 int32_t mGrowthIncrement;
721 nsRefPtr<mozIStorageCompletionCallback> mCallback;
722 };
724 } // anonymous namespace
726 NS_IMETHODIMP
727 Service::OpenAsyncDatabase(nsIVariant *aDatabaseStore,
728 nsIPropertyBag2 *aOptions,
729 mozIStorageCompletionCallback *aCallback)
730 {
731 if (!NS_IsMainThread()) {
732 return NS_ERROR_NOT_SAME_THREAD;
733 }
734 NS_ENSURE_ARG(aDatabaseStore);
735 NS_ENSURE_ARG(aCallback);
737 nsCOMPtr<nsIFile> storageFile;
738 int flags = SQLITE_OPEN_READWRITE;
740 nsCOMPtr<nsISupports> dbStore;
741 nsresult rv = aDatabaseStore->GetAsISupports(getter_AddRefs(dbStore));
742 if (NS_SUCCEEDED(rv)) {
743 // Generally, aDatabaseStore holds the database nsIFile.
744 storageFile = do_QueryInterface(dbStore, &rv);
745 if (NS_FAILED(rv)) {
746 return NS_ERROR_INVALID_ARG;
747 }
749 rv = storageFile->Clone(getter_AddRefs(storageFile));
750 MOZ_ASSERT(NS_SUCCEEDED(rv));
752 // Ensure that SQLITE_OPEN_CREATE is passed in for compatibility reasons.
753 flags |= SQLITE_OPEN_CREATE;
755 // Extract and apply the shared-cache option.
756 bool shared = false;
757 if (aOptions) {
758 rv = aOptions->GetPropertyAsBool(NS_LITERAL_STRING("shared"), &shared);
759 if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) {
760 return NS_ERROR_INVALID_ARG;
761 }
762 }
763 flags |= shared ? SQLITE_OPEN_SHAREDCACHE : SQLITE_OPEN_PRIVATECACHE;
764 } else {
765 // Sometimes, however, it's a special database name.
766 nsAutoCString keyString;
767 rv = aDatabaseStore->GetAsACString(keyString);
768 if (NS_FAILED(rv) || !keyString.EqualsLiteral("memory")) {
769 return NS_ERROR_INVALID_ARG;
770 }
772 // Just fall through with nullptr storageFile, this will cause the storage
773 // connection to use a memory DB.
774 }
776 int32_t growthIncrement = -1;
777 if (aOptions && storageFile) {
778 rv = aOptions->GetPropertyAsInt32(NS_LITERAL_STRING("growthIncrement"),
779 &growthIncrement);
780 if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) {
781 return NS_ERROR_INVALID_ARG;
782 }
783 }
785 // Create connection on this thread, but initialize it on its helper thread.
786 nsRefPtr<Connection> msc = new Connection(this, flags, true);
787 nsCOMPtr<nsIEventTarget> target = msc->getAsyncExecutionTarget();
788 MOZ_ASSERT(target, "Cannot initialize a connection that has been closed already");
790 nsRefPtr<AsyncInitDatabase> asyncInit =
791 new AsyncInitDatabase(msc,
792 storageFile,
793 growthIncrement,
794 aCallback);
795 return target->Dispatch(asyncInit, nsIEventTarget::DISPATCH_NORMAL);
796 }
798 NS_IMETHODIMP
799 Service::OpenDatabase(nsIFile *aDatabaseFile,
800 mozIStorageConnection **_connection)
801 {
802 NS_ENSURE_ARG(aDatabaseFile);
804 // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
805 // reasons.
806 int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE |
807 SQLITE_OPEN_CREATE;
808 nsRefPtr<Connection> msc = new Connection(this, flags, false);
810 nsresult rv = msc->initialize(aDatabaseFile);
811 NS_ENSURE_SUCCESS(rv, rv);
813 msc.forget(_connection);
814 return NS_OK;
815 }
817 NS_IMETHODIMP
818 Service::OpenUnsharedDatabase(nsIFile *aDatabaseFile,
819 mozIStorageConnection **_connection)
820 {
821 NS_ENSURE_ARG(aDatabaseFile);
823 // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
824 // reasons.
825 int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_PRIVATECACHE |
826 SQLITE_OPEN_CREATE;
827 nsRefPtr<Connection> msc = new Connection(this, flags, false);
829 nsresult rv = msc->initialize(aDatabaseFile);
830 NS_ENSURE_SUCCESS(rv, rv);
832 msc.forget(_connection);
833 return NS_OK;
834 }
836 NS_IMETHODIMP
837 Service::OpenDatabaseWithFileURL(nsIFileURL *aFileURL,
838 mozIStorageConnection **_connection)
839 {
840 NS_ENSURE_ARG(aFileURL);
842 // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
843 // reasons.
844 int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE |
845 SQLITE_OPEN_CREATE | SQLITE_OPEN_URI;
846 nsRefPtr<Connection> msc = new Connection(this, flags, false);
848 nsresult rv = msc->initialize(aFileURL);
849 NS_ENSURE_SUCCESS(rv, rv);
851 msc.forget(_connection);
852 return NS_OK;
853 }
855 NS_IMETHODIMP
856 Service::BackupDatabaseFile(nsIFile *aDBFile,
857 const nsAString &aBackupFileName,
858 nsIFile *aBackupParentDirectory,
859 nsIFile **backup)
860 {
861 nsresult rv;
862 nsCOMPtr<nsIFile> parentDir = aBackupParentDirectory;
863 if (!parentDir) {
864 // This argument is optional, and defaults to the same parent directory
865 // as the current file.
866 rv = aDBFile->GetParent(getter_AddRefs(parentDir));
867 NS_ENSURE_SUCCESS(rv, rv);
868 }
870 nsCOMPtr<nsIFile> backupDB;
871 rv = parentDir->Clone(getter_AddRefs(backupDB));
872 NS_ENSURE_SUCCESS(rv, rv);
874 rv = backupDB->Append(aBackupFileName);
875 NS_ENSURE_SUCCESS(rv, rv);
877 rv = backupDB->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
878 NS_ENSURE_SUCCESS(rv, rv);
880 nsAutoString fileName;
881 rv = backupDB->GetLeafName(fileName);
882 NS_ENSURE_SUCCESS(rv, rv);
884 rv = backupDB->Remove(false);
885 NS_ENSURE_SUCCESS(rv, rv);
887 backupDB.forget(backup);
889 return aDBFile->CopyTo(parentDir, fileName);
890 }
892 ////////////////////////////////////////////////////////////////////////////////
893 //// nsIObserver
895 NS_IMETHODIMP
896 Service::Observe(nsISupports *, const char *aTopic, const char16_t *)
897 {
898 if (strcmp(aTopic, "memory-pressure") == 0) {
899 minimizeMemory();
900 } else if (strcmp(aTopic, "xpcom-shutdown") == 0) {
901 shutdown();
902 } else if (strcmp(aTopic, "xpcom-shutdown-threads") == 0) {
903 nsCOMPtr<nsIObserverService> os =
904 mozilla::services::GetObserverService();
906 for (size_t i = 0; i < ArrayLength(sObserverTopics); ++i) {
907 (void)os->RemoveObserver(this, sObserverTopics[i]);
908 }
910 bool anyOpen = false;
911 do {
912 nsTArray<nsRefPtr<Connection> > connections;
913 getConnections(connections);
914 anyOpen = false;
915 for (uint32_t i = 0; i < connections.Length(); i++) {
916 nsRefPtr<Connection> &conn = connections[i];
917 if (conn->isClosing()) {
918 anyOpen = true;
919 break;
920 }
921 }
922 if (anyOpen) {
923 nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
924 NS_ProcessNextEvent(thread);
925 }
926 } while (anyOpen);
928 if (gShutdownChecks == SCM_CRASH) {
929 nsTArray<nsRefPtr<Connection> > connections;
930 getConnections(connections);
931 for (uint32_t i = 0, n = connections.Length(); i < n; i++) {
932 if (!connections[i]->isClosed()) {
933 MOZ_CRASH();
934 }
935 }
936 }
937 }
939 return NS_OK;
940 }
942 } // namespace storage
943 } // namespace mozilla