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 /* 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 "IndexedDatabaseManager.h"
9 #include "nsIConsoleService.h"
10 #include "nsIDiskSpaceWatcher.h"
11 #include "nsIDOMScriptObjectFactory.h"
13 #include "nsIFileStorage.h"
14 #include "nsIObserverService.h"
15 #include "nsIScriptError.h"
17 #include "mozilla/ClearOnShutdown.h"
18 #include "mozilla/CondVar.h"
19 #include "mozilla/dom/quota/QuotaManager.h"
20 #include "mozilla/dom/quota/Utilities.h"
21 #include "mozilla/dom/TabContext.h"
22 #include "mozilla/Services.h"
23 #include "mozilla/storage.h"
24 #include "nsContentUtils.h"
25 #include "nsEventDispatcher.h"
26 #include "nsThreadUtils.h"
28 #include "IDBEvents.h"
29 #include "IDBFactory.h"
30 #include "IDBKeyRange.h"
31 #include "IDBRequest.h"
33 // The two possible values for the data argument when receiving the disk space
34 // observer notification.
35 #define LOW_DISK_SPACE_DATA_FULL "full"
36 #define LOW_DISK_SPACE_DATA_FREE "free"
38 USING_INDEXEDDB_NAMESPACE
39 using namespace mozilla::dom
;
42 static NS_DEFINE_CID(kDOMSOF_CID
, NS_DOM_SCRIPT_OBJECT_FACTORY_CID
);
46 mozilla::StaticRefPtr
<IndexedDatabaseManager
> gInstance
;
48 int32_t gInitialized
= 0;
51 class AsyncDeleteFileRunnable MOZ_FINAL
: public nsIRunnable
57 AsyncDeleteFileRunnable(FileManager
* aFileManager
, int64_t aFileId
);
60 nsRefPtr
<FileManager
> mFileManager
;
64 class GetFileReferencesHelper MOZ_FINAL
: public nsIRunnable
70 GetFileReferencesHelper(const nsACString
& aOrigin
,
71 const nsAString
& aDatabaseName
,
73 : mOrigin(aOrigin
), mDatabaseName(aDatabaseName
), mFileId(aFileId
),
74 mMutex(IndexedDatabaseManager::FileMutex()),
75 mCondVar(mMutex
, "GetFileReferencesHelper::mCondVar"),
84 DispatchAndReturnFileReferences(int32_t* aMemRefCnt
,
86 int32_t* aSliceRefCnt
,
91 nsString mDatabaseName
;
94 mozilla::Mutex
& mMutex
;
95 mozilla::CondVar mCondVar
;
104 InvalidateAndRemoveFileManagers(
105 const nsACString
& aKey
,
106 nsAutoPtr
<nsTArray
<nsRefPtr
<FileManager
> > >& aValue
,
109 AssertIsOnIOThread();
110 NS_ASSERTION(!aKey
.IsEmpty(), "Empty key!");
111 NS_ASSERTION(aValue
, "Null pointer!");
113 const nsACString
* pattern
=
114 static_cast<const nsACString
*>(aUserArg
);
116 if (!pattern
|| PatternMatchesOrigin(*pattern
, aKey
)) {
117 for (uint32_t i
= 0; i
< aValue
->Length(); i
++) {
118 nsRefPtr
<FileManager
>& fileManager
= aValue
->ElementAt(i
);
119 fileManager
->Invalidate();
121 return PL_DHASH_REMOVE
;
124 return PL_DHASH_NEXT
;
127 } // anonymous namespace
129 IndexedDatabaseManager::IndexedDatabaseManager()
130 : mFileMutex("IndexedDatabaseManager.mFileMutex")
132 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
134 mFileManagers
.Init();
137 IndexedDatabaseManager::~IndexedDatabaseManager()
139 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
142 bool IndexedDatabaseManager::sIsMainProcess
= false;
143 int32_t IndexedDatabaseManager::sLowDiskSpaceMode
= 0;
146 IndexedDatabaseManager
*
147 IndexedDatabaseManager::GetOrCreate()
149 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
152 NS_ERROR("Calling GetOrCreate() after shutdown!");
157 sIsMainProcess
= XRE_GetProcessType() == GeckoProcessType_Default
;
159 if (sIsMainProcess
) {
160 // See if we're starting up in low disk space conditions.
161 nsCOMPtr
<nsIDiskSpaceWatcher
> watcher
=
162 do_GetService(DISKSPACEWATCHER_CONTRACTID
);
165 if (NS_SUCCEEDED(watcher
->GetIsDiskFull(&isDiskFull
))) {
166 sLowDiskSpaceMode
= isDiskFull
? 1 : 0;
169 NS_WARNING("GetIsDiskFull failed!");
173 NS_WARNING("No disk space watcher component available!");
177 nsRefPtr
<IndexedDatabaseManager
> instance(new IndexedDatabaseManager());
179 nsresult rv
= instance
->Init();
180 NS_ENSURE_SUCCESS(rv
, nullptr);
182 if (PR_ATOMIC_SET(&gInitialized
, 1)) {
183 NS_ERROR("Initialized more than once?!");
186 gInstance
= instance
;
188 ClearOnShutdown(&gInstance
);
195 IndexedDatabaseManager
*
196 IndexedDatabaseManager::Get()
198 // Does not return an owning reference.
203 IndexedDatabaseManager
*
204 IndexedDatabaseManager::FactoryCreate()
206 // Returns a raw pointer that carries an owning reference! Lame, but the
207 // singleton factory macros force this.
208 IndexedDatabaseManager
* mgr
= GetOrCreate();
214 IndexedDatabaseManager::Init()
216 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
218 // Make sure that the quota manager is up.
219 QuotaManager
* qm
= QuotaManager::GetOrCreate();
222 // During Init() we can't yet call IsMainProcess(), just check sIsMainProcess
224 if (sIsMainProcess
) {
225 // Must initialize the storage service on the main thread.
226 nsCOMPtr
<mozIStorageService
> ss
=
227 do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID
);
230 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
231 NS_ENSURE_STATE(obs
);
234 obs
->AddObserver(this, DISKSPACEWATCHER_OBSERVER_TOPIC
, false);
235 NS_ENSURE_SUCCESS(rv
, rv
);
242 IndexedDatabaseManager::Destroy()
244 // Setting the closed flag prevents the service from being recreated.
245 // Don't set it though if there's no real instance created.
246 if (!!gInitialized
&& PR_ATOMIC_SET(&gClosed
, 1)) {
247 NS_ERROR("Shutdown more than once?!");
255 IndexedDatabaseManager::FireWindowOnError(nsPIDOMWindow
* aOwner
,
256 nsEventChainPostVisitor
& aVisitor
)
258 NS_ENSURE_TRUE(aVisitor
.mDOMEvent
, NS_ERROR_UNEXPECTED
);
263 if (aVisitor
.mEventStatus
== nsEventStatus_eConsumeNoDefault
) {
268 nsresult rv
= aVisitor
.mDOMEvent
->GetType(type
);
269 NS_ENSURE_SUCCESS(rv
, rv
);
271 if (!type
.EqualsLiteral(ERROR_EVT_STR
)) {
275 nsCOMPtr
<EventTarget
> eventTarget
=
276 aVisitor
.mDOMEvent
->InternalDOMEvent()->GetTarget();
278 nsCOMPtr
<nsIIDBRequest
> strongRequest
= do_QueryInterface(eventTarget
);
279 IDBRequest
* request
= static_cast<IDBRequest
*>(strongRequest
.get());
280 NS_ENSURE_TRUE(request
, NS_ERROR_UNEXPECTED
);
283 nsRefPtr
<DOMError
> error
= request
->GetError(ret
);
285 return ret
.ErrorCode();
290 error
->GetName(errorName
);
293 nsScriptErrorEvent
event(true, NS_LOAD_ERROR
);
294 request
->FillScriptErrorEvent(&event
);
295 NS_ABORT_IF_FALSE(event
.fileName
,
296 "FillScriptErrorEvent should give us a non-null string "
297 "for our error's fileName");
299 event
.errorMsg
= errorName
.get();
301 nsCOMPtr
<nsIScriptGlobalObject
> sgo(do_QueryInterface(aOwner
));
302 NS_ASSERTION(sgo
, "How can this happen?!");
304 nsEventStatus status
= nsEventStatus_eIgnore
;
305 if (NS_FAILED(sgo
->HandleScriptError(&event
, &status
))) {
306 NS_WARNING("Failed to dispatch script error event");
307 status
= nsEventStatus_eIgnore
;
310 bool preventDefaultCalled
= status
== nsEventStatus_eConsumeNoDefault
;
311 if (preventDefaultCalled
) {
315 // Log an error to the error console.
316 nsCOMPtr
<nsIScriptError
> scriptError
=
317 do_CreateInstance(NS_SCRIPTERROR_CONTRACTID
, &rv
);
318 NS_ENSURE_SUCCESS(rv
, rv
);
320 if (NS_FAILED(scriptError
->InitWithWindowID(errorName
,
321 nsDependentString(event
.fileName
),
322 EmptyString(), event
.lineNr
,
325 aOwner
->WindowID()))) {
326 NS_WARNING("Failed to init script error!");
327 return NS_ERROR_FAILURE
;
330 nsCOMPtr
<nsIConsoleService
> consoleService
=
331 do_GetService(NS_CONSOLESERVICE_CONTRACTID
, &rv
);
332 NS_ENSURE_SUCCESS(rv
, rv
);
334 return consoleService
->LogMessage(scriptError
);
339 IndexedDatabaseManager::TabContextMayAccessOrigin(const TabContext
& aContext
,
340 const nsACString
& aOrigin
)
342 NS_ASSERTION(!aOrigin
.IsEmpty(), "Empty origin!");
344 // If aContext is for a browser element, it's allowed only to access other
345 // browser elements. But if aContext is not for a browser element, it may
346 // access both browser and non-browser elements.
347 nsAutoCString pattern
;
348 QuotaManager::GetOriginPatternStringMaybeIgnoreBrowser(
349 aContext
.OwnOrContainingAppId(),
350 aContext
.IsBrowserElement(),
353 return PatternMatchesOrigin(pattern
, aOrigin
);
358 IndexedDatabaseManager::IsClosed()
366 IndexedDatabaseManager::IsMainProcess()
368 NS_ASSERTION(gInstance
,
369 "IsMainProcess() called before indexedDB has been initialized!");
370 NS_ASSERTION((XRE_GetProcessType() == GeckoProcessType_Default
) ==
371 sIsMainProcess
, "XRE_GetProcessType changed its tune!");
372 return sIsMainProcess
;
377 IndexedDatabaseManager::InLowDiskSpaceMode()
379 NS_ASSERTION(gInstance
,
380 "InLowDiskSpaceMode() called before indexedDB has been "
382 return !!sLowDiskSpaceMode
;
386 already_AddRefed
<FileManager
>
387 IndexedDatabaseManager::GetFileManager(const nsACString
& aOrigin
,
388 const nsAString
& aDatabaseName
)
390 AssertIsOnIOThread();
392 nsTArray
<nsRefPtr
<FileManager
> >* array
;
393 if (!mFileManagers
.Get(aOrigin
, &array
)) {
397 for (uint32_t i
= 0; i
< array
->Length(); i
++) {
398 nsRefPtr
<FileManager
>& fileManager
= array
->ElementAt(i
);
400 if (fileManager
->DatabaseName().Equals(aDatabaseName
)) {
401 nsRefPtr
<FileManager
> result
= fileManager
;
402 return result
.forget();
410 IndexedDatabaseManager::AddFileManager(FileManager
* aFileManager
)
412 AssertIsOnIOThread();
413 NS_ASSERTION(aFileManager
, "Null file manager!");
415 nsTArray
<nsRefPtr
<FileManager
> >* array
;
416 if (!mFileManagers
.Get(aFileManager
->Origin(), &array
)) {
417 array
= new nsTArray
<nsRefPtr
<FileManager
> >();
418 mFileManagers
.Put(aFileManager
->Origin(), array
);
421 array
->AppendElement(aFileManager
);
425 IndexedDatabaseManager::InvalidateAllFileManagers()
427 AssertIsOnIOThread();
429 mFileManagers
.Enumerate(InvalidateAndRemoveFileManagers
, nullptr);
433 IndexedDatabaseManager::InvalidateFileManagersForPattern(
434 const nsACString
& aPattern
)
436 AssertIsOnIOThread();
437 NS_ASSERTION(!aPattern
.IsEmpty(), "Empty pattern!");
439 mFileManagers
.Enumerate(InvalidateAndRemoveFileManagers
,
440 const_cast<nsACString
*>(&aPattern
));
444 IndexedDatabaseManager::InvalidateFileManager(const nsACString
& aOrigin
,
445 const nsAString
& aDatabaseName
)
447 AssertIsOnIOThread();
449 nsTArray
<nsRefPtr
<FileManager
> >* array
;
450 if (!mFileManagers
.Get(aOrigin
, &array
)) {
454 for (uint32_t i
= 0; i
< array
->Length(); i
++) {
455 nsRefPtr
<FileManager
> fileManager
= array
->ElementAt(i
);
456 if (fileManager
->DatabaseName().Equals(aDatabaseName
)) {
457 fileManager
->Invalidate();
458 array
->RemoveElementAt(i
);
460 if (array
->IsEmpty()) {
461 mFileManagers
.Remove(aOrigin
);
470 IndexedDatabaseManager::AsyncDeleteFile(FileManager
* aFileManager
,
473 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
475 NS_ENSURE_ARG_POINTER(aFileManager
);
477 QuotaManager
* quotaManager
= QuotaManager::Get();
478 NS_ASSERTION(quotaManager
, "Shouldn't be null!");
480 // See if we're currently clearing the storages for this origin. If so then
481 // we pretend that we've already deleted everything.
482 if (quotaManager
->IsClearOriginPending(aFileManager
->Origin())) {
486 nsRefPtr
<AsyncDeleteFileRunnable
> runnable
=
487 new AsyncDeleteFileRunnable(aFileManager
, aFileId
);
490 quotaManager
->IOThread()->Dispatch(runnable
, NS_DISPATCH_NORMAL
);
491 NS_ENSURE_SUCCESS(rv
, rv
);
497 IndexedDatabaseManager::BlockAndGetFileReferences(
498 const nsACString
& aOrigin
,
499 const nsAString
& aDatabaseName
,
503 int32_t* aSliceRefCnt
,
506 nsRefPtr
<GetFileReferencesHelper
> helper
=
507 new GetFileReferencesHelper(aOrigin
, aDatabaseName
, aFileId
);
509 nsresult rv
= helper
->DispatchAndReturnFileReferences(aRefCnt
, aDBRefCnt
,
510 aSliceRefCnt
, aResult
);
511 NS_ENSURE_SUCCESS(rv
, rv
);
516 NS_IMPL_ADDREF(IndexedDatabaseManager
)
517 NS_IMPL_RELEASE_WITH_DESTROY(IndexedDatabaseManager
, Destroy())
518 NS_IMPL_QUERY_INTERFACE2(IndexedDatabaseManager
, nsIIndexedDatabaseManager
,
522 IndexedDatabaseManager::InitWindowless(const jsval
& aObj
, JSContext
* aCx
)
524 NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE
);
525 NS_ENSURE_ARG(!JSVAL_IS_PRIMITIVE(aObj
));
527 JS::Rooted
<JSObject
*> obj(aCx
, JSVAL_TO_OBJECT(aObj
));
530 if (!JS_HasProperty(aCx
, obj
, "indexedDB", &hasIndexedDB
)) {
531 return NS_ERROR_FAILURE
;
535 NS_WARNING("Passed object already has an 'indexedDB' property!");
536 return NS_ERROR_FAILURE
;
539 // Instantiating this class will register exception providers so even
540 // in xpcshell we will get typed (dom) exceptions, instead of general
542 nsCOMPtr
<nsIDOMScriptObjectFactory
> sof(do_GetService(kDOMSOF_CID
));
544 JS::Rooted
<JSObject
*> global(aCx
, JS_GetGlobalForObject(aCx
, obj
));
545 NS_ASSERTION(global
, "What?! No global!");
547 nsRefPtr
<IDBFactory
> factory
;
549 IDBFactory::Create(aCx
, global
, nullptr, getter_AddRefs(factory
));
550 NS_ENSURE_SUCCESS(rv
, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
);
552 NS_ASSERTION(factory
, "This should never fail for chrome!");
554 JS::Rooted
<JS::Value
> indexedDBVal(aCx
);
555 rv
= nsContentUtils::WrapNative(aCx
, obj
, factory
, indexedDBVal
.address());
556 NS_ENSURE_SUCCESS(rv
, rv
);
558 if (!JS_DefineProperty(aCx
, obj
, "indexedDB", indexedDBVal
, nullptr,
559 nullptr, JSPROP_ENUMERATE
)) {
560 return NS_ERROR_FAILURE
;
563 JS::Rooted
<JSObject
*> keyrangeObj(aCx
,
564 JS_NewObject(aCx
, nullptr, nullptr, nullptr));
565 NS_ENSURE_TRUE(keyrangeObj
, NS_ERROR_OUT_OF_MEMORY
);
567 if (!IDBKeyRange::DefineConstructors(aCx
, keyrangeObj
)) {
568 return NS_ERROR_FAILURE
;
571 if (!JS_DefineProperty(aCx
, obj
, "IDBKeyRange", OBJECT_TO_JSVAL(keyrangeObj
),
572 nullptr, nullptr, JSPROP_ENUMERATE
)) {
573 return NS_ERROR_FAILURE
;
580 IndexedDatabaseManager::Observe(nsISupports
* aSubject
, const char* aTopic
,
581 const PRUnichar
* aData
)
583 NS_ASSERTION(IsMainProcess(), "Wrong process!");
584 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
586 if (!strcmp(aTopic
, DISKSPACEWATCHER_OBSERVER_TOPIC
)) {
587 NS_ASSERTION(aData
, "No data?!");
589 const nsDependentString
data(aData
);
591 if (data
.EqualsLiteral(LOW_DISK_SPACE_DATA_FULL
)) {
592 PR_ATOMIC_SET(&sLowDiskSpaceMode
, 1);
594 else if (data
.EqualsLiteral(LOW_DISK_SPACE_DATA_FREE
)) {
595 PR_ATOMIC_SET(&sLowDiskSpaceMode
, 0);
598 NS_NOTREACHED("Unknown data value!");
604 NS_NOTREACHED("Unknown topic!");
605 return NS_ERROR_UNEXPECTED
;
608 AsyncDeleteFileRunnable::AsyncDeleteFileRunnable(FileManager
* aFileManager
,
610 : mFileManager(aFileManager
), mFileId(aFileId
)
614 NS_IMPL_THREADSAFE_ISUPPORTS1(AsyncDeleteFileRunnable
,
618 AsyncDeleteFileRunnable::Run()
620 AssertIsOnIOThread();
622 nsCOMPtr
<nsIFile
> directory
= mFileManager
->GetDirectory();
623 NS_ENSURE_TRUE(directory
, NS_ERROR_FAILURE
);
625 nsCOMPtr
<nsIFile
> file
= mFileManager
->GetFileForId(directory
, mFileId
);
626 NS_ENSURE_TRUE(file
, NS_ERROR_FAILURE
);
631 if (mFileManager
->Privilege() != Chrome
) {
632 rv
= file
->GetFileSize(&fileSize
);
633 NS_ENSURE_SUCCESS(rv
, NS_ERROR_FAILURE
);
636 rv
= file
->Remove(false);
637 NS_ENSURE_SUCCESS(rv
, NS_ERROR_FAILURE
);
639 if (mFileManager
->Privilege() != Chrome
) {
640 QuotaManager
* quotaManager
= QuotaManager::Get();
641 NS_ASSERTION(quotaManager
, "Shouldn't be null!");
643 quotaManager
->DecreaseUsageForOrigin(mFileManager
->Origin(), fileSize
);
646 directory
= mFileManager
->GetJournalDirectory();
647 NS_ENSURE_TRUE(directory
, NS_ERROR_FAILURE
);
649 file
= mFileManager
->GetFileForId(directory
, mFileId
);
650 NS_ENSURE_TRUE(file
, NS_ERROR_FAILURE
);
652 rv
= file
->Remove(false);
653 NS_ENSURE_SUCCESS(rv
, rv
);
659 GetFileReferencesHelper::DispatchAndReturnFileReferences(int32_t* aMemRefCnt
,
661 int32_t* aSliceRefCnt
,
664 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
666 QuotaManager
* quotaManager
= QuotaManager::Get();
667 NS_ASSERTION(quotaManager
, "Shouldn't be null!");
670 quotaManager
->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL
);
671 NS_ENSURE_SUCCESS(rv
, rv
);
673 mozilla::MutexAutoLock
autolock(mMutex
);
678 *aMemRefCnt
= mMemRefCnt
;
679 *aDBRefCnt
= mDBRefCnt
;
680 *aSliceRefCnt
= mSliceRefCnt
;
686 NS_IMPL_THREADSAFE_ISUPPORTS1(GetFileReferencesHelper
,
690 GetFileReferencesHelper::Run()
692 AssertIsOnIOThread();
694 IndexedDatabaseManager
* mgr
= IndexedDatabaseManager::Get();
695 NS_ASSERTION(mgr
, "This should never fail!");
697 nsRefPtr
<FileManager
> fileManager
=
698 mgr
->GetFileManager(mOrigin
, mDatabaseName
);
701 nsRefPtr
<FileInfo
> fileInfo
= fileManager
->GetFileInfo(mFileId
);
704 fileInfo
->GetReferences(&mMemRefCnt
, &mDBRefCnt
, &mSliceRefCnt
);
706 if (mMemRefCnt
!= -1) {
707 // We added an extra temp ref, so account for that accordingly.
715 mozilla::MutexAutoLock
lock(mMutex
);
716 NS_ASSERTION(mWaiting
, "Huh?!");