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 "base/basictypes.h"
9 #include "AsyncConnectionHelper.h"
11 #include "mozilla/dom/quota/QuotaManager.h"
12 #include "mozilla/storage.h"
13 #include "nsComponentManagerUtils.h"
14 #include "nsContentUtils.h"
15 #include "nsProxyRelease.h"
16 #include "nsThreadUtils.h"
17 #include "nsWrapperCacheInlines.h"
19 #include "IDBEvents.h"
20 #include "IDBTransaction.h"
21 #include "IndexedDatabaseManager.h"
22 #include "ProfilerHelpers.h"
23 #include "ReportInternalError.h"
24 #include "TransactionThreadPool.h"
26 #include "ipc/IndexedDBChild.h"
27 #include "ipc/IndexedDBParent.h"
29 using namespace mozilla
;
30 USING_INDEXEDDB_NAMESPACE
31 using mozilla::dom::quota::QuotaManager
;
35 IDBTransaction
* gCurrentTransaction
= nullptr;
37 const uint32_t kProgressHandlerGranularity
= 1000;
39 class MOZ_STACK_CLASS TransactionPoolEventTarget
: public StackBasedEventTarget
42 NS_DECL_NSIEVENTTARGET
44 TransactionPoolEventTarget(IDBTransaction
* aTransaction
)
45 : mTransaction(aTransaction
)
49 IDBTransaction
* mTransaction
;
52 // This inline is just so that we always clear aBuffers appropriately even if
56 ConvertCloneReadInfosToArrayInternal(
58 nsTArray
<StructuredCloneReadInfo
>& aReadInfos
,
59 JS::MutableHandle
<JS::Value
> aResult
)
61 JS::Rooted
<JSObject
*> array(aCx
, JS_NewArrayObject(aCx
, 0));
63 IDB_WARNING("Failed to make array!");
64 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
;
67 if (!aReadInfos
.IsEmpty()) {
68 if (!JS_SetArrayLength(aCx
, array
, uint32_t(aReadInfos
.Length()))) {
69 IDB_WARNING("Failed to set array length!");
70 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
;
73 for (uint32_t index
= 0, count
= aReadInfos
.Length(); index
< count
;
75 StructuredCloneReadInfo
& readInfo
= aReadInfos
[index
];
77 JS::Rooted
<JS::Value
> val(aCx
);
78 if (!IDBObjectStore::DeserializeValue(aCx
, readInfo
, &val
)) {
79 NS_WARNING("Failed to decode!");
80 return NS_ERROR_DOM_DATA_CLONE_ERR
;
83 if (!JS_SetElement(aCx
, array
, index
, val
)) {
84 IDB_WARNING("Failed to set array element!");
85 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
;
90 aResult
.setObject(*array
);
94 } // anonymous namespace
96 HelperBase::~HelperBase()
98 if (!NS_IsMainThread()) {
100 mRequest
.forget(&request
);
103 nsCOMPtr
<nsIThread
> mainThread
;
104 NS_GetMainThread(getter_AddRefs(mainThread
));
105 NS_WARN_IF_FALSE(mainThread
, "Couldn't get the main thread!");
108 NS_ProxyRelease(mainThread
, static_cast<EventTarget
*>(request
));
115 HelperBase::WrapNative(JSContext
* aCx
,
116 nsISupports
* aNative
,
117 JS::MutableHandle
<JS::Value
> aResult
)
119 NS_ASSERTION(aCx
, "Null context!");
120 NS_ASSERTION(aNative
, "Null pointer!");
121 NS_ASSERTION(aResult
.address(), "Null pointer!");
122 NS_ASSERTION(mRequest
, "Null request!");
124 nsresult rv
= nsContentUtils::WrapNative(aCx
, aNative
, aResult
);
125 IDB_ENSURE_SUCCESS(rv
, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
);
131 HelperBase::ReleaseMainThreadObjects()
133 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
138 AsyncConnectionHelper::AsyncConnectionHelper(IDBDatabase
* aDatabase
,
139 IDBRequest
* aRequest
)
140 : HelperBase(aRequest
),
141 mDatabase(aDatabase
),
145 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
148 AsyncConnectionHelper::AsyncConnectionHelper(IDBTransaction
* aTransaction
,
149 IDBRequest
* aRequest
)
150 : HelperBase(aRequest
),
151 mDatabase(aTransaction
->mDatabase
),
152 mTransaction(aTransaction
),
156 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
159 AsyncConnectionHelper::~AsyncConnectionHelper()
161 if (!NS_IsMainThread()) {
162 IDBDatabase
* database
;
163 mDatabase
.forget(&database
);
165 IDBTransaction
* transaction
;
166 mTransaction
.forget(&transaction
);
168 nsCOMPtr
<nsIThread
> mainThread
;
169 NS_GetMainThread(getter_AddRefs(mainThread
));
170 NS_WARN_IF_FALSE(mainThread
, "Couldn't get the main thread!");
174 NS_ProxyRelease(mainThread
, static_cast<IDBWrapperCache
*>(database
));
177 NS_ProxyRelease(mainThread
, static_cast<IDBWrapperCache
*>(transaction
));
182 NS_ASSERTION(!mOldProgressHandler
, "Should not have anything here!");
185 NS_IMPL_ISUPPORTS(AsyncConnectionHelper
, nsIRunnable
,
186 mozIStorageProgressHandler
)
189 AsyncConnectionHelper::Run()
191 if (NS_IsMainThread()) {
192 PROFILER_MAIN_THREAD_LABEL("AsyncConnectionHelper", "Run",
193 js::ProfileEntry::Category::STORAGE
);
196 mTransaction
->IsAborted()) {
197 // Always fire a "error" event with ABORT_ERR if the transaction was
198 // aborted, even if the request succeeded or failed with another error.
199 mResultCode
= NS_ERROR_DOM_INDEXEDDB_ABORT_ERR
;
202 IDBTransaction
* oldTransaction
= gCurrentTransaction
;
203 gCurrentTransaction
= mTransaction
;
205 ChildProcessSendResult sendResult
=
206 IndexedDatabaseManager::IsMainProcess() ?
207 MaybeSendResponseToChildProcess(mResultCode
) :
210 switch (sendResult
) {
213 mRequest
->NotifyHelperSentResultsToChildProcess(NS_OK
);
218 case Success_NotSent
: {
220 nsresult rv
= mRequest
->NotifyHelperCompleted(this);
221 if (NS_SUCCEEDED(mResultCode
) && NS_FAILED(rv
)) {
225 IDB_PROFILER_MARK("IndexedDB Request %llu: Running main thread "
226 "response (rv = %lu)",
227 "IDBRequest[%llu] MT Done",
228 mRequest
->GetSerialNumber(), mResultCode
);
231 // Call OnError if the database had an error or if the OnSuccess
232 // handler has an error.
233 if (NS_FAILED(mResultCode
) ||
234 NS_FAILED((mResultCode
= OnSuccess()))) {
240 case Success_ActorDisconnected
: {
241 // Nothing needs to be done here.
246 IDB_WARNING("MaybeSendResultsToChildProcess failed!");
247 mResultCode
= NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
;
249 mRequest
->NotifyHelperSentResultsToChildProcess(mResultCode
);
255 MOZ_CRASH("Unknown value for ChildProcessSendResult!");
258 NS_ASSERTION(gCurrentTransaction
== mTransaction
, "Should be unchanged!");
259 gCurrentTransaction
= oldTransaction
;
261 if (mDispatched
&& mTransaction
) {
262 mTransaction
->OnRequestFinished();
265 ReleaseMainThreadObjects();
267 NS_ASSERTION(!(mDatabase
|| mTransaction
|| mRequest
), "Subclass didn't "
268 "call AsyncConnectionHelper::ReleaseMainThreadObjects!");
273 NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!");
275 PROFILER_LABEL("AsyncConnectionHelper", "Run",
276 js::ProfileEntry::Category::STORAGE
);
278 IDB_PROFILER_MARK_IF(mRequest
,
279 "IndexedDB Request %llu: Beginning database work",
280 "IDBRequest[%llu] DT Start",
281 mRequest
->GetSerialNumber());
284 nsCOMPtr
<mozIStorageConnection
> connection
;
287 rv
= mTransaction
->GetOrCreateConnection(getter_AddRefs(connection
));
288 if (NS_SUCCEEDED(rv
)) {
289 NS_ASSERTION(connection
, "This should never be null!");
293 bool setProgressHandler
= false;
295 rv
= connection
->SetProgressHandler(kProgressHandlerGranularity
, this,
296 getter_AddRefs(mOldProgressHandler
));
297 NS_WARN_IF_FALSE(NS_SUCCEEDED(rv
), "SetProgressHandler failed!");
298 if (NS_SUCCEEDED(rv
)) {
299 setProgressHandler
= true;
303 if (NS_SUCCEEDED(rv
)) {
304 bool hasSavepoint
= false;
306 QuotaManager::SetCurrentWindow(mDatabase
->GetOwner());
308 // Make the first savepoint.
310 if (!(hasSavepoint
= mTransaction
->StartSavepoint())) {
311 NS_WARNING("Failed to make savepoint!");
316 mResultCode
= DoDatabaseWork(connection
);
319 // Release or roll back the savepoint depending on the error code.
321 NS_ASSERTION(mTransaction
, "Huh?!");
322 if (NS_SUCCEEDED(mResultCode
)) {
323 mTransaction
->ReleaseSavepoint();
326 mTransaction
->RollbackSavepoint();
330 // Don't unset this until we're sure that all SQLite activity has
332 QuotaManager::SetCurrentWindow(nullptr);
336 // NS_ERROR_NOT_AVAILABLE is our special code for "database is invalidated"
337 // and we should fail with RECOVERABLE_ERR.
338 if (rv
== NS_ERROR_NOT_AVAILABLE
) {
339 mResultCode
= NS_ERROR_DOM_INDEXEDDB_RECOVERABLE_ERR
;
342 IDB_REPORT_INTERNAL_ERR();
343 mResultCode
= NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
;
347 if (setProgressHandler
) {
348 nsCOMPtr
<mozIStorageProgressHandler
> handler
;
349 rv
= connection
->RemoveProgressHandler(getter_AddRefs(handler
));
350 NS_WARN_IF_FALSE(NS_SUCCEEDED(rv
), "RemoveProgressHandler failed!");
352 if (NS_SUCCEEDED(rv
)) {
353 NS_ASSERTION(SameCOMIdentity(handler
, static_cast<nsIRunnable
*>(this)),
359 IDB_PROFILER_MARK_IF(mRequest
,
360 "IndexedDB Request %llu: Finished database work "
362 "IDBRequest[%llu] DT Done", mRequest
->GetSerialNumber(),
365 return NS_DispatchToMainThread(this);
369 AsyncConnectionHelper::OnProgress(mozIStorageConnection
* aConnection
,
372 if (mDatabase
&& mDatabase
->IsInvalidated()) {
373 // Someone is trying to delete the database file. Exit lightningfast!
378 if (mOldProgressHandler
) {
379 return mOldProgressHandler
->OnProgress(aConnection
, _retval
);
387 AsyncConnectionHelper::Dispatch(nsIEventTarget
* aTarget
)
389 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
391 nsresult rv
= Init();
396 rv
= aTarget
->Dispatch(this, NS_DISPATCH_NORMAL
);
397 NS_ENSURE_SUCCESS(rv
, rv
);
400 mTransaction
->OnNewRequest();
409 AsyncConnectionHelper::DispatchToTransactionPool()
411 NS_ASSERTION(mTransaction
, "Only ok to call this with a transaction!");
412 TransactionPoolEventTarget
target(mTransaction
);
413 return Dispatch(&target
);
418 AsyncConnectionHelper::SetCurrentTransaction(IDBTransaction
* aTransaction
)
420 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
421 NS_ASSERTION(!aTransaction
|| !gCurrentTransaction
,
422 "Stepping on another transaction!");
424 gCurrentTransaction
= aTransaction
;
429 AsyncConnectionHelper::GetCurrentTransaction()
431 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
433 return gCurrentTransaction
;
437 AsyncConnectionHelper::Init()
439 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
444 already_AddRefed
<nsIDOMEvent
>
445 AsyncConnectionHelper::CreateSuccessEvent(mozilla::dom::EventTarget
* aOwner
)
447 return CreateGenericEvent(mRequest
, NS_LITERAL_STRING(SUCCESS_EVT_STR
),
448 eDoesNotBubble
, eNotCancelable
);
452 AsyncConnectionHelper::OnSuccess()
454 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
455 NS_ASSERTION(mRequest
, "Null request!");
457 PROFILER_MAIN_THREAD_LABEL("AsyncConnectionHelper", "OnSuccess",
458 js::ProfileEntry::Category::STORAGE
);
460 nsRefPtr
<nsIDOMEvent
> event
= CreateSuccessEvent(mRequest
);
462 IDB_WARNING("Failed to create event!");
463 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
;
467 nsresult rv
= mRequest
->DispatchEvent(event
, &dummy
);
468 IDB_ENSURE_SUCCESS(rv
, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
);
470 WidgetEvent
* internalEvent
= event
->GetInternalNSEvent();
471 NS_ASSERTION(internalEvent
, "This should never be null!");
473 NS_ASSERTION(!mTransaction
||
474 mTransaction
->IsOpen() ||
475 mTransaction
->IsAborted(),
476 "How else can this be closed?!");
478 if (internalEvent
->mFlags
.mExceptionHasBeenRisen
&&
480 mTransaction
->IsOpen()) {
481 rv
= mTransaction
->Abort(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR
);
482 NS_ENSURE_SUCCESS(rv
, rv
);
489 AsyncConnectionHelper::OnError()
491 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
492 NS_ASSERTION(mRequest
, "Null request!");
494 PROFILER_MAIN_THREAD_LABEL("AsyncConnectionHelper", "OnError",
495 js::ProfileEntry::Category::STORAGE
);
497 // Make an error event and fire it at the target.
498 nsRefPtr
<nsIDOMEvent
> event
=
499 CreateGenericEvent(mRequest
, NS_LITERAL_STRING(ERROR_EVT_STR
), eDoesBubble
,
502 NS_ERROR("Failed to create event!");
507 nsresult rv
= mRequest
->DispatchEvent(event
, &doDefault
);
508 if (NS_SUCCEEDED(rv
)) {
509 NS_ASSERTION(!mTransaction
||
510 mTransaction
->IsOpen() ||
511 mTransaction
->IsAborted(),
512 "How else can this be closed?!");
514 WidgetEvent
* internalEvent
= event
->GetInternalNSEvent();
515 NS_ASSERTION(internalEvent
, "This should never be null!");
517 if (internalEvent
->mFlags
.mExceptionHasBeenRisen
&&
519 mTransaction
->IsOpen() &&
520 NS_FAILED(mTransaction
->Abort(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR
))) {
521 NS_WARNING("Failed to abort transaction!");
526 mTransaction
->IsOpen() &&
527 NS_FAILED(mTransaction
->Abort(mRequest
))) {
528 NS_WARNING("Failed to abort transaction!");
532 NS_WARNING("DispatchEvent failed!");
537 AsyncConnectionHelper::GetSuccessResult(JSContext
* aCx
,
538 JS::MutableHandle
<JS::Value
> aVal
)
540 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
547 AsyncConnectionHelper::ReleaseMainThreadObjects()
549 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
552 mTransaction
= nullptr;
554 HelperBase::ReleaseMainThreadObjects();
557 AsyncConnectionHelper::ChildProcessSendResult
558 AsyncConnectionHelper::MaybeSendResponseToChildProcess(nsresult aResultCode
)
560 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
561 NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!");
563 // If there's no request, there could never have been an actor, and so there
566 return Success_NotSent
;
569 IDBTransaction
* trans
= GetCurrentTransaction();
570 // We may not have a transaction, e.g. for deleteDatabase
572 return Success_NotSent
;
575 // Are we shutting down the child?
576 IndexedDBDatabaseParent
* dbActor
= trans
->Database()->GetActorParent();
577 if (dbActor
&& dbActor
->IsDisconnected()) {
578 return Success_ActorDisconnected
;
581 IndexedDBRequestParentBase
* actor
= mRequest
->GetActorParent();
583 return Success_NotSent
;
586 IDB_PROFILER_MARK("IndexedDB Request %llu: Sending response to child "
587 "process (rv = %lu)",
588 "IDBRequest[%llu] MT Done",
589 mRequest
->GetSerialNumber(), aResultCode
);
591 return SendResponseToChildProcess(aResultCode
);
595 AsyncConnectionHelper::OnParentProcessRequestComplete(
596 const ResponseValue
& aResponseValue
)
598 NS_ASSERTION(!IndexedDatabaseManager::IsMainProcess(), "Wrong process!");
600 if (aResponseValue
.type() == ResponseValue::Tnsresult
) {
601 NS_ASSERTION(NS_FAILED(aResponseValue
.get_nsresult()), "Huh?");
602 SetError(aResponseValue
.get_nsresult());
605 nsresult rv
= UnpackResponseFromParentProcess(aResponseValue
);
606 NS_ENSURE_SUCCESS(rv
, rv
);
614 AsyncConnectionHelper::ConvertToArrayAndCleanup(
616 nsTArray
<StructuredCloneReadInfo
>& aReadInfos
,
617 JS::MutableHandle
<JS::Value
> aResult
)
619 NS_ASSERTION(aCx
, "Null context!");
620 NS_ASSERTION(aResult
.address(), "Null pointer!");
622 nsresult rv
= ConvertCloneReadInfosToArrayInternal(aCx
, aReadInfos
, aResult
);
624 for (uint32_t index
= 0; index
< aReadInfos
.Length(); index
++) {
625 aReadInfos
[index
].mCloneBuffer
.clear();
632 NS_IMETHODIMP_(MozExternalRefCountType
)
633 StackBasedEventTarget::AddRef()
635 NS_NOTREACHED("Don't call me!");
639 NS_IMETHODIMP_(MozExternalRefCountType
)
640 StackBasedEventTarget::Release()
642 NS_NOTREACHED("Don't call me!");
647 StackBasedEventTarget::QueryInterface(REFNSIID aIID
,
650 NS_NOTREACHED("Don't call me!");
651 return NS_NOINTERFACE
;
655 ImmediateRunEventTarget::Dispatch(nsIRunnable
* aRunnable
,
658 NS_ASSERTION(aRunnable
, "Null pointer!");
660 nsCOMPtr
<nsIRunnable
> runnable(aRunnable
);
661 DebugOnly
<nsresult
> rv
=
663 MOZ_ASSERT(NS_SUCCEEDED(rv
));
668 ImmediateRunEventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread
)
670 *aIsOnCurrentThread
= true;
675 TransactionPoolEventTarget::Dispatch(nsIRunnable
* aRunnable
,
678 NS_ASSERTION(aRunnable
, "Null pointer!");
679 NS_ASSERTION(aFlags
== NS_DISPATCH_NORMAL
, "Unsupported!");
681 TransactionThreadPool
* pool
= TransactionThreadPool::GetOrCreate();
682 NS_ENSURE_TRUE(pool
, NS_ERROR_UNEXPECTED
);
684 nsresult rv
= pool
->Dispatch(mTransaction
, aRunnable
, false, nullptr);
685 NS_ENSURE_SUCCESS(rv
, rv
);
691 TransactionPoolEventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread
)
693 *aIsOnCurrentThread
= false;
698 NoDispatchEventTarget::Dispatch(nsIRunnable
* aRunnable
,
701 nsCOMPtr
<nsIRunnable
> runnable
= aRunnable
;
706 NoDispatchEventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread
)
708 *aIsOnCurrentThread
= true;