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 "AsyncConnectionHelper.h"
41 :
42 : #include "mozilla/storage.h"
43 : #include "nsComponentManagerUtils.h"
44 : #include "nsContentUtils.h"
45 : #include "nsProxyRelease.h"
46 : #include "nsThreadUtils.h"
47 : #include "nsWrapperCacheInlines.h"
48 :
49 : #include "IDBEvents.h"
50 : #include "IDBTransaction.h"
51 : #include "IndexedDatabaseManager.h"
52 : #include "TransactionThreadPool.h"
53 :
54 : USING_INDEXEDDB_NAMESPACE
55 :
56 : namespace {
57 :
58 : IDBTransaction* gCurrentTransaction = nsnull;
59 :
60 : const PRUint32 kProgressHandlerGranularity = 1000;
61 :
62 : NS_STACK_CLASS
63 : class TransactionPoolEventTarget : public nsIEventTarget
64 : {
65 : public:
66 : NS_DECL_ISUPPORTS
67 : NS_DECL_NSIEVENTTARGET
68 :
69 4670 : TransactionPoolEventTarget(IDBTransaction* aTransaction)
70 4670 : : mTransaction(aTransaction)
71 4670 : { }
72 :
73 : private:
74 : IDBTransaction* mTransaction;
75 : };
76 :
77 : // This inline is just so that we always clear aBuffers appropriately even if
78 : // something fails.
79 : inline
80 : nsresult
81 29 : ConvertCloneReadInfosToArrayInternal(
82 : JSContext* aCx,
83 : nsTArray<StructuredCloneReadInfo>& aReadInfos,
84 : jsval* aResult)
85 : {
86 29 : JSObject* array = JS_NewArrayObject(aCx, 0, nsnull);
87 29 : if (!array) {
88 0 : NS_WARNING("Failed to make array!");
89 0 : return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
90 : }
91 :
92 29 : if (!aReadInfos.IsEmpty()) {
93 28 : if (!JS_SetArrayLength(aCx, array, uint32_t(aReadInfos.Length()))) {
94 0 : NS_WARNING("Failed to set array length!");
95 0 : return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
96 : }
97 :
98 171 : for (uint32_t index = 0, count = aReadInfos.Length(); index < count;
99 : index++) {
100 143 : StructuredCloneReadInfo& readInfo = aReadInfos[index];
101 :
102 : jsval val;
103 143 : if (!IDBObjectStore::DeserializeValue(aCx, readInfo, &val)) {
104 0 : NS_WARNING("Failed to decode!");
105 0 : return NS_ERROR_DOM_DATA_CLONE_ERR;
106 : }
107 :
108 143 : if (!JS_SetElement(aCx, array, index, &val)) {
109 0 : NS_WARNING("Failed to set array element!");
110 0 : return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
111 : }
112 : }
113 : }
114 :
115 29 : *aResult = OBJECT_TO_JSVAL(array);
116 29 : return NS_OK;
117 : }
118 :
119 : } // anonymous namespace
120 :
121 9492 : HelperBase::~HelperBase()
122 : {
123 4746 : if (!NS_IsMainThread()) {
124 : IDBRequest* request;
125 33 : mRequest.forget(&request);
126 :
127 33 : if (request) {
128 0 : nsCOMPtr<nsIThread> mainThread;
129 0 : NS_GetMainThread(getter_AddRefs(mainThread));
130 0 : NS_WARN_IF_FALSE(mainThread, "Couldn't get the main thread!");
131 :
132 0 : if (mainThread) {
133 0 : NS_ProxyRelease(mainThread, static_cast<nsIDOMEventTarget*>(request));
134 : }
135 : }
136 : }
137 9492 : }
138 :
139 : nsresult
140 1861 : HelperBase::WrapNative(JSContext* aCx,
141 : nsISupports* aNative,
142 : jsval* aResult)
143 : {
144 1861 : NS_ASSERTION(aCx, "Null context!");
145 1861 : NS_ASSERTION(aNative, "Null pointer!");
146 1861 : NS_ASSERTION(aResult, "Null pointer!");
147 1861 : NS_ASSERTION(mRequest, "Null request!");
148 :
149 1861 : JSObject* global = mRequest->GetParentObject();
150 1861 : NS_ASSERTION(global, "This should never be null!");
151 :
152 : nsresult rv =
153 1861 : nsContentUtils::WrapNative(aCx, global, aNative, aResult);
154 1861 : NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
155 :
156 1861 : return NS_OK;
157 : }
158 :
159 : void
160 4746 : HelperBase::ReleaseMainThreadObjects()
161 : {
162 4746 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
163 :
164 4746 : mRequest = nsnull;
165 4746 : }
166 :
167 0 : AsyncConnectionHelper::AsyncConnectionHelper(IDBDatabase* aDatabase,
168 : IDBRequest* aRequest)
169 : : HelperBase(aRequest),
170 : mDatabase(aDatabase),
171 : mResultCode(NS_OK),
172 0 : mDispatched(false)
173 : {
174 0 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
175 0 : }
176 :
177 4670 : AsyncConnectionHelper::AsyncConnectionHelper(IDBTransaction* aTransaction,
178 : IDBRequest* aRequest)
179 : : HelperBase(aRequest),
180 : mDatabase(aTransaction->mDatabase),
181 : mTransaction(aTransaction),
182 : mResultCode(NS_OK),
183 4670 : mDispatched(false)
184 : {
185 4670 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
186 4670 : }
187 :
188 9340 : AsyncConnectionHelper::~AsyncConnectionHelper()
189 : {
190 4670 : if (!NS_IsMainThread()) {
191 : IDBDatabase* database;
192 32 : mDatabase.forget(&database);
193 :
194 : IDBTransaction* transaction;
195 32 : mTransaction.forget(&transaction);
196 :
197 64 : nsCOMPtr<nsIThread> mainThread;
198 32 : NS_GetMainThread(getter_AddRefs(mainThread));
199 32 : NS_WARN_IF_FALSE(mainThread, "Couldn't get the main thread!");
200 :
201 32 : if (mainThread) {
202 32 : if (database) {
203 0 : NS_ProxyRelease(mainThread, static_cast<nsIIDBDatabase*>(database));
204 : }
205 32 : if (transaction) {
206 : NS_ProxyRelease(mainThread,
207 0 : static_cast<nsIIDBTransaction*>(transaction));
208 : }
209 : }
210 : }
211 :
212 4670 : NS_ASSERTION(!mOldProgressHandler, "Should not have anything here!");
213 9340 : }
214 :
215 133932 : NS_IMPL_THREADSAFE_ISUPPORTS2(AsyncConnectionHelper, nsIRunnable,
216 : mozIStorageProgressHandler)
217 :
218 : NS_IMETHODIMP
219 9340 : AsyncConnectionHelper::Run()
220 : {
221 9340 : if (NS_IsMainThread()) {
222 9340 : if (mTransaction &&
223 9340 : mTransaction->IsAborted()) {
224 : // Always fire a "error" event with ABORT_ERR if the transaction was
225 : // aborted, even if the request succeeded or failed with another error.
226 14 : mResultCode = NS_ERROR_DOM_INDEXEDDB_ABORT_ERR;
227 : }
228 :
229 4670 : IDBTransaction* oldTransaction = gCurrentTransaction;
230 4670 : gCurrentTransaction = mTransaction;
231 :
232 4670 : if (mRequest) {
233 4237 : nsresult rv = mRequest->NotifyHelperCompleted(this);
234 4237 : if (NS_SUCCEEDED(mResultCode) && NS_FAILED(rv)) {
235 0 : mResultCode = rv;
236 : }
237 : }
238 :
239 : // Call OnError if the database had an error or if the OnSuccess handler
240 : // has an error.
241 13946 : if (NS_FAILED(mResultCode) ||
242 9276 : NS_FAILED((mResultCode = OnSuccess()))) {
243 32 : OnError();
244 : }
245 :
246 4670 : NS_ASSERTION(gCurrentTransaction == mTransaction, "Should be unchanged!");
247 4670 : gCurrentTransaction = oldTransaction;
248 :
249 4670 : if (mDispatched && mTransaction) {
250 4670 : mTransaction->OnRequestFinished();
251 : }
252 :
253 4670 : ReleaseMainThreadObjects();
254 :
255 4670 : NS_ASSERTION(!(mDatabase || mTransaction || mRequest), "Subclass didn't "
256 : "call AsyncConnectionHelper::ReleaseMainThreadObjects!");
257 :
258 4670 : return NS_OK;
259 : }
260 :
261 4670 : nsresult rv = NS_OK;
262 9340 : nsCOMPtr<mozIStorageConnection> connection;
263 :
264 4670 : if (mTransaction) {
265 4670 : rv = mTransaction->GetOrCreateConnection(getter_AddRefs(connection));
266 4670 : if (NS_SUCCEEDED(rv)) {
267 4670 : NS_ASSERTION(connection, "This should never be null!");
268 : }
269 : }
270 :
271 4670 : bool setProgressHandler = false;
272 4670 : if (connection) {
273 4670 : rv = connection->SetProgressHandler(kProgressHandlerGranularity, this,
274 4670 : getter_AddRefs(mOldProgressHandler));
275 4670 : NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "SetProgressHandler failed!");
276 4670 : if (NS_SUCCEEDED(rv)) {
277 4670 : setProgressHandler = true;
278 : }
279 : }
280 :
281 4670 : if (NS_SUCCEEDED(rv)) {
282 4670 : bool hasSavepoint = false;
283 4670 : if (mDatabase) {
284 4670 : IndexedDatabaseManager::SetCurrentWindow(mDatabase->GetOwner());
285 :
286 : // Make the first savepoint.
287 4670 : if (mTransaction) {
288 4670 : if (!(hasSavepoint = mTransaction->StartSavepoint())) {
289 0 : NS_WARNING("Failed to make savepoint!");
290 : }
291 : }
292 : }
293 :
294 4670 : mResultCode = DoDatabaseWork(connection);
295 :
296 4670 : if (mDatabase) {
297 4670 : IndexedDatabaseManager::SetCurrentWindow(nsnull);
298 :
299 : // Release or roll back the savepoint depending on the error code.
300 4670 : if (hasSavepoint) {
301 4670 : NS_ASSERTION(mTransaction, "Huh?!");
302 4670 : if (NS_SUCCEEDED(mResultCode)) {
303 4651 : mTransaction->ReleaseSavepoint();
304 : }
305 : else {
306 19 : mTransaction->RollbackSavepoint();
307 : }
308 : }
309 : }
310 : }
311 : else {
312 : // NS_ERROR_NOT_AVAILABLE is our special code for "database is invalidated"
313 : // and we should fail with RECOVERABLE_ERR.
314 0 : if (rv == NS_ERROR_NOT_AVAILABLE) {
315 0 : mResultCode = NS_ERROR_DOM_INDEXEDDB_RECOVERABLE_ERR;
316 : }
317 : else {
318 0 : mResultCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
319 : }
320 : }
321 :
322 4670 : if (setProgressHandler) {
323 9340 : nsCOMPtr<mozIStorageProgressHandler> handler;
324 4670 : rv = connection->RemoveProgressHandler(getter_AddRefs(handler));
325 4670 : NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "RemoveProgressHandler failed!");
326 : #ifdef DEBUG
327 4670 : if (NS_SUCCEEDED(rv)) {
328 4670 : NS_ASSERTION(SameCOMIdentity(handler, static_cast<nsIRunnable*>(this)),
329 : "Mismatch!");
330 : }
331 : #endif
332 : }
333 :
334 4670 : return NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL);
335 : }
336 :
337 : NS_IMETHODIMP
338 79 : AsyncConnectionHelper::OnProgress(mozIStorageConnection* aConnection,
339 : bool* _retval)
340 : {
341 79 : if (mDatabase && mDatabase->IsInvalidated()) {
342 : // Someone is trying to delete the database file. Exit lightningfast!
343 0 : *_retval = true;
344 0 : return NS_OK;
345 : }
346 :
347 79 : if (mOldProgressHandler) {
348 0 : return mOldProgressHandler->OnProgress(aConnection, _retval);
349 : }
350 :
351 79 : *_retval = false;
352 79 : return NS_OK;
353 : }
354 :
355 : nsresult
356 4670 : AsyncConnectionHelper::Dispatch(nsIEventTarget* aDatabaseThread)
357 : {
358 : #ifdef DEBUG
359 4670 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
360 : {
361 : bool sameThread;
362 4670 : nsresult rv = aDatabaseThread->IsOnCurrentThread(&sameThread);
363 4670 : NS_ASSERTION(NS_SUCCEEDED(rv), "IsOnCurrentThread failed!");
364 4670 : NS_ASSERTION(!sameThread, "Dispatching to main thread not supported!");
365 : }
366 : #endif
367 :
368 4670 : nsresult rv = Init();
369 4670 : if (NS_FAILED(rv)) {
370 0 : return rv;
371 : }
372 :
373 4670 : rv = aDatabaseThread->Dispatch(this, NS_DISPATCH_NORMAL);
374 4670 : NS_ENSURE_SUCCESS(rv, rv);
375 :
376 4670 : if (mTransaction) {
377 4670 : mTransaction->OnNewRequest();
378 : }
379 :
380 4670 : mDispatched = true;
381 :
382 4670 : return NS_OK;
383 : }
384 :
385 : nsresult
386 4670 : AsyncConnectionHelper::DispatchToTransactionPool()
387 : {
388 4670 : NS_ASSERTION(mTransaction, "Only ok to call this with a transaction!");
389 4670 : TransactionPoolEventTarget target(mTransaction);
390 4670 : return Dispatch(&target);
391 : }
392 :
393 : // static
394 : IDBTransaction*
395 9637 : AsyncConnectionHelper::GetCurrentTransaction()
396 : {
397 9637 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
398 :
399 9637 : return gCurrentTransaction;
400 : }
401 :
402 : nsresult
403 4599 : AsyncConnectionHelper::Init()
404 : {
405 4599 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
406 :
407 4599 : return NS_OK;
408 : }
409 :
410 : already_AddRefed<nsDOMEvent>
411 4136 : AsyncConnectionHelper::CreateSuccessEvent()
412 : {
413 4136 : return CreateGenericEvent(NS_LITERAL_STRING(SUCCESS_EVT_STR),
414 4136 : eDoesNotBubble, eNotCancelable);
415 : }
416 :
417 : nsresult
418 4207 : AsyncConnectionHelper::OnSuccess()
419 : {
420 4207 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
421 4207 : NS_ASSERTION(mRequest, "Null request!");
422 :
423 8414 : nsRefPtr<nsDOMEvent> event = CreateSuccessEvent();
424 4207 : if (!event) {
425 0 : NS_ERROR("Failed to create event!");
426 0 : return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
427 : }
428 :
429 : bool dummy;
430 4207 : nsresult rv = mRequest->DispatchEvent(event, &dummy);
431 4207 : NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
432 :
433 4207 : nsEvent* internalEvent = event->GetInternalNSEvent();
434 4207 : NS_ASSERTION(internalEvent, "This should never be null!");
435 :
436 4207 : NS_ASSERTION(!mTransaction ||
437 : mTransaction->IsOpen() ||
438 : mTransaction->IsAborted(),
439 : "How else can this be closed?!");
440 :
441 4207 : if ((internalEvent->flags & NS_EVENT_FLAG_EXCEPTION_THROWN) &&
442 0 : mTransaction &&
443 0 : mTransaction->IsOpen()) {
444 0 : rv = mTransaction->Abort();
445 0 : NS_ENSURE_SUCCESS(rv, rv);
446 : }
447 :
448 4207 : return NS_OK;
449 : }
450 :
451 : void
452 30 : AsyncConnectionHelper::OnError()
453 : {
454 30 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
455 30 : NS_ASSERTION(mRequest, "Null request!");
456 :
457 : // Make an error event and fire it at the target.
458 : nsRefPtr<nsDOMEvent> event =
459 30 : CreateGenericEvent(NS_LITERAL_STRING(ERROR_EVT_STR), eDoesBubble,
460 60 : eCancelable);
461 30 : if (!event) {
462 0 : NS_ERROR("Failed to create event!");
463 : return;
464 : }
465 :
466 : bool doDefault;
467 30 : nsresult rv = mRequest->DispatchEvent(event, &doDefault);
468 30 : if (NS_SUCCEEDED(rv)) {
469 30 : NS_ASSERTION(!mTransaction ||
470 : mTransaction->IsOpen() ||
471 : mTransaction->IsAborted(),
472 : "How else can this be closed?!");
473 :
474 30 : if (doDefault &&
475 0 : mTransaction &&
476 0 : mTransaction->IsOpen() &&
477 0 : NS_FAILED(mTransaction->Abort())) {
478 0 : NS_WARNING("Failed to abort transaction!");
479 : }
480 : }
481 : else {
482 0 : NS_WARNING("DispatchEvent failed!");
483 : }
484 : }
485 :
486 : nsresult
487 10 : AsyncConnectionHelper::GetSuccessResult(JSContext* aCx,
488 : jsval* aVal)
489 : {
490 10 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
491 :
492 10 : *aVal = JSVAL_VOID;
493 10 : return NS_OK;
494 : }
495 :
496 : void
497 4670 : AsyncConnectionHelper::ReleaseMainThreadObjects()
498 : {
499 4670 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
500 :
501 4670 : mDatabase = nsnull;
502 4670 : mTransaction = nsnull;
503 :
504 4670 : HelperBase::ReleaseMainThreadObjects();
505 4670 : }
506 :
507 : // static
508 : nsresult
509 29 : AsyncConnectionHelper::ConvertCloneReadInfosToArray(
510 : JSContext* aCx,
511 : nsTArray<StructuredCloneReadInfo>& aReadInfos,
512 : jsval* aResult)
513 : {
514 29 : NS_ASSERTION(aCx, "Null context!");
515 29 : NS_ASSERTION(aResult, "Null pointer!");
516 :
517 58 : JSAutoRequest ar(aCx);
518 :
519 29 : nsresult rv = ConvertCloneReadInfosToArrayInternal(aCx, aReadInfos, aResult);
520 :
521 172 : for (PRUint32 index = 0; index < aReadInfos.Length(); index++) {
522 143 : aReadInfos[index].mCloneBuffer.clear();
523 : }
524 29 : aReadInfos.Clear();
525 :
526 29 : return rv;
527 : }
528 :
529 : NS_IMETHODIMP_(nsrefcnt)
530 0 : TransactionPoolEventTarget::AddRef()
531 : {
532 0 : NS_NOTREACHED("Don't call me!");
533 0 : return 2;
534 : }
535 :
536 : NS_IMETHODIMP_(nsrefcnt)
537 0 : TransactionPoolEventTarget::Release()
538 : {
539 0 : NS_NOTREACHED("Don't call me!");
540 0 : return 1;
541 : }
542 :
543 0 : NS_IMPL_QUERY_INTERFACE1(TransactionPoolEventTarget, nsIEventTarget)
544 :
545 : NS_IMETHODIMP
546 4670 : TransactionPoolEventTarget::Dispatch(nsIRunnable* aRunnable,
547 : PRUint32 aFlags)
548 : {
549 4670 : NS_ASSERTION(aRunnable, "Null pointer!");
550 4670 : NS_ASSERTION(aFlags == NS_DISPATCH_NORMAL, "Unsupported!");
551 :
552 4670 : TransactionThreadPool* pool = TransactionThreadPool::GetOrCreate();
553 4670 : return pool->Dispatch(mTransaction, aRunnable, false, nsnull);
554 : }
555 :
556 : NS_IMETHODIMP
557 4670 : TransactionPoolEventTarget::IsOnCurrentThread(bool* aResult)
558 : {
559 4670 : *aResult = false;
560 4670 : return NS_OK;
561 : }
|