1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=2 et sw=2 tw=80: */
3 : /* ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is Indexed Database.
17 : *
18 : * The Initial Developer of the Original Code is
19 : * The Mozilla Foundation.
20 : * Portions created by the Initial Developer are Copyright (C) 2010
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : * Ben Turner <bent.mozilla@gmail.com>
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either the GNU General Public License Version 2 or later (the "GPL"), or
28 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include "IndexedDatabaseManager.h"
41 : #include "DatabaseInfo.h"
42 :
43 : #include "nsIDOMScriptObjectFactory.h"
44 : #include "nsIFile.h"
45 : #include "nsIObserverService.h"
46 : #include "nsIScriptObjectPrincipal.h"
47 : #include "nsIScriptSecurityManager.h"
48 : #include "nsISHEntry.h"
49 : #include "nsISimpleEnumerator.h"
50 : #include "nsITimer.h"
51 :
52 : #include "mozilla/LazyIdleThread.h"
53 : #include "mozilla/Preferences.h"
54 : #include "mozilla/Services.h"
55 : #include "mozilla/storage.h"
56 : #include "nsContentUtils.h"
57 : #include "nsThreadUtils.h"
58 : #include "nsXPCOM.h"
59 : #include "nsXPCOMPrivate.h"
60 : #include "test_quota.h"
61 : #include "xpcpublic.h"
62 :
63 : #include "AsyncConnectionHelper.h"
64 : #include "CheckQuotaHelper.h"
65 : #include "IDBDatabase.h"
66 : #include "IDBEvents.h"
67 : #include "IDBFactory.h"
68 : #include "IDBKeyRange.h"
69 : #include "OpenDatabaseHelper.h"
70 : #include "TransactionThreadPool.h"
71 :
72 : // The amount of time, in milliseconds, that our IO thread will stay alive
73 : // after the last event it processes.
74 : #define DEFAULT_THREAD_TIMEOUT_MS 30000
75 :
76 : // The amount of time, in milliseconds, that we will wait for active database
77 : // transactions on shutdown before aborting them.
78 : #define DEFAULT_SHUTDOWN_TIMER_MS 30000
79 :
80 : // Amount of space that IndexedDB databases may use by default in megabytes.
81 : #define DEFAULT_QUOTA_MB 50
82 :
83 : // Preference that users can set to override DEFAULT_QUOTA_MB
84 : #define PREF_INDEXEDDB_QUOTA "dom.indexedDB.warningQuota"
85 :
86 : USING_INDEXEDDB_NAMESPACE
87 : using namespace mozilla::services;
88 : using mozilla::Preferences;
89 :
90 : static NS_DEFINE_CID(kDOMSOF_CID, NS_DOM_SCRIPT_OBJECT_FACTORY_CID);
91 :
92 : namespace {
93 :
94 : PRInt32 gShutdown = 0;
95 : PRInt32 gClosed = 0;
96 :
97 : // Does not hold a reference.
98 : IndexedDatabaseManager* gInstance = nsnull;
99 :
100 : PRInt32 gIndexedDBQuotaMB = DEFAULT_QUOTA_MB;
101 :
102 : bool
103 0 : GetBaseFilename(const nsAString& aFilename,
104 : nsAString& aBaseFilename)
105 : {
106 0 : NS_ASSERTION(!aFilename.IsEmpty(), "Bad argument!");
107 :
108 0 : NS_NAMED_LITERAL_STRING(sqlite, ".sqlite");
109 0 : nsAString::size_type filenameLen = aFilename.Length();
110 0 : nsAString::size_type sqliteLen = sqlite.Length();
111 :
112 0 : if (sqliteLen > filenameLen ||
113 0 : Substring(aFilename, filenameLen - sqliteLen, sqliteLen) != sqlite) {
114 0 : return false;
115 : }
116 :
117 0 : aBaseFilename = Substring(aFilename, 0, filenameLen - sqliteLen);
118 :
119 0 : return true;
120 : }
121 :
122 : class QuotaCallback : public mozIStorageQuotaCallback
123 54 : {
124 : public:
125 : NS_DECL_ISUPPORTS
126 :
127 : NS_IMETHOD
128 0 : QuotaExceeded(const nsACString& aFilename,
129 : PRInt64 aCurrentSizeLimit,
130 : PRInt64 aCurrentTotalSize,
131 : nsISupports* aUserData,
132 : PRInt64* _retval)
133 : {
134 0 : if (IndexedDatabaseManager::QuotaIsLifted()) {
135 0 : *_retval = 0;
136 0 : return NS_OK;
137 : }
138 :
139 0 : return NS_ERROR_FAILURE;
140 : }
141 : };
142 :
143 574 : NS_IMPL_THREADSAFE_ISUPPORTS1(QuotaCallback, mozIStorageQuotaCallback)
144 :
145 : // Adds all databases in the hash to the given array.
146 : PLDHashOperator
147 0 : EnumerateToTArray(const nsACString& aKey,
148 : nsTArray<IDBDatabase*>* aValue,
149 : void* aUserArg)
150 : {
151 0 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
152 0 : NS_ASSERTION(!aKey.IsEmpty(), "Empty key!");
153 0 : NS_ASSERTION(aValue, "Null pointer!");
154 0 : NS_ASSERTION(aUserArg, "Null pointer!");
155 :
156 : nsTArray<IDBDatabase*>* array =
157 0 : static_cast<nsTArray<IDBDatabase*>*>(aUserArg);
158 :
159 0 : if (!array->AppendElements(*aValue)) {
160 0 : NS_WARNING("Out of memory!");
161 0 : return PL_DHASH_STOP;
162 : }
163 :
164 0 : return PL_DHASH_NEXT;
165 : }
166 :
167 : PLDHashOperator
168 50 : InvalidateAllFileManagers(const nsACString& aKey,
169 : nsTArray<nsRefPtr<FileManager> >* aValue,
170 : void* aUserArg)
171 : {
172 50 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
173 50 : NS_ASSERTION(!aKey.IsEmpty(), "Empty key!");
174 50 : NS_ASSERTION(aValue, "Null pointer!");
175 :
176 102 : for (PRUint32 i = 0; i < aValue->Length(); i++) {
177 104 : nsRefPtr<FileManager> fileManager = aValue->ElementAt(i);
178 52 : fileManager->Invalidate();
179 : }
180 :
181 50 : return PL_DHASH_NEXT;
182 : }
183 :
184 : } // anonymous namespace
185 :
186 54 : IndexedDatabaseManager::IndexedDatabaseManager()
187 : : mCurrentWindowIndex(BAD_TLS_INDEX),
188 : mQuotaHelperMutex("IndexedDatabaseManager.mQuotaHelperMutex"),
189 54 : mFileMutex("IndexedDatabaseManager.mFileMutex")
190 : {
191 54 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
192 54 : NS_ASSERTION(!gInstance, "More than one instance!");
193 54 : }
194 :
195 108 : IndexedDatabaseManager::~IndexedDatabaseManager()
196 : {
197 54 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
198 54 : NS_ASSERTION(gInstance == this, "Different instances!");
199 54 : gInstance = nsnull;
200 54 : }
201 :
202 : // static
203 : already_AddRefed<IndexedDatabaseManager>
204 130 : IndexedDatabaseManager::GetOrCreate()
205 : {
206 130 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
207 :
208 130 : if (IsShuttingDown()) {
209 0 : NS_ERROR("Calling GetOrCreateInstance() after shutdown!");
210 0 : return nsnull;
211 : }
212 :
213 260 : nsRefPtr<IndexedDatabaseManager> instance(gInstance);
214 :
215 130 : if (!instance) {
216 54 : if (NS_FAILED(Preferences::AddIntVarCache(&gIndexedDBQuotaMB,
217 : PREF_INDEXEDDB_QUOTA,
218 : DEFAULT_QUOTA_MB))) {
219 0 : NS_WARNING("Unable to respond to quota pref changes!");
220 0 : gIndexedDBQuotaMB = DEFAULT_QUOTA_MB;
221 : }
222 :
223 54 : instance = new IndexedDatabaseManager();
224 :
225 162 : if (!instance->mLiveDatabases.Init() ||
226 54 : !instance->mQuotaHelperHash.Init() ||
227 54 : !instance->mFileManagers.Init()) {
228 0 : NS_WARNING("Out of memory!");
229 0 : return nsnull;
230 : }
231 :
232 : // We need a thread-local to hold the current window.
233 54 : NS_ASSERTION(instance->mCurrentWindowIndex == BAD_TLS_INDEX, "Huh?");
234 :
235 54 : if (PR_NewThreadPrivateIndex(&instance->mCurrentWindowIndex, nsnull) !=
236 : PR_SUCCESS) {
237 0 : NS_ERROR("PR_NewThreadPrivateIndex failed, IndexedDB disabled");
238 0 : instance->mCurrentWindowIndex = BAD_TLS_INDEX;
239 0 : return nsnull;
240 : }
241 :
242 : // Make a timer here to avoid potential failures later. We don't actually
243 : // initialize the timer until shutdown.
244 54 : instance->mShutdownTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
245 54 : NS_ENSURE_TRUE(instance->mShutdownTimer, nsnull);
246 :
247 108 : nsCOMPtr<nsIObserverService> obs = GetObserverService();
248 54 : NS_ENSURE_TRUE(obs, nsnull);
249 :
250 : // We need this callback to know when to shut down all our threads.
251 54 : nsresult rv = obs->AddObserver(instance, NS_XPCOM_SHUTDOWN_OBSERVER_ID,
252 54 : false);
253 54 : NS_ENSURE_SUCCESS(rv, nsnull);
254 :
255 : // Make a lazy thread for any IO we need (like clearing or enumerating the
256 : // contents of indexedDB database directories).
257 54 : instance->mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS,
258 108 : LazyIdleThread::ManualShutdown);
259 :
260 : // We need one quota callback object to hand to SQLite.
261 54 : instance->mQuotaCallbackSingleton = new QuotaCallback();
262 :
263 : // The observer service will hold our last reference, don't AddRef here.
264 108 : gInstance = instance;
265 : }
266 :
267 130 : return instance.forget();
268 : }
269 :
270 : // static
271 : IndexedDatabaseManager*
272 11271 : IndexedDatabaseManager::Get()
273 : {
274 : // Does not return an owning reference.
275 11271 : return gInstance;
276 : }
277 :
278 : // static
279 : IndexedDatabaseManager*
280 54 : IndexedDatabaseManager::FactoryCreate()
281 : {
282 : // Returns a raw pointer that carries an owning reference! Lame, but the
283 : // singleton factory macros force this.
284 54 : return GetOrCreate().get();
285 : }
286 :
287 : bool
288 75 : IndexedDatabaseManager::RegisterDatabase(IDBDatabase* aDatabase)
289 : {
290 75 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
291 75 : NS_ASSERTION(aDatabase, "Null pointer!");
292 :
293 : // Don't allow any new databases to be created after shutdown.
294 75 : if (IsShuttingDown()) {
295 0 : return false;
296 : }
297 :
298 : // Add this database to its origin array if it exists, create it otherwise.
299 : nsTArray<IDBDatabase*>* array;
300 75 : if (!mLiveDatabases.Get(aDatabase->Origin(), &array)) {
301 100 : nsAutoPtr<nsTArray<IDBDatabase*> > newArray(new nsTArray<IDBDatabase*>());
302 50 : if (!mLiveDatabases.Put(aDatabase->Origin(), newArray)) {
303 0 : NS_WARNING("Out of memory?");
304 0 : return false;
305 : }
306 100 : array = newArray.forget();
307 : }
308 75 : if (!array->AppendElement(aDatabase)) {
309 0 : NS_WARNING("Out of memory?");
310 0 : return false;
311 : }
312 :
313 75 : aDatabase->mRegistered = true;
314 75 : return true;
315 : }
316 :
317 : void
318 12 : IndexedDatabaseManager::UnregisterDatabase(IDBDatabase* aDatabase)
319 : {
320 12 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
321 12 : NS_ASSERTION(aDatabase, "Null pointer!");
322 :
323 : // Remove this database from its origin array, maybe remove the array if it
324 : // is then empty.
325 : nsTArray<IDBDatabase*>* array;
326 24 : if (mLiveDatabases.Get(aDatabase->Origin(), &array) &&
327 12 : array->RemoveElement(aDatabase)) {
328 12 : if (array->IsEmpty()) {
329 1 : mLiveDatabases.Remove(aDatabase->Origin());
330 : }
331 12 : return;
332 : }
333 0 : NS_ERROR("Didn't know anything about this database!");
334 : }
335 :
336 : void
337 0 : IndexedDatabaseManager::OnUsageCheckComplete(AsyncUsageRunnable* aRunnable)
338 : {
339 0 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
340 0 : NS_ASSERTION(aRunnable, "Null pointer!");
341 0 : NS_ASSERTION(!aRunnable->mURI, "Should have been cleared!");
342 0 : NS_ASSERTION(!aRunnable->mCallback, "Should have been cleared!");
343 :
344 0 : if (!mUsageRunnables.RemoveElement(aRunnable)) {
345 0 : NS_ERROR("Don't know anything about this runnable!");
346 : }
347 0 : }
348 :
349 : nsresult
350 84 : IndexedDatabaseManager::WaitForOpenAllowed(const nsACString& aOrigin,
351 : nsIAtom* aId,
352 : nsIRunnable* aRunnable)
353 : {
354 84 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
355 84 : NS_ASSERTION(!aOrigin.IsEmpty(), "Empty origin!");
356 84 : NS_ASSERTION(aRunnable, "Null pointer!");
357 :
358 168 : nsAutoPtr<SynchronizedOp> op(new SynchronizedOp(aOrigin, aId));
359 :
360 : // See if this runnable needs to wait.
361 84 : bool delayed = false;
362 90 : for (PRUint32 index = mSynchronizedOps.Length(); index > 0; index--) {
363 29 : nsAutoPtr<SynchronizedOp>& existingOp = mSynchronizedOps[index - 1];
364 29 : if (op->MustWaitFor(*existingOp)) {
365 23 : existingOp->DelayRunnable(aRunnable);
366 23 : delayed = true;
367 23 : break;
368 : }
369 : }
370 :
371 : // Otherwise, dispatch it immediately.
372 84 : if (!delayed) {
373 61 : nsresult rv = NS_DispatchToCurrentThread(aRunnable);
374 61 : NS_ENSURE_SUCCESS(rv, rv);
375 : }
376 :
377 : // Adding this to the synchronized ops list will block any additional
378 : // ops from proceeding until this one is done.
379 84 : mSynchronizedOps.AppendElement(op.forget());
380 :
381 84 : return NS_OK;
382 : }
383 :
384 : void
385 84 : IndexedDatabaseManager::AllowNextSynchronizedOp(const nsACString& aOrigin,
386 : nsIAtom* aId)
387 : {
388 84 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
389 84 : NS_ASSERTION(!aOrigin.IsEmpty(), "Empty origin!");
390 :
391 84 : PRUint32 count = mSynchronizedOps.Length();
392 84 : for (PRUint32 index = 0; index < count; index++) {
393 84 : nsAutoPtr<SynchronizedOp>& op = mSynchronizedOps[index];
394 84 : if (op->mOrigin.Equals(aOrigin)) {
395 84 : if (op->mId == aId) {
396 84 : NS_ASSERTION(op->mDatabases.IsEmpty(), "How did this happen?");
397 :
398 84 : op->DispatchDelayedRunnables();
399 :
400 84 : mSynchronizedOps.RemoveElementAt(index);
401 84 : return;
402 : }
403 :
404 : // If one or the other is for an origin clear, we should have matched
405 : // solely on origin.
406 0 : NS_ASSERTION(op->mId && aId, "Why didn't we match earlier?");
407 : }
408 : }
409 :
410 0 : NS_NOTREACHED("Why didn't we find a SynchronizedOp?");
411 : }
412 :
413 : nsresult
414 71 : IndexedDatabaseManager::AcquireExclusiveAccess(const nsACString& aOrigin,
415 : IDBDatabase* aDatabase,
416 : AsyncConnectionHelper* aHelper,
417 : WaitingOnDatabasesCallback aCallback,
418 : void* aClosure)
419 : {
420 71 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
421 71 : NS_ASSERTION(aHelper, "Why are you talking to me?");
422 :
423 : // Find the right SynchronizedOp.
424 71 : SynchronizedOp* op = nsnull;
425 71 : PRUint32 count = mSynchronizedOps.Length();
426 71 : for (PRUint32 index = 0; index < count; index++) {
427 71 : SynchronizedOp* currentop = mSynchronizedOps[index].get();
428 71 : if (currentop->mOrigin.Equals(aOrigin)) {
429 142 : if (!currentop->mId ||
430 71 : (aDatabase && currentop->mId == aDatabase->Id())) {
431 : // We've found the right one.
432 71 : NS_ASSERTION(!currentop->mHelper,
433 : "SynchronizedOp already has a helper?!?");
434 71 : op = currentop;
435 71 : break;
436 : }
437 : }
438 : }
439 :
440 71 : NS_ASSERTION(op, "We didn't find a SynchronizedOp?");
441 :
442 : nsTArray<IDBDatabase*>* array;
443 71 : mLiveDatabases.Get(aOrigin, &array);
444 :
445 : // We need to wait for the databases to go away.
446 : // Hold on to all database objects that represent the same database file
447 : // (except the one that is requesting this version change).
448 142 : nsTArray<nsRefPtr<IDBDatabase> > liveDatabases;
449 :
450 71 : if (array) {
451 71 : PRUint32 count = array->Length();
452 178 : for (PRUint32 index = 0; index < count; index++) {
453 107 : IDBDatabase*& database = array->ElementAt(index);
454 114 : if (!database->IsClosed() &&
455 : (!aDatabase ||
456 : (aDatabase &&
457 : database != aDatabase &&
458 7 : database->Id() == aDatabase->Id()))) {
459 4 : liveDatabases.AppendElement(database);
460 : }
461 : }
462 : }
463 :
464 71 : if (liveDatabases.IsEmpty()) {
465 68 : IndexedDatabaseManager::DispatchHelper(aHelper);
466 68 : return NS_OK;
467 : }
468 :
469 3 : NS_ASSERTION(op->mDatabases.IsEmpty(), "How do we already have databases here?");
470 3 : op->mDatabases.AppendElements(liveDatabases);
471 3 : op->mHelper = aHelper;
472 :
473 : // Give our callback the databases so it can decide what to do with them.
474 3 : aCallback(liveDatabases, aClosure);
475 :
476 3 : NS_ASSERTION(liveDatabases.IsEmpty(),
477 : "Should have done something with the array!");
478 3 : return NS_OK;
479 : }
480 :
481 : // static
482 : bool
483 752 : IndexedDatabaseManager::IsShuttingDown()
484 : {
485 752 : return !!gShutdown;
486 : }
487 :
488 : // static
489 : bool
490 52 : IndexedDatabaseManager::IsClosed()
491 : {
492 52 : return !!gClosed;
493 : }
494 :
495 : void
496 0 : IndexedDatabaseManager::AbortCloseDatabasesForWindow(nsPIDOMWindow* aWindow)
497 : {
498 0 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
499 0 : NS_ASSERTION(aWindow, "Null pointer!");
500 :
501 0 : nsAutoTArray<IDBDatabase*, 50> liveDatabases;
502 0 : mLiveDatabases.EnumerateRead(EnumerateToTArray, &liveDatabases);
503 :
504 0 : TransactionThreadPool* pool = TransactionThreadPool::Get();
505 :
506 0 : for (PRUint32 index = 0; index < liveDatabases.Length(); index++) {
507 0 : IDBDatabase*& database = liveDatabases[index];
508 0 : if (database->GetOwner() == aWindow) {
509 0 : if (NS_FAILED(database->Close())) {
510 0 : NS_WARNING("Failed to close database for dying window!");
511 : }
512 :
513 0 : if (pool) {
514 0 : pool->AbortTransactionsForDatabase(database);
515 : }
516 : }
517 : }
518 0 : }
519 :
520 : bool
521 0 : IndexedDatabaseManager::HasOpenTransactions(nsPIDOMWindow* aWindow)
522 : {
523 0 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
524 0 : NS_ASSERTION(aWindow, "Null pointer!");
525 :
526 0 : nsAutoTArray<IDBDatabase*, 50> liveDatabases;
527 0 : mLiveDatabases.EnumerateRead(EnumerateToTArray, &liveDatabases);
528 :
529 0 : TransactionThreadPool* pool = TransactionThreadPool::Get();
530 0 : if (!pool) {
531 0 : return false;
532 : }
533 :
534 0 : for (PRUint32 index = 0; index < liveDatabases.Length(); index++) {
535 0 : IDBDatabase*& database = liveDatabases[index];
536 0 : if (database->GetOwner() == aWindow &&
537 0 : pool->HasTransactionsForDatabase(database)) {
538 0 : return true;
539 : }
540 : }
541 :
542 0 : return false;
543 : }
544 :
545 : void
546 31 : IndexedDatabaseManager::OnDatabaseClosed(IDBDatabase* aDatabase)
547 : {
548 31 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
549 31 : NS_ASSERTION(aDatabase, "Null pointer!");
550 :
551 : // Check through the list of SynchronizedOps to see if any are waiting for
552 : // this database to close before proceeding.
553 31 : PRUint32 count = mSynchronizedOps.Length();
554 61 : for (PRUint32 index = 0; index < count; index++) {
555 34 : nsAutoPtr<SynchronizedOp>& op = mSynchronizedOps[index];
556 :
557 68 : if (op->mOrigin == aDatabase->Origin() &&
558 34 : (op->mId == aDatabase->Id() || !op->mId)) {
559 : // This database is in the scope of this SynchronizedOp. Remove it
560 : // from the list if necessary.
561 34 : if (op->mDatabases.RemoveElement(aDatabase)) {
562 : // Now set up the helper if there are no more live databases.
563 4 : NS_ASSERTION(op->mHelper, "How did we get rid of the helper before "
564 : "removing the last database?");
565 4 : if (op->mDatabases.IsEmpty()) {
566 : // At this point, all databases are closed, so no new transactions
567 : // can be started. There may, however, still be outstanding
568 : // transactions that have not completed. We need to wait for those
569 : // before we dispatch the helper.
570 :
571 3 : TransactionThreadPool* pool = TransactionThreadPool::GetOrCreate();
572 3 : if (!pool) {
573 0 : NS_ERROR("IndexedDB is totally broken.");
574 0 : return;
575 : }
576 :
577 : nsRefPtr<WaitForTransactionsToFinishRunnable> waitRunnable =
578 9 : new WaitForTransactionsToFinishRunnable(op);
579 :
580 6 : nsAutoTArray<nsRefPtr<IDBDatabase>, 1> array;
581 3 : array.AppendElement(aDatabase);
582 :
583 : // Use the WaitForTransactionsToFinishRunnable as the callback.
584 3 : if (!pool->WaitForAllDatabasesToComplete(array, waitRunnable)) {
585 0 : NS_WARNING("Failed to wait for transaction to complete!");
586 : }
587 : }
588 4 : break;
589 : }
590 : }
591 : }
592 : }
593 :
594 : void
595 10550 : IndexedDatabaseManager::SetCurrentWindowInternal(nsPIDOMWindow* aWindow)
596 : {
597 10550 : if (aWindow) {
598 : #ifdef DEBUG
599 0 : NS_ASSERTION(!PR_GetThreadPrivate(mCurrentWindowIndex),
600 : "Somebody forgot to clear the current window!");
601 : #endif
602 0 : PR_SetThreadPrivate(mCurrentWindowIndex, aWindow);
603 : }
604 : else {
605 : // We cannot assert PR_GetThreadPrivate(mCurrentWindowIndex) here
606 : // because we cannot distinguish between the thread private became
607 : // null and that it was set to null on the first place,
608 : // because we didn't have a window.
609 10550 : PR_SetThreadPrivate(mCurrentWindowIndex, nsnull);
610 : }
611 10550 : }
612 :
613 : // static
614 : PRUint32
615 50 : IndexedDatabaseManager::GetIndexedDBQuotaMB()
616 : {
617 50 : return PRUint32(NS_MAX(gIndexedDBQuotaMB, 0));
618 : }
619 :
620 : nsresult
621 76 : IndexedDatabaseManager::EnsureOriginIsInitialized(const nsACString& aOrigin,
622 : nsIFile** aDirectory)
623 : {
624 : #ifdef DEBUG
625 : {
626 : bool correctThread;
627 76 : NS_ASSERTION(NS_SUCCEEDED(mIOThread->IsOnCurrentThread(&correctThread)) &&
628 : correctThread,
629 : "Running on the wrong thread!");
630 : }
631 : #endif
632 :
633 152 : nsCOMPtr<nsIFile> directory;
634 : nsresult rv = IDBFactory::GetDirectoryForOrigin(aOrigin,
635 76 : getter_AddRefs(directory));
636 76 : NS_ENSURE_SUCCESS(rv, rv);
637 :
638 : bool exists;
639 76 : rv = directory->Exists(&exists);
640 76 : NS_ENSURE_SUCCESS(rv, rv);
641 :
642 76 : if (exists) {
643 : bool isDirectory;
644 26 : rv = directory->IsDirectory(&isDirectory);
645 26 : NS_ENSURE_SUCCESS(rv, rv);
646 26 : NS_ENSURE_TRUE(isDirectory, NS_ERROR_UNEXPECTED);
647 : }
648 : else {
649 50 : rv = directory->Create(nsIFile::DIRECTORY_TYPE, 0755);
650 50 : NS_ENSURE_SUCCESS(rv, rv);
651 : }
652 :
653 76 : if (mFileManagers.Get(aOrigin)) {
654 26 : NS_ADDREF(*aDirectory = directory);
655 26 : return NS_OK;
656 : }
657 :
658 : // First figure out the filename pattern we'll use.
659 100 : nsCOMPtr<nsIFile> patternFile;
660 50 : rv = directory->Clone(getter_AddRefs(patternFile));
661 50 : NS_ENSURE_SUCCESS(rv, rv);
662 :
663 50 : rv = patternFile->Append(NS_LITERAL_STRING("*"));
664 50 : NS_ENSURE_SUCCESS(rv, rv);
665 :
666 100 : nsCString pattern;
667 50 : rv = patternFile->GetNativePath(pattern);
668 50 : NS_ENSURE_SUCCESS(rv, rv);
669 :
670 : // Now tell SQLite to start tracking this pattern.
671 : nsCOMPtr<mozIStorageServiceQuotaManagement> ss =
672 100 : do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
673 50 : NS_ENSURE_TRUE(ss, NS_ERROR_FAILURE);
674 :
675 50 : rv = ss->SetQuotaForFilenamePattern(pattern,
676 50 : GetIndexedDBQuotaMB() * 1024 * 1024,
677 100 : mQuotaCallbackSingleton, nsnull);
678 50 : NS_ENSURE_SUCCESS(rv, rv);
679 :
680 : // We need to see if there are any files in the directory already. If they
681 : // are database files then we need to create file managers for them and also
682 : // tell SQLite about all of them.
683 :
684 100 : nsAutoTArray<nsString, 20> subdirsToProcess;
685 100 : nsAutoTArray<nsCOMPtr<nsIFile> , 20> unknownFiles;
686 :
687 : nsAutoPtr<nsTArray<nsRefPtr<FileManager> > > fileManagers(
688 100 : new nsTArray<nsRefPtr<FileManager> >());
689 :
690 100 : nsTHashtable<nsStringHashKey> validSubdirs;
691 50 : NS_ENSURE_TRUE(validSubdirs.Init(20), NS_ERROR_OUT_OF_MEMORY);
692 :
693 100 : nsCOMPtr<nsISimpleEnumerator> entries;
694 50 : rv = directory->GetDirectoryEntries(getter_AddRefs(entries));
695 50 : NS_ENSURE_SUCCESS(rv, rv);
696 :
697 : bool hasMore;
698 100 : while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) {
699 0 : nsCOMPtr<nsISupports> entry;
700 0 : rv = entries->GetNext(getter_AddRefs(entry));
701 0 : NS_ENSURE_SUCCESS(rv, rv);
702 :
703 0 : nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
704 0 : NS_ENSURE_TRUE(file, NS_NOINTERFACE);
705 :
706 0 : nsString leafName;
707 0 : rv = file->GetLeafName(leafName);
708 0 : NS_ENSURE_SUCCESS(rv, rv);
709 :
710 : bool isDirectory;
711 0 : rv = file->IsDirectory(&isDirectory);
712 0 : NS_ENSURE_SUCCESS(rv, rv);
713 :
714 0 : if (isDirectory) {
715 0 : if (!validSubdirs.GetEntry(leafName)) {
716 0 : subdirsToProcess.AppendElement(leafName);
717 : }
718 0 : continue;
719 : }
720 :
721 0 : nsString dbBaseFilename;
722 0 : if (!GetBaseFilename(leafName, dbBaseFilename)) {
723 0 : unknownFiles.AppendElement(file);
724 0 : continue;
725 : }
726 :
727 0 : nsCOMPtr<nsIFile> fileManagerDirectory;
728 0 : rv = directory->Clone(getter_AddRefs(fileManagerDirectory));
729 0 : NS_ENSURE_SUCCESS(rv, rv);
730 :
731 0 : rv = fileManagerDirectory->Append(dbBaseFilename);
732 0 : NS_ENSURE_SUCCESS(rv, rv);
733 :
734 0 : nsCOMPtr<mozIStorageConnection> connection;
735 : rv = OpenDatabaseHelper::CreateDatabaseConnection(
736 0 : NullString(), file, fileManagerDirectory, getter_AddRefs(connection));
737 0 : NS_ENSURE_SUCCESS(rv, rv);
738 :
739 0 : nsCOMPtr<mozIStorageStatement> stmt;
740 0 : rv = connection->CreateStatement(NS_LITERAL_CSTRING(
741 : "SELECT name "
742 : "FROM database"
743 0 : ), getter_AddRefs(stmt));
744 0 : NS_ENSURE_SUCCESS(rv, rv);
745 :
746 : bool hasResult;
747 0 : rv = stmt->ExecuteStep(&hasResult);
748 0 : NS_ENSURE_SUCCESS(rv, rv);
749 :
750 0 : if (!hasResult) {
751 0 : NS_ERROR("Database has no name!");
752 0 : return NS_ERROR_UNEXPECTED;
753 : }
754 :
755 0 : nsString databaseName;
756 0 : rv = stmt->GetString(0, databaseName);
757 0 : NS_ENSURE_SUCCESS(rv, rv);
758 :
759 0 : nsRefPtr<FileManager> fileManager = new FileManager(aOrigin, databaseName);
760 :
761 0 : rv = fileManager->Init(fileManagerDirectory, connection);
762 0 : NS_ENSURE_SUCCESS(rv, rv);
763 :
764 0 : fileManagers->AppendElement(fileManager);
765 :
766 0 : rv = ss->UpdateQuotaInformationForFile(file);
767 0 : NS_ENSURE_SUCCESS(rv, rv);
768 :
769 0 : if (!validSubdirs.PutEntry(dbBaseFilename)) {
770 0 : NS_WARNING("Out of memory?");
771 0 : return NS_ERROR_OUT_OF_MEMORY;
772 : }
773 : }
774 50 : NS_ENSURE_SUCCESS(rv, rv);
775 :
776 50 : for (PRUint32 i = 0; i < subdirsToProcess.Length(); i++) {
777 0 : const nsString& subdir = subdirsToProcess[i];
778 0 : if (!validSubdirs.GetEntry(subdir)) {
779 0 : NS_WARNING("Unknown subdirectory found!");
780 0 : return NS_ERROR_UNEXPECTED;
781 : }
782 : }
783 :
784 50 : for (PRUint32 i = 0; i < unknownFiles.Length(); i++) {
785 0 : nsCOMPtr<nsIFile>& unknownFile = unknownFiles[i];
786 :
787 : // Some temporary SQLite files could disappear, so we have to check if the
788 : // unknown file still exists.
789 : bool exists;
790 0 : rv = unknownFile->Exists(&exists);
791 0 : NS_ENSURE_SUCCESS(rv, rv);
792 :
793 0 : if (exists) {
794 0 : nsString leafName;
795 0 : unknownFile->GetLeafName(leafName);
796 :
797 : // The journal file may exists even after db has been correctly opened.
798 0 : if (!StringEndsWith(leafName, NS_LITERAL_STRING(".sqlite-journal"))) {
799 0 : NS_WARNING("Unknown file found!");
800 0 : return NS_ERROR_UNEXPECTED;
801 : }
802 : }
803 : }
804 :
805 50 : if (!mFileManagers.Put(aOrigin, fileManagers)) {
806 0 : NS_WARNING("Out of memory?");
807 0 : return NS_ERROR_OUT_OF_MEMORY;
808 : }
809 :
810 50 : fileManagers.forget();
811 :
812 50 : NS_ADDREF(*aDirectory = directory);
813 50 : return NS_OK;
814 : }
815 :
816 : bool
817 0 : IndexedDatabaseManager::QuotaIsLiftedInternal()
818 : {
819 0 : nsPIDOMWindow* window = nsnull;
820 0 : nsRefPtr<CheckQuotaHelper> helper = nsnull;
821 0 : bool createdHelper = false;
822 :
823 : window =
824 0 : static_cast<nsPIDOMWindow*>(PR_GetThreadPrivate(mCurrentWindowIndex));
825 :
826 : // Once IDB is supported outside of Windows this should become an early
827 : // return true.
828 0 : NS_ASSERTION(window, "Why don't we have a Window here?");
829 :
830 : // Hold the lock from here on.
831 0 : MutexAutoLock autoLock(mQuotaHelperMutex);
832 :
833 0 : mQuotaHelperHash.Get(window, getter_AddRefs(helper));
834 :
835 0 : if (!helper) {
836 0 : helper = new CheckQuotaHelper(window, mQuotaHelperMutex);
837 0 : createdHelper = true;
838 :
839 0 : bool result = mQuotaHelperHash.Put(window, helper);
840 0 : NS_ENSURE_TRUE(result, result);
841 :
842 : // Unlock while calling out to XPCOM
843 : {
844 0 : MutexAutoUnlock autoUnlock(mQuotaHelperMutex);
845 :
846 0 : nsresult rv = NS_DispatchToMainThread(helper);
847 0 : NS_ENSURE_SUCCESS(rv, false);
848 : }
849 :
850 : // Relocked. If any other threads hit the quota limit on the same Window,
851 : // they are using the helper we created here and are now blocking in
852 : // PromptAndReturnQuotaDisabled.
853 : }
854 :
855 0 : bool result = helper->PromptAndReturnQuotaIsDisabled();
856 :
857 : // If this thread created the helper and added it to the hash, this thread
858 : // must remove it.
859 0 : if (createdHelper) {
860 0 : mQuotaHelperHash.Remove(window);
861 : }
862 :
863 0 : return result;
864 : }
865 :
866 : void
867 0 : IndexedDatabaseManager::CancelPromptsForWindowInternal(nsPIDOMWindow* aWindow)
868 : {
869 0 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
870 :
871 0 : nsRefPtr<CheckQuotaHelper> helper;
872 :
873 0 : MutexAutoLock autoLock(mQuotaHelperMutex);
874 :
875 0 : mQuotaHelperHash.Get(aWindow, getter_AddRefs(helper));
876 :
877 0 : if (helper) {
878 0 : helper->Cancel();
879 : }
880 0 : }
881 :
882 : // static
883 : nsresult
884 76 : IndexedDatabaseManager::GetASCIIOriginFromWindow(nsPIDOMWindow* aWindow,
885 : nsCString& aASCIIOrigin)
886 : {
887 76 : NS_ASSERTION(NS_IsMainThread(),
888 : "We're about to touch a window off the main thread!");
889 :
890 76 : if (!aWindow) {
891 76 : aASCIIOrigin.AssignLiteral("chrome");
892 76 : NS_ASSERTION(nsContentUtils::IsCallerChrome(),
893 : "Null window but not chrome!");
894 76 : return NS_OK;
895 : }
896 :
897 0 : nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aWindow);
898 0 : NS_ENSURE_TRUE(sop, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
899 :
900 0 : nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
901 0 : NS_ENSURE_TRUE(principal, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
902 :
903 0 : if (nsContentUtils::IsSystemPrincipal(principal)) {
904 0 : aASCIIOrigin.AssignLiteral("chrome");
905 : }
906 : else {
907 0 : nsresult rv = nsContentUtils::GetASCIIOrigin(principal, aASCIIOrigin);
908 0 : NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
909 :
910 0 : if (aASCIIOrigin.EqualsLiteral("null")) {
911 0 : NS_WARNING("IndexedDB databases not allowed for this principal!");
912 0 : return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
913 : }
914 : }
915 :
916 0 : return NS_OK;
917 : }
918 :
919 : already_AddRefed<FileManager>
920 75 : IndexedDatabaseManager::GetOrCreateFileManager(const nsACString& aOrigin,
921 : const nsAString& aDatabaseName)
922 : {
923 : nsTArray<nsRefPtr<FileManager> >* array;
924 75 : if (!mFileManagers.Get(aOrigin, &array)) {
925 : nsAutoPtr<nsTArray<nsRefPtr<FileManager> > > newArray(
926 0 : new nsTArray<nsRefPtr<FileManager> >());
927 0 : if (!mFileManagers.Put(aOrigin, newArray)) {
928 0 : NS_WARNING("Out of memory?");
929 0 : return nsnull;
930 : }
931 0 : array = newArray.forget();
932 : }
933 :
934 150 : nsRefPtr<FileManager> fileManager;
935 78 : for (PRUint32 i = 0; i < array->Length(); i++) {
936 52 : nsRefPtr<FileManager> fm = array->ElementAt(i);
937 :
938 26 : if (fm->DatabaseName().Equals(aDatabaseName)) {
939 23 : fileManager = fm.forget();
940 : break;
941 : }
942 : }
943 :
944 75 : if (!fileManager) {
945 52 : fileManager = new FileManager(aOrigin, aDatabaseName);
946 :
947 52 : array->AppendElement(fileManager);
948 : }
949 :
950 75 : return fileManager.forget();
951 : }
952 :
953 : already_AddRefed<FileManager>
954 0 : IndexedDatabaseManager::GetFileManager(const nsACString& aOrigin,
955 : const nsAString& aDatabaseName)
956 : {
957 : nsTArray<nsRefPtr<FileManager> >* array;
958 0 : if (!mFileManagers.Get(aOrigin, &array)) {
959 0 : return nsnull;
960 : }
961 :
962 0 : for (PRUint32 i = 0; i < array->Length(); i++) {
963 0 : nsRefPtr<FileManager>& fileManager = array->ElementAt(i);
964 :
965 0 : if (fileManager->DatabaseName().Equals(aDatabaseName)) {
966 0 : nsRefPtr<FileManager> result = fileManager;
967 0 : return result.forget();
968 : }
969 : }
970 :
971 0 : return nsnull;
972 : }
973 :
974 : void
975 8 : IndexedDatabaseManager::InvalidateFileManagersForOrigin(
976 : const nsACString& aOrigin)
977 : {
978 : nsTArray<nsRefPtr<FileManager> >* array;
979 8 : if (mFileManagers.Get(aOrigin, &array)) {
980 0 : for (PRUint32 i = 0; i < array->Length(); i++) {
981 0 : nsRefPtr<FileManager> fileManager = array->ElementAt(i);
982 0 : fileManager->Invalidate();
983 : }
984 0 : mFileManagers.Remove(aOrigin);
985 : }
986 8 : }
987 :
988 : void
989 0 : IndexedDatabaseManager::InvalidateFileManager(const nsACString& aOrigin,
990 : const nsAString& aDatabaseName)
991 : {
992 : nsTArray<nsRefPtr<FileManager> >* array;
993 0 : if (!mFileManagers.Get(aOrigin, &array)) {
994 0 : return;
995 : }
996 :
997 0 : for (PRUint32 i = 0; i < array->Length(); i++) {
998 0 : nsRefPtr<FileManager> fileManager = array->ElementAt(i);
999 0 : if (fileManager->DatabaseName().Equals(aDatabaseName)) {
1000 0 : fileManager->Invalidate();
1001 0 : array->RemoveElementAt(i);
1002 :
1003 0 : if (array->IsEmpty()) {
1004 0 : mFileManagers.Remove(aOrigin);
1005 : }
1006 :
1007 : break;
1008 : }
1009 : }
1010 : }
1011 :
1012 : nsresult
1013 0 : IndexedDatabaseManager::AsyncDeleteFile(FileManager* aFileManager,
1014 : PRInt64 aFileId)
1015 : {
1016 0 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
1017 :
1018 0 : NS_ENSURE_ARG_POINTER(aFileManager);
1019 :
1020 : // See if we're currently clearing the databases for this origin. If so then
1021 : // we pretend that we've already deleted everything.
1022 0 : if (IsClearOriginPending(aFileManager->Origin())) {
1023 0 : return NS_OK;
1024 : }
1025 :
1026 0 : nsCOMPtr<nsIFile> directory = aFileManager->GetDirectory();
1027 0 : NS_ENSURE_TRUE(directory, NS_ERROR_FAILURE);
1028 :
1029 0 : nsCOMPtr<nsIFile> file = aFileManager->GetFileForId(directory, aFileId);
1030 0 : NS_ENSURE_TRUE(file, NS_ERROR_FAILURE);
1031 :
1032 0 : nsString filePath;
1033 0 : nsresult rv = file->GetPath(filePath);
1034 0 : NS_ENSURE_SUCCESS(rv, rv);
1035 :
1036 : nsRefPtr<AsyncDeleteFileRunnable> runnable =
1037 0 : new AsyncDeleteFileRunnable(filePath);
1038 :
1039 0 : rv = mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
1040 0 : NS_ENSURE_SUCCESS(rv, rv);
1041 :
1042 0 : return NS_OK;
1043 : }
1044 :
1045 : // static
1046 : nsresult
1047 71 : IndexedDatabaseManager::DispatchHelper(AsyncConnectionHelper* aHelper)
1048 : {
1049 71 : nsresult rv = NS_OK;
1050 :
1051 : // If the helper has a transaction, dispatch it to the transaction
1052 : // threadpool.
1053 71 : if (aHelper->HasTransaction()) {
1054 71 : rv = aHelper->DispatchToTransactionPool();
1055 : }
1056 : else {
1057 : // Otherwise, dispatch it to the IO thread.
1058 0 : IndexedDatabaseManager* manager = IndexedDatabaseManager::Get();
1059 0 : NS_ASSERTION(manager, "We should definitely have a manager here");
1060 :
1061 0 : rv = aHelper->Dispatch(manager->IOThread());
1062 : }
1063 :
1064 71 : NS_ENSURE_SUCCESS(rv, rv);
1065 71 : return rv;
1066 : }
1067 :
1068 : bool
1069 96 : IndexedDatabaseManager::IsClearOriginPending(const nsACString& origin)
1070 : {
1071 : // Iterate through our SynchronizedOps to see if we have an entry that matches
1072 : // this origin and has no id.
1073 96 : PRUint32 count = mSynchronizedOps.Length();
1074 144 : for (PRUint32 index = 0; index < count; index++) {
1075 136 : nsAutoPtr<SynchronizedOp>& op = mSynchronizedOps[index];
1076 136 : if (op->mOrigin.Equals(origin) && !op->mId) {
1077 88 : return true;
1078 : }
1079 : }
1080 :
1081 8 : return false;
1082 : }
1083 :
1084 2168 : NS_IMPL_ISUPPORTS2(IndexedDatabaseManager, nsIIndexedDatabaseManager,
1085 : nsIObserver)
1086 :
1087 : NS_IMETHODIMP
1088 0 : IndexedDatabaseManager::GetUsageForURI(
1089 : nsIURI* aURI,
1090 : nsIIndexedDatabaseUsageCallback* aCallback)
1091 : {
1092 0 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
1093 :
1094 0 : NS_ENSURE_ARG_POINTER(aURI);
1095 0 : NS_ENSURE_ARG_POINTER(aCallback);
1096 :
1097 : // Figure out which origin we're dealing with.
1098 0 : nsCString origin;
1099 0 : nsresult rv = nsContentUtils::GetASCIIOrigin(aURI, origin);
1100 0 : NS_ENSURE_SUCCESS(rv, rv);
1101 :
1102 : nsRefPtr<AsyncUsageRunnable> runnable =
1103 0 : new AsyncUsageRunnable(aURI, origin, aCallback);
1104 :
1105 : nsRefPtr<AsyncUsageRunnable>* newRunnable =
1106 0 : mUsageRunnables.AppendElement(runnable);
1107 0 : NS_ENSURE_TRUE(newRunnable, NS_ERROR_OUT_OF_MEMORY);
1108 :
1109 : // Non-standard URIs can't create databases anyway so fire the callback
1110 : // immediately.
1111 0 : if (origin.EqualsLiteral("null")) {
1112 0 : rv = NS_DispatchToCurrentThread(runnable);
1113 0 : NS_ENSURE_SUCCESS(rv, rv);
1114 0 : return NS_OK;
1115 : }
1116 :
1117 : // See if we're currently clearing the databases for this origin. If so then
1118 : // we pretend that we've already deleted everything.
1119 0 : if (IsClearOriginPending(origin)) {
1120 0 : rv = NS_DispatchToCurrentThread(runnable);
1121 0 : NS_ENSURE_SUCCESS(rv, rv);
1122 0 : return NS_OK;
1123 : }
1124 :
1125 : // Otherwise dispatch to the IO thread to actually compute the usage.
1126 0 : rv = mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
1127 0 : NS_ENSURE_SUCCESS(rv, rv);
1128 :
1129 0 : return NS_OK;
1130 : }
1131 :
1132 : NS_IMETHODIMP
1133 0 : IndexedDatabaseManager::CancelGetUsageForURI(
1134 : nsIURI* aURI,
1135 : nsIIndexedDatabaseUsageCallback* aCallback)
1136 : {
1137 0 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
1138 :
1139 0 : NS_ENSURE_ARG_POINTER(aURI);
1140 0 : NS_ENSURE_ARG_POINTER(aCallback);
1141 :
1142 : // See if one of our pending callbacks matches both the URI and the callback
1143 : // given. Cancel an remove it if so.
1144 0 : for (PRUint32 index = 0; index < mUsageRunnables.Length(); index++) {
1145 0 : nsRefPtr<AsyncUsageRunnable>& runnable = mUsageRunnables[index];
1146 :
1147 : bool equals;
1148 0 : nsresult rv = runnable->mURI->Equals(aURI, &equals);
1149 0 : NS_ENSURE_SUCCESS(rv, rv);
1150 :
1151 0 : if (equals && SameCOMIdentity(aCallback, runnable->mCallback)) {
1152 0 : runnable->Cancel();
1153 0 : break;
1154 : }
1155 : }
1156 0 : return NS_OK;
1157 : }
1158 :
1159 : NS_IMETHODIMP
1160 96 : IndexedDatabaseManager::ClearDatabasesForURI(nsIURI* aURI)
1161 : {
1162 96 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
1163 :
1164 96 : NS_ENSURE_ARG_POINTER(aURI);
1165 :
1166 : // Figure out which origin we're dealing with.
1167 192 : nsCString origin;
1168 96 : nsresult rv = nsContentUtils::GetASCIIOrigin(aURI, origin);
1169 96 : NS_ENSURE_SUCCESS(rv, rv);
1170 :
1171 : // Non-standard URIs can't create databases anyway, so return immediately.
1172 96 : if (origin.EqualsLiteral("null")) {
1173 0 : return NS_OK;
1174 : }
1175 :
1176 : // If there is a pending or running clear operation for this origin, return
1177 : // immediately.
1178 96 : if (IsClearOriginPending(origin)) {
1179 88 : return NS_OK;
1180 : }
1181 :
1182 : // Queue up the origin clear runnable.
1183 : nsRefPtr<OriginClearRunnable> runnable =
1184 24 : new OriginClearRunnable(origin, mIOThread);
1185 :
1186 8 : rv = WaitForOpenAllowed(origin, nsnull, runnable);
1187 8 : NS_ENSURE_SUCCESS(rv, rv);
1188 :
1189 : // Give the runnable some help by invalidating any databases in the way.
1190 : // We need to grab references to any live databases here to prevent them from
1191 : // dying while we invalidate them.
1192 16 : nsTArray<nsRefPtr<IDBDatabase> > liveDatabases;
1193 :
1194 : // Grab all live databases for this origin.
1195 : nsTArray<IDBDatabase*>* array;
1196 8 : if (mLiveDatabases.Get(origin, &array)) {
1197 0 : liveDatabases.AppendElements(*array);
1198 : }
1199 :
1200 : // Invalidate all the live databases first.
1201 8 : for (PRUint32 index = 0; index < liveDatabases.Length(); index++) {
1202 0 : liveDatabases[index]->Invalidate();
1203 : }
1204 :
1205 8 : DatabaseInfo::RemoveAllForOrigin(origin);
1206 :
1207 : // After everything has been invalidated the helper should be dispatched to
1208 : // the end of the event queue.
1209 :
1210 8 : return NS_OK;
1211 : }
1212 :
1213 : NS_IMETHODIMP
1214 54 : IndexedDatabaseManager::Observe(nsISupports* aSubject,
1215 : const char* aTopic,
1216 : const PRUnichar* aData)
1217 : {
1218 54 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
1219 :
1220 54 : if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
1221 : // Setting this flag prevents the service from being recreated and prevents
1222 : // further databases from being created.
1223 54 : if (PR_ATOMIC_SET(&gShutdown, 1)) {
1224 0 : NS_ERROR("Shutdown more than once?!");
1225 : }
1226 :
1227 : // Make sure to join with our IO thread.
1228 54 : if (NS_FAILED(mIOThread->Shutdown())) {
1229 0 : NS_WARNING("Failed to shutdown IO thread!");
1230 : }
1231 :
1232 : // Kick off the shutdown timer.
1233 54 : if (NS_FAILED(mShutdownTimer->Init(this, DEFAULT_SHUTDOWN_TIMER_MS,
1234 : nsITimer::TYPE_ONE_SHOT))) {
1235 0 : NS_WARNING("Failed to initialize shutdown timer!");
1236 : }
1237 :
1238 : // This will spin the event loop while we wait on all the database threads
1239 : // to close. Our timer may fire during that loop.
1240 54 : TransactionThreadPool::Shutdown();
1241 :
1242 : // Cancel the timer regardless of whether it actually fired.
1243 54 : if (NS_FAILED(mShutdownTimer->Cancel())) {
1244 0 : NS_WARNING("Failed to cancel shutdown timer!");
1245 : }
1246 :
1247 54 : mFileManagers.EnumerateRead(InvalidateAllFileManagers, nsnull);
1248 :
1249 54 : if (PR_ATOMIC_SET(&gClosed, 1)) {
1250 0 : NS_ERROR("Close more than once?!");
1251 : }
1252 :
1253 54 : return NS_OK;
1254 : }
1255 :
1256 0 : if (!strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC)) {
1257 : NS_WARNING("Some database operations are taking longer than expected "
1258 0 : "during shutdown and will be aborted!");
1259 :
1260 : // Grab all live databases, for all origins.
1261 0 : nsAutoTArray<IDBDatabase*, 50> liveDatabases;
1262 0 : mLiveDatabases.EnumerateRead(EnumerateToTArray, &liveDatabases);
1263 :
1264 : // Invalidate them all.
1265 0 : if (!liveDatabases.IsEmpty()) {
1266 0 : PRUint32 count = liveDatabases.Length();
1267 0 : for (PRUint32 index = 0; index < count; index++) {
1268 0 : liveDatabases[index]->Invalidate();
1269 : }
1270 : }
1271 :
1272 0 : return NS_OK;
1273 : }
1274 :
1275 0 : NS_NOTREACHED("Unknown topic!");
1276 0 : return NS_ERROR_UNEXPECTED;
1277 : }
1278 :
1279 144 : NS_IMPL_THREADSAFE_ISUPPORTS1(IndexedDatabaseManager::OriginClearRunnable,
1280 : nsIRunnable)
1281 :
1282 : NS_IMETHODIMP
1283 24 : IndexedDatabaseManager::OriginClearRunnable::Run()
1284 : {
1285 24 : if (NS_IsMainThread()) {
1286 : // On the first time on the main thread we dispatch to the IO thread.
1287 16 : if (mFirstCallback) {
1288 8 : NS_ASSERTION(mThread, "Should have a thread here!");
1289 :
1290 8 : mFirstCallback = false;
1291 :
1292 16 : nsCOMPtr<nsIThread> thread;
1293 8 : mThread.swap(thread);
1294 :
1295 : // Dispatch to the IO thread.
1296 8 : if (NS_FAILED(thread->Dispatch(this, NS_DISPATCH_NORMAL))) {
1297 0 : NS_WARNING("Failed to dispatch to IO thread!");
1298 0 : return NS_ERROR_FAILURE;
1299 : }
1300 :
1301 8 : return NS_OK;
1302 : }
1303 :
1304 8 : NS_ASSERTION(!mThread, "Should have been cleared already!");
1305 :
1306 8 : IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
1307 8 : NS_ASSERTION(mgr, "This should never fail!");
1308 :
1309 8 : mgr->InvalidateFileManagersForOrigin(mOrigin);
1310 :
1311 : // Tell the IndexedDatabaseManager that we're done.
1312 8 : mgr->AllowNextSynchronizedOp(mOrigin, nsnull);
1313 :
1314 8 : return NS_OK;
1315 : }
1316 :
1317 8 : NS_ASSERTION(!mThread, "Should have been cleared already!");
1318 :
1319 : // Remove the directory that contains all our databases.
1320 16 : nsCOMPtr<nsIFile> directory;
1321 : nsresult rv = IDBFactory::GetDirectoryForOrigin(mOrigin,
1322 8 : getter_AddRefs(directory));
1323 8 : if (NS_SUCCEEDED(rv)) {
1324 : bool exists;
1325 8 : rv = directory->Exists(&exists);
1326 8 : if (NS_SUCCEEDED(rv) && exists) {
1327 0 : rv = directory->Remove(true);
1328 : }
1329 : }
1330 8 : NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to remove directory!");
1331 :
1332 : // Switch back to the main thread to complete the sequence.
1333 8 : rv = NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL);
1334 8 : NS_ENSURE_SUCCESS(rv, rv);
1335 :
1336 8 : return NS_OK;
1337 : }
1338 :
1339 0 : IndexedDatabaseManager::AsyncUsageRunnable::AsyncUsageRunnable(
1340 : nsIURI* aURI,
1341 : const nsACString& aOrigin,
1342 : nsIIndexedDatabaseUsageCallback* aCallback)
1343 : : mURI(aURI),
1344 : mOrigin(aOrigin),
1345 : mCallback(aCallback),
1346 : mUsage(0),
1347 : mFileUsage(0),
1348 0 : mCanceled(0)
1349 : {
1350 0 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
1351 0 : NS_ASSERTION(aURI, "Null pointer!");
1352 0 : NS_ASSERTION(!aOrigin.IsEmpty(), "Empty origin!");
1353 0 : NS_ASSERTION(aCallback, "Null pointer!");
1354 0 : }
1355 :
1356 : void
1357 0 : IndexedDatabaseManager::AsyncUsageRunnable::Cancel()
1358 : {
1359 0 : if (PR_ATOMIC_SET(&mCanceled, 1)) {
1360 0 : NS_ERROR("Canceled more than once?!");
1361 : }
1362 0 : }
1363 :
1364 : inline void
1365 0 : IncrementUsage(PRUint64* aUsage, PRUint64 aDelta)
1366 : {
1367 : // Watch for overflow!
1368 0 : if ((LL_MAXINT - *aUsage) <= aDelta) {
1369 0 : NS_WARNING("Database sizes exceed max we can report!");
1370 0 : *aUsage = LL_MAXINT;
1371 : }
1372 : else {
1373 0 : *aUsage += aDelta;
1374 : }
1375 0 : }
1376 :
1377 : nsresult
1378 0 : IndexedDatabaseManager::AsyncUsageRunnable::RunInternal()
1379 : {
1380 0 : if (NS_IsMainThread()) {
1381 : // Call the callback unless we were canceled.
1382 0 : if (!mCanceled) {
1383 0 : PRUint64 usage = mUsage;
1384 0 : IncrementUsage(&usage, mFileUsage);
1385 0 : mCallback->OnUsageResult(mURI, usage, mFileUsage);
1386 : }
1387 :
1388 : // Clean up.
1389 0 : mURI = nsnull;
1390 0 : mCallback = nsnull;
1391 :
1392 : // And tell the IndexedDatabaseManager that we're done.
1393 0 : IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
1394 0 : if (mgr) {
1395 0 : mgr->OnUsageCheckComplete(this);
1396 : }
1397 :
1398 0 : return NS_OK;
1399 : }
1400 :
1401 0 : if (mCanceled) {
1402 0 : return NS_OK;
1403 : }
1404 :
1405 : // Get the directory that contains all the database files we care about.
1406 0 : nsCOMPtr<nsIFile> directory;
1407 : nsresult rv = IDBFactory::GetDirectoryForOrigin(mOrigin,
1408 0 : getter_AddRefs(directory));
1409 0 : NS_ENSURE_SUCCESS(rv, rv);
1410 :
1411 : bool exists;
1412 0 : rv = directory->Exists(&exists);
1413 0 : NS_ENSURE_SUCCESS(rv, rv);
1414 :
1415 : // If the directory exists then enumerate all the files inside, adding up the
1416 : // sizes to get the final usage statistic.
1417 0 : if (exists && !mCanceled) {
1418 0 : rv = GetUsageForDirectory(directory, &mUsage);
1419 0 : NS_ENSURE_SUCCESS(rv, rv);
1420 : }
1421 0 : return NS_OK;
1422 : }
1423 :
1424 : nsresult
1425 0 : IndexedDatabaseManager::AsyncUsageRunnable::GetUsageForDirectory(
1426 : nsIFile* aDirectory,
1427 : PRUint64* aUsage)
1428 : {
1429 0 : NS_ASSERTION(aDirectory, "Null pointer!");
1430 0 : NS_ASSERTION(aUsage, "Null pointer!");
1431 :
1432 0 : nsCOMPtr<nsISimpleEnumerator> entries;
1433 0 : nsresult rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries));
1434 0 : NS_ENSURE_SUCCESS(rv, rv);
1435 :
1436 0 : if (!entries) {
1437 0 : return NS_OK;
1438 : }
1439 :
1440 : bool hasMore;
1441 0 : while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) &&
1442 0 : hasMore && !mCanceled) {
1443 0 : nsCOMPtr<nsISupports> entry;
1444 0 : rv = entries->GetNext(getter_AddRefs(entry));
1445 0 : NS_ENSURE_SUCCESS(rv, rv);
1446 :
1447 0 : nsCOMPtr<nsIFile> file(do_QueryInterface(entry));
1448 0 : NS_ASSERTION(file, "Don't know what this is!");
1449 :
1450 : bool isDirectory;
1451 0 : rv = file->IsDirectory(&isDirectory);
1452 0 : NS_ENSURE_SUCCESS(rv, rv);
1453 :
1454 0 : if (isDirectory) {
1455 0 : if (aUsage == &mFileUsage) {
1456 0 : NS_WARNING("Unknown directory found!");
1457 : }
1458 : else {
1459 0 : rv = GetUsageForDirectory(file, &mFileUsage);
1460 0 : NS_ENSURE_SUCCESS(rv, rv);
1461 : }
1462 :
1463 0 : continue;
1464 : }
1465 :
1466 : PRInt64 fileSize;
1467 0 : rv = file->GetFileSize(&fileSize);
1468 0 : NS_ENSURE_SUCCESS(rv, rv);
1469 :
1470 0 : NS_ASSERTION(fileSize > 0, "Negative size?!");
1471 :
1472 0 : IncrementUsage(aUsage, PRUint64(fileSize));
1473 : }
1474 0 : NS_ENSURE_SUCCESS(rv, rv);
1475 :
1476 0 : return NS_OK;
1477 : }
1478 :
1479 0 : NS_IMPL_THREADSAFE_ISUPPORTS1(IndexedDatabaseManager::AsyncUsageRunnable,
1480 : nsIRunnable)
1481 :
1482 : NS_IMETHODIMP
1483 0 : IndexedDatabaseManager::AsyncUsageRunnable::Run()
1484 : {
1485 0 : nsresult rv = RunInternal();
1486 :
1487 0 : if (!NS_IsMainThread()) {
1488 0 : if (NS_FAILED(rv)) {
1489 0 : mUsage = 0;
1490 : }
1491 :
1492 0 : if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) {
1493 0 : NS_WARNING("Failed to dispatch to main thread!");
1494 : }
1495 : }
1496 :
1497 0 : NS_ENSURE_SUCCESS(rv, rv);
1498 0 : return NS_OK;
1499 : }
1500 :
1501 24 : NS_IMPL_THREADSAFE_ISUPPORTS1(IndexedDatabaseManager::WaitForTransactionsToFinishRunnable,
1502 : nsIRunnable)
1503 :
1504 : NS_IMETHODIMP
1505 3 : IndexedDatabaseManager::WaitForTransactionsToFinishRunnable::Run()
1506 : {
1507 3 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
1508 3 : NS_ASSERTION(mOp && mOp->mHelper, "What?");
1509 :
1510 : // Don't hold the callback alive longer than necessary.
1511 6 : nsRefPtr<AsyncConnectionHelper> helper;
1512 3 : helper.swap(mOp->mHelper);
1513 :
1514 3 : mOp = nsnull;
1515 :
1516 3 : IndexedDatabaseManager::DispatchHelper(helper);
1517 :
1518 : // The helper is responsible for calling
1519 : // IndexedDatabaseManager::AllowNextSynchronizedOp.
1520 :
1521 3 : return NS_OK;
1522 : }
1523 :
1524 :
1525 84 : IndexedDatabaseManager::SynchronizedOp::SynchronizedOp(const nsACString& aOrigin,
1526 : nsIAtom* aId)
1527 84 : : mOrigin(aOrigin), mId(aId)
1528 : {
1529 84 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
1530 84 : MOZ_COUNT_CTOR(IndexedDatabaseManager::SynchronizedOp);
1531 84 : }
1532 :
1533 168 : IndexedDatabaseManager::SynchronizedOp::~SynchronizedOp()
1534 : {
1535 84 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
1536 84 : MOZ_COUNT_DTOR(IndexedDatabaseManager::SynchronizedOp);
1537 84 : }
1538 :
1539 : bool
1540 29 : IndexedDatabaseManager::SynchronizedOp::MustWaitFor(const SynchronizedOp& aRhs)
1541 : const
1542 : {
1543 29 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
1544 :
1545 : // If the origins don't match, the second can proceed.
1546 29 : if (!aRhs.mOrigin.Equals(mOrigin)) {
1547 4 : return false;
1548 : }
1549 :
1550 : // If the origins and the ids match, the second must wait.
1551 25 : if (aRhs.mId == mId) {
1552 23 : return true;
1553 : }
1554 :
1555 : // Waiting is required if either one corresponds to an origin clearing
1556 : // (a null Id).
1557 2 : if (!aRhs.mId || !mId) {
1558 0 : return true;
1559 : }
1560 :
1561 : // Otherwise, things for the same origin but different databases can proceed
1562 : // independently.
1563 2 : return false;
1564 : }
1565 :
1566 : void
1567 23 : IndexedDatabaseManager::SynchronizedOp::DelayRunnable(nsIRunnable* aRunnable)
1568 : {
1569 23 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
1570 23 : NS_ASSERTION(mDelayedRunnables.IsEmpty() || !mId,
1571 : "Only ClearOrigin operations can delay multiple runnables!");
1572 :
1573 23 : mDelayedRunnables.AppendElement(aRunnable);
1574 23 : }
1575 :
1576 : void
1577 84 : IndexedDatabaseManager::SynchronizedOp::DispatchDelayedRunnables()
1578 : {
1579 84 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
1580 84 : NS_ASSERTION(!mHelper, "Any helper should be gone by now!");
1581 :
1582 84 : PRUint32 count = mDelayedRunnables.Length();
1583 107 : for (PRUint32 index = 0; index < count; index++) {
1584 23 : NS_DispatchToCurrentThread(mDelayedRunnables[index]);
1585 : }
1586 :
1587 84 : mDelayedRunnables.Clear();
1588 84 : }
1589 :
1590 : NS_IMETHODIMP
1591 50 : IndexedDatabaseManager::InitWindowless(const jsval& aObj, JSContext* aCx)
1592 : {
1593 50 : NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE);
1594 50 : NS_ENSURE_ARG(!JSVAL_IS_PRIMITIVE(aObj));
1595 :
1596 : // Instantiating this class will register exception providers so even
1597 : // in xpcshell we will get typed (dom) exceptions, instead of general
1598 : // exceptions.
1599 100 : nsCOMPtr<nsIDOMScriptObjectFactory> sof(do_GetService(kDOMSOF_CID));
1600 :
1601 50 : JSObject* obj = JSVAL_TO_OBJECT(aObj);
1602 :
1603 50 : JSObject* global = JS_GetGlobalForObject(aCx, obj);
1604 :
1605 100 : nsCOMPtr<nsIIDBFactory> factory = IDBFactory::Create(aCx, global);
1606 50 : NS_ENSURE_TRUE(factory, NS_ERROR_FAILURE);
1607 :
1608 : jsval mozIndexedDBVal;
1609 50 : nsresult rv = nsContentUtils::WrapNative(aCx, obj, factory, &mozIndexedDBVal);
1610 50 : NS_ENSURE_SUCCESS(rv, rv);
1611 :
1612 50 : if (!JS_DefineProperty(aCx, obj, "mozIndexedDB", mozIndexedDBVal, nsnull,
1613 50 : nsnull, JSPROP_ENUMERATE)) {
1614 0 : return NS_ERROR_FAILURE;
1615 : }
1616 :
1617 50 : JSObject* keyrangeObj = JS_NewObject(aCx, nsnull, nsnull, nsnull);
1618 50 : NS_ENSURE_TRUE(keyrangeObj, NS_ERROR_OUT_OF_MEMORY);
1619 :
1620 50 : if (!IDBKeyRange::DefineConstructors(aCx, keyrangeObj)) {
1621 0 : return NS_ERROR_FAILURE;
1622 : }
1623 :
1624 50 : if (!JS_DefineProperty(aCx, obj, "IDBKeyRange", OBJECT_TO_JSVAL(keyrangeObj),
1625 50 : nsnull, nsnull, JSPROP_ENUMERATE)) {
1626 0 : return NS_ERROR_FAILURE;
1627 : }
1628 :
1629 50 : return NS_OK;
1630 : }
1631 :
1632 0 : NS_IMPL_THREADSAFE_ISUPPORTS1(IndexedDatabaseManager::AsyncDeleteFileRunnable,
1633 : nsIRunnable)
1634 :
1635 : NS_IMETHODIMP
1636 0 : IndexedDatabaseManager::AsyncDeleteFileRunnable::Run()
1637 : {
1638 0 : NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
1639 :
1640 0 : int rc = sqlite3_quota_remove(NS_ConvertUTF16toUTF8(mFilePath).get());
1641 0 : if (rc != SQLITE_OK) {
1642 0 : NS_WARNING("Failed to delete stored file!");
1643 0 : return NS_ERROR_FAILURE;
1644 : }
1645 :
1646 0 : return NS_OK;
1647 : }
|