IDBTransaction.cpp (32846B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=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/. */ 6 7 #include "IDBTransaction.h" 8 9 #include "BackgroundChildImpl.h" 10 #include "IDBDatabase.h" 11 #include "IDBEvents.h" 12 #include "IDBObjectStore.h" 13 #include "IDBRequest.h" 14 #include "ProfilerHelpers.h" 15 #include "ReportInternalError.h" 16 #include "ThreadLocal.h" 17 #include "mozilla/ErrorResult.h" 18 #include "mozilla/EventDispatcher.h" 19 #include "mozilla/HoldDropJSObjects.h" 20 #include "mozilla/ScopeExit.h" 21 #include "mozilla/dom/DOMException.h" 22 #include "mozilla/dom/DOMStringList.h" 23 #include "mozilla/dom/WorkerPrivate.h" 24 #include "mozilla/dom/WorkerRef.h" 25 #include "mozilla/ipc/BackgroundChild.h" 26 #include "nsPIDOMWindow.h" 27 #include "nsQueryObject.h" 28 #include "nsServiceManagerUtils.h" 29 #include "nsTHashtable.h" 30 31 // Include this last to avoid path problems on Windows. 32 #include "ActorsChild.h" 33 34 namespace { 35 using namespace mozilla::dom::indexedDB; 36 using namespace mozilla::ipc; 37 38 // TODO: Move this to xpcom/ds. 39 template <typename T, typename Range, typename Transformation> 40 nsTHashtable<T> TransformToHashtable(const Range& aRange, 41 const Transformation& aTransformation) { 42 // TODO: Determining the size of the range is not syntactically necessary (and 43 // requires random access iterators if expressed this way). It is a 44 // performance optimization. We could resort to std::distance to support any 45 // iterator category, but this would lead to a double iteration of the range 46 // in case of non-random-access iterators. It is hard to determine in general 47 // if double iteration or reallocation is worse. 48 auto res = nsTHashtable<T>(aRange.cend() - aRange.cbegin()); 49 // TOOD: std::transform could be used if nsTHashtable had an insert_iterator, 50 // and this would also allow a more generic version not depending on 51 // nsTHashtable at all. 52 for (const auto& item : aRange) { 53 res.PutEntry(aTransformation(item)); 54 } 55 return res; 56 } 57 58 ThreadLocal* GetIndexedDBThreadLocal() { 59 BackgroundChildImpl::ThreadLocal* const threadLocal = 60 BackgroundChildImpl::GetThreadLocalForCurrentThread(); 61 MOZ_ASSERT(threadLocal); 62 63 ThreadLocal* idbThreadLocal = threadLocal->mIndexedDBThreadLocal.get(); 64 MOZ_ASSERT(idbThreadLocal); 65 66 return idbThreadLocal; 67 } 68 } // namespace 69 70 namespace mozilla::dom { 71 72 using namespace mozilla::dom::indexedDB; 73 using namespace mozilla::ipc; 74 75 bool IDBTransaction::HasTransactionChild() const { 76 return (mMode == Mode::VersionChange 77 ? static_cast<void*>( 78 mBackgroundActor.mVersionChangeBackgroundActor) 79 : mBackgroundActor.mNormalBackgroundActor) != nullptr; 80 } 81 82 template <typename Func> 83 auto IDBTransaction::DoWithTransactionChild(const Func& aFunc) const { 84 MOZ_ASSERT(HasTransactionChild()); 85 return mMode == Mode::VersionChange 86 ? aFunc(*mBackgroundActor.mVersionChangeBackgroundActor) 87 : aFunc(*mBackgroundActor.mNormalBackgroundActor); 88 } 89 90 IDBTransaction::IDBTransaction(IDBDatabase* const aDatabase, 91 const nsTArray<nsString>& aObjectStoreNames, 92 const Mode aMode, const Durability aDurability, 93 JSCallingLocation&& aCallerLocation, 94 CreatedFromFactoryFunction /*aDummy*/) 95 : DOMEventTargetHelper(aDatabase), 96 mDatabase(aDatabase), 97 mObjectStoreNames(aObjectStoreNames.Clone()), 98 mLoggingSerialNumber(GetIndexedDBThreadLocal()->NextTransactionSN(aMode)), 99 mNextObjectStoreId(0), 100 mNextIndexId(0), 101 mNextRequestId(0), 102 mAbortCode(NS_OK), 103 mPendingRequestCount(0), 104 mCallerLocation(std::move(aCallerLocation)), 105 mMode(aMode), 106 mDurability(aDurability), 107 mRegistered(false), 108 mNotedActiveTransaction(false) { 109 MOZ_ASSERT(aDatabase); 110 aDatabase->AssertIsOnOwningThread(); 111 112 // This also nulls mBackgroundActor.mVersionChangeBackgroundActor, so this is 113 // valid also for mMode == Mode::VersionChange. 114 mBackgroundActor.mNormalBackgroundActor = nullptr; 115 116 #ifdef DEBUG 117 if (!aObjectStoreNames.IsEmpty()) { 118 // Make sure the array is properly sorted. 119 MOZ_ASSERT( 120 std::is_sorted(aObjectStoreNames.cbegin(), aObjectStoreNames.cend())); 121 122 // Make sure there are no duplicates in our objectStore names. 123 MOZ_ASSERT(aObjectStoreNames.cend() == 124 std::adjacent_find(aObjectStoreNames.cbegin(), 125 aObjectStoreNames.cend())); 126 } 127 #endif 128 129 mozilla::HoldJSObjects(this); 130 } 131 132 IDBTransaction::~IDBTransaction() { 133 AssertIsOnOwningThread(); 134 MOZ_ASSERT(!mPendingRequestCount); 135 MOZ_ASSERT(mReadyState != ReadyState::Active); 136 MOZ_ASSERT(mReadyState != ReadyState::Inactive); 137 MOZ_ASSERT(mReadyState != ReadyState::Committing); 138 MOZ_ASSERT(!mNotedActiveTransaction); 139 MOZ_ASSERT(mSentCommitOrAbort); 140 MOZ_ASSERT_IF(HasTransactionChild(), mFiredCompleteOrAbort); 141 142 if (mRegistered) { 143 mDatabase->UnregisterTransaction(*this); 144 #ifdef DEBUG 145 mRegistered = false; 146 #endif 147 } 148 149 if (HasTransactionChild()) { 150 if (mMode == Mode::VersionChange) { 151 mBackgroundActor.mVersionChangeBackgroundActor->SendDeleteMeInternal( 152 /* aFailedConstructor */ false); 153 } else { 154 mBackgroundActor.mNormalBackgroundActor->SendDeleteMeInternal(); 155 } 156 } 157 MOZ_ASSERT(!HasTransactionChild(), 158 "SendDeleteMeInternal should have cleared!"); 159 160 mozilla::DropJSObjects(this); 161 } 162 163 // static 164 SafeRefPtr<IDBTransaction> IDBTransaction::CreateVersionChange( 165 IDBDatabase* const aDatabase, 166 BackgroundVersionChangeTransactionChild* const aActor, 167 const NotNull<IDBOpenDBRequest*> aOpenRequest, 168 const int64_t aNextObjectStoreId, const int64_t aNextIndexId) { 169 MOZ_ASSERT(aDatabase); 170 aDatabase->AssertIsOnOwningThread(); 171 MOZ_ASSERT(aActor); 172 MOZ_ASSERT(aNextObjectStoreId > 0); 173 MOZ_ASSERT(aNextIndexId > 0); 174 175 const nsTArray<nsString> emptyObjectStoreNames; 176 177 // XXX: What should we have as durability hint here? 178 auto transaction = MakeSafeRefPtr<IDBTransaction>( 179 aDatabase, emptyObjectStoreNames, Mode::VersionChange, 180 Durability::Default, JSCallingLocation(aOpenRequest->GetCallerLocation()), 181 CreatedFromFactoryFunction{}); 182 183 transaction->NoteActiveTransaction(); 184 185 transaction->mBackgroundActor.mVersionChangeBackgroundActor = aActor; 186 transaction->mNextObjectStoreId = aNextObjectStoreId; 187 transaction->mNextIndexId = aNextIndexId; 188 189 aDatabase->RegisterTransaction(*transaction); 190 transaction->mRegistered = true; 191 192 return transaction; 193 } 194 195 // static 196 SafeRefPtr<IDBTransaction> IDBTransaction::Create( 197 JSContext* const aCx, IDBDatabase* const aDatabase, 198 const nsTArray<nsString>& aObjectStoreNames, const Mode aMode, 199 const Durability aDurability) { 200 MOZ_ASSERT(aDatabase); 201 aDatabase->AssertIsOnOwningThread(); 202 MOZ_ASSERT(!aObjectStoreNames.IsEmpty()); 203 MOZ_ASSERT(aMode == Mode::ReadOnly || aMode == Mode::ReadWrite || 204 aMode == Mode::ReadWriteFlush || aMode == Mode::Cleanup); 205 206 auto transaction = MakeSafeRefPtr<IDBTransaction>( 207 aDatabase, aObjectStoreNames, aMode, aDurability, 208 JSCallingLocation::Get(aCx), CreatedFromFactoryFunction{}); 209 210 if (!NS_IsMainThread()) { 211 WorkerPrivate* const workerPrivate = GetCurrentThreadWorkerPrivate(); 212 MOZ_ASSERT(workerPrivate); 213 214 workerPrivate->AssertIsOnWorkerThread(); 215 216 RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create( 217 workerPrivate, "IDBTransaction", 218 [transaction = AsRefPtr(transaction.clonePtr())]() { 219 transaction->AssertIsOnOwningThread(); 220 if (!transaction->IsCommittingOrFinished()) { 221 IDB_REPORT_INTERNAL_ERR(); 222 transaction->AbortInternal(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, 223 nullptr); 224 } 225 }); 226 if (NS_WARN_IF(!workerRef)) { 227 #ifdef DEBUG 228 // Silence the destructor assertions if we never made this object live. 229 transaction->mReadyState = ReadyState::Finished; 230 transaction->mSentCommitOrAbort.Flip(); 231 #endif 232 return nullptr; 233 } 234 235 transaction->mWorkerRef = std::move(workerRef); 236 } 237 238 nsCOMPtr<nsIRunnable> runnable = 239 do_QueryObject(transaction.unsafeGetRawPtr()); 240 nsContentUtils::AddPendingIDBTransaction(runnable.forget()); 241 242 aDatabase->RegisterTransaction(*transaction); 243 transaction->mRegistered = true; 244 245 return transaction; 246 } 247 248 // static 249 Maybe<IDBTransaction&> IDBTransaction::MaybeCurrent() { 250 using namespace mozilla::ipc; 251 252 MOZ_ASSERT(BackgroundChild::GetForCurrentThread()); 253 254 return GetIndexedDBThreadLocal()->MaybeCurrentTransactionRef(); 255 } 256 257 #ifdef DEBUG 258 259 void IDBTransaction::AssertIsOnOwningThread() const { 260 MOZ_ASSERT(mDatabase); 261 mDatabase->AssertIsOnOwningThread(); 262 } 263 264 #endif // DEBUG 265 266 void IDBTransaction::SetBackgroundActor( 267 indexedDB::BackgroundTransactionChild* const aBackgroundActor) { 268 AssertIsOnOwningThread(); 269 MOZ_ASSERT(aBackgroundActor); 270 MOZ_ASSERT(!mBackgroundActor.mNormalBackgroundActor); 271 MOZ_ASSERT(mMode != Mode::VersionChange); 272 273 NoteActiveTransaction(); 274 275 mBackgroundActor.mNormalBackgroundActor = aBackgroundActor; 276 } 277 278 BackgroundRequestChild* IDBTransaction::StartRequest( 279 MovingNotNull<RefPtr<mozilla::dom::IDBRequest> > aRequest, 280 const RequestParams& aParams) { 281 AssertIsOnOwningThread(); 282 MOZ_ASSERT(aParams.type() != RequestParams::T__None); 283 284 BackgroundRequestChild* const actor = 285 new BackgroundRequestChild(std::move(aRequest)); 286 287 DoWithTransactionChild([this, actor, &aParams](auto& transactionChild) { 288 transactionChild.SendPBackgroundIDBRequestConstructor( 289 actor, NextRequestId(), aParams); 290 }); 291 292 // Balanced in BackgroundRequestChild::Recv__delete__(). 293 OnNewRequest(); 294 295 return actor; 296 } 297 298 void IDBTransaction::OpenCursor(PBackgroundIDBCursorChild& aBackgroundActor, 299 const OpenCursorParams& aParams) { 300 AssertIsOnOwningThread(); 301 MOZ_ASSERT(aParams.type() != OpenCursorParams::T__None); 302 303 DoWithTransactionChild([this, &aBackgroundActor, &aParams](auto& actor) { 304 actor.SendPBackgroundIDBCursorConstructor(&aBackgroundActor, 305 NextRequestId(), aParams); 306 }); 307 308 // Balanced in BackgroundCursorChild::RecvResponse(). 309 OnNewRequest(); 310 } 311 312 void IDBTransaction::RefreshSpec(const bool aMayDelete) { 313 AssertIsOnOwningThread(); 314 315 for (auto& objectStore : mObjectStores) { 316 objectStore->RefreshSpec(aMayDelete); 317 } 318 319 for (auto& objectStore : mDeletedObjectStores) { 320 objectStore->RefreshSpec(false); 321 } 322 } 323 324 void IDBTransaction::OnNewRequest() { 325 AssertIsOnOwningThread(); 326 327 if (!mPendingRequestCount) { 328 MOZ_ASSERT(ReadyState::Active == mReadyState); 329 mStarted.Flip(); 330 } 331 332 ++mPendingRequestCount; 333 } 334 335 void IDBTransaction::OnRequestFinished( 336 const bool aRequestCompletedSuccessfully) { 337 AssertIsOnOwningThread(); 338 MOZ_ASSERT(mReadyState != ReadyState::Active); 339 MOZ_ASSERT_IF(mReadyState == ReadyState::Finished, !NS_SUCCEEDED(mAbortCode)); 340 MOZ_ASSERT(mPendingRequestCount); 341 342 --mPendingRequestCount; 343 344 if (!mPendingRequestCount) { 345 if (mSentCommitOrAbort) { 346 return; 347 } 348 349 if (aRequestCompletedSuccessfully) { 350 if (mReadyState == ReadyState::Inactive) { 351 mReadyState = ReadyState::Committing; 352 } 353 354 if (NS_SUCCEEDED(mAbortCode)) { 355 SendCommit(true); 356 } else { 357 SendAbort(mAbortCode); 358 } 359 } else { 360 // Don't try to send any more messages to the parent if the request actor 361 // was killed. Set our state accordingly to Finished. 362 mReadyState = ReadyState::Finished; 363 mSentCommitOrAbort.Flip(); 364 IDB_LOG_MARK_CHILD_TRANSACTION( 365 "Request actor was killed, transaction will be aborted", 366 "IDBTransaction abort", LoggingSerialNumber()); 367 } 368 } 369 } 370 371 void IDBTransaction::SendCommit(const bool aAutoCommit) { 372 AssertIsOnOwningThread(); 373 MOZ_ASSERT(NS_SUCCEEDED(mAbortCode)); 374 MOZ_ASSERT(IsCommittingOrFinished()); 375 376 // Don't do this in the macro because we always need to increment the serial 377 // number to keep in sync with the parent. 378 const uint64_t requestSerialNumber = IDBRequest::NextSerialNumber(); 379 380 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST( 381 "Committing transaction (%s)", "IDBTransaction commit (%s)", 382 LoggingSerialNumber(), requestSerialNumber, 383 aAutoCommit ? "automatically" : "explicitly"); 384 385 const int64_t requestId = NextRequestId(); 386 387 const auto lastRequestId = [this, aAutoCommit, 388 requestId]() -> Maybe<decltype(requestId)> { 389 if (aAutoCommit) { 390 return Nothing(); 391 } 392 393 // In case of an explicit commit, we need to note the id of the last 394 // request to check if a request submitted before the commit request 395 // failed. If we are currently in an event handler for a request on this 396 // transaction, ignore this request. This is used to synchronize the 397 // transaction's committing state with the parent side, to abort the 398 // transaction in case of a request resulting in an error (see 399 // https://w3c.github.io/IndexedDB/#async-execute-request, step 5.3.). With 400 // automatic commit, this is not necessary, as the transaction's state will 401 // only be set to committing after the last request completed. 402 const auto maybeCurrentTransaction = 403 BackgroundChildImpl::GetThreadLocalForCurrentThread() 404 ->mIndexedDBThreadLocal->MaybeCurrentTransactionRef(); 405 const bool dispatchingEventForThisTransaction = 406 maybeCurrentTransaction && &maybeCurrentTransaction.ref() == this; 407 408 return Some(requestId 409 ? (requestId - (dispatchingEventForThisTransaction ? 0 : 1)) 410 : 0); 411 }(); 412 413 DoWithTransactionChild( 414 [lastRequestId](auto& actor) { actor.SendCommit(lastRequestId); }); 415 416 mSentCommitOrAbort.Flip(); 417 } 418 419 void IDBTransaction::SendAbort(const nsresult aResultCode) { 420 AssertIsOnOwningThread(); 421 MOZ_ASSERT(NS_FAILED(aResultCode)); 422 MOZ_ASSERT(IsCommittingOrFinished()); 423 424 // Don't do this in the macro because we always need to increment the serial 425 // number to keep in sync with the parent. 426 const uint64_t requestSerialNumber = IDBRequest::NextSerialNumber(); 427 428 IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST( 429 "Aborting transaction with result 0x%" PRIx32, 430 "IDBTransaction abort (0x%" PRIx32 ")", LoggingSerialNumber(), 431 requestSerialNumber, static_cast<uint32_t>(aResultCode)); 432 433 DoWithTransactionChild( 434 [aResultCode](auto& actor) { actor.SendAbort(aResultCode); }); 435 436 mSentCommitOrAbort.Flip(); 437 } 438 439 void IDBTransaction::NoteActiveTransaction() { 440 AssertIsOnOwningThread(); 441 MOZ_ASSERT(!mNotedActiveTransaction); 442 443 mDatabase->NoteActiveTransaction(); 444 mNotedActiveTransaction = true; 445 } 446 447 void IDBTransaction::MaybeNoteInactiveTransaction() { 448 AssertIsOnOwningThread(); 449 450 if (mNotedActiveTransaction) { 451 mDatabase->NoteInactiveTransaction(); 452 mNotedActiveTransaction = false; 453 } 454 } 455 456 RefPtr<IDBObjectStore> IDBTransaction::CreateObjectStore( 457 ObjectStoreSpec& aSpec) { 458 AssertIsOnOwningThread(); 459 MOZ_ASSERT(aSpec.metadata().id()); 460 MOZ_ASSERT(Mode::VersionChange == mMode); 461 MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor); 462 MOZ_ASSERT(IsActive()); 463 464 #ifdef DEBUG 465 { 466 // TODO: Bind name outside of lambda capture as a workaround for GCC 7 bug 467 // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66735. 468 const auto& name = aSpec.metadata().name(); 469 // TODO: Use #ifdef and local variable as a workaround for Bug 1583449. 470 const bool objectStoreNameDoesNotYetExist = 471 std::all_of(mObjectStores.cbegin(), mObjectStores.cend(), 472 [&name](const auto& objectStore) { 473 return objectStore->Name() != name; 474 }); 475 MOZ_ASSERT(objectStoreNameDoesNotYetExist); 476 } 477 #endif 478 479 MOZ_ALWAYS_TRUE( 480 mBackgroundActor.mVersionChangeBackgroundActor->SendCreateObjectStore( 481 aSpec.metadata())); 482 483 RefPtr<IDBObjectStore> objectStore = IDBObjectStore::Create( 484 SafeRefPtr{this, AcquireStrongRefFromRawPtr{}}, aSpec); 485 MOZ_ASSERT(objectStore); 486 487 mObjectStores.AppendElement(objectStore); 488 489 return objectStore; 490 } 491 492 void IDBTransaction::DeleteObjectStore(const int64_t aObjectStoreId) { 493 AssertIsOnOwningThread(); 494 MOZ_ASSERT(aObjectStoreId); 495 MOZ_ASSERT(Mode::VersionChange == mMode); 496 MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor); 497 MOZ_ASSERT(IsActive()); 498 499 MOZ_ALWAYS_TRUE( 500 mBackgroundActor.mVersionChangeBackgroundActor->SendDeleteObjectStore( 501 aObjectStoreId)); 502 503 const auto foundIt = 504 std::find_if(mObjectStores.begin(), mObjectStores.end(), 505 [aObjectStoreId](const auto& objectStore) { 506 return objectStore->Id() == aObjectStoreId; 507 }); 508 if (foundIt != mObjectStores.end()) { 509 auto& objectStore = *foundIt; 510 objectStore->NoteDeletion(); 511 512 RefPtr<IDBObjectStore>* deletedObjectStore = 513 mDeletedObjectStores.AppendElement(); 514 deletedObjectStore->swap(objectStore); 515 516 mObjectStores.RemoveElementAt(foundIt); 517 } 518 } 519 520 void IDBTransaction::RenameObjectStore(const int64_t aObjectStoreId, 521 const nsAString& aName) const { 522 AssertIsOnOwningThread(); 523 MOZ_ASSERT(aObjectStoreId); 524 MOZ_ASSERT(Mode::VersionChange == mMode); 525 MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor); 526 MOZ_ASSERT(IsActive()); 527 528 MOZ_ALWAYS_TRUE( 529 mBackgroundActor.mVersionChangeBackgroundActor->SendRenameObjectStore( 530 aObjectStoreId, nsString(aName))); 531 } 532 533 void IDBTransaction::CreateIndex( 534 IDBObjectStore* const aObjectStore, 535 const indexedDB::IndexMetadata& aMetadata) const { 536 AssertIsOnOwningThread(); 537 MOZ_ASSERT(aObjectStore); 538 MOZ_ASSERT(aMetadata.id()); 539 MOZ_ASSERT(Mode::VersionChange == mMode); 540 MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor); 541 MOZ_ASSERT(IsActive()); 542 543 MOZ_ALWAYS_TRUE( 544 mBackgroundActor.mVersionChangeBackgroundActor->SendCreateIndex( 545 aObjectStore->Id(), aMetadata)); 546 } 547 548 void IDBTransaction::DeleteIndex(IDBObjectStore* const aObjectStore, 549 const int64_t aIndexId) const { 550 AssertIsOnOwningThread(); 551 MOZ_ASSERT(aObjectStore); 552 MOZ_ASSERT(aIndexId); 553 MOZ_ASSERT(Mode::VersionChange == mMode); 554 MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor); 555 MOZ_ASSERT(IsActive()); 556 557 MOZ_ALWAYS_TRUE( 558 mBackgroundActor.mVersionChangeBackgroundActor->SendDeleteIndex( 559 aObjectStore->Id(), aIndexId)); 560 } 561 562 void IDBTransaction::RenameIndex(IDBObjectStore* const aObjectStore, 563 const int64_t aIndexId, 564 const nsAString& aName) const { 565 AssertIsOnOwningThread(); 566 MOZ_ASSERT(aObjectStore); 567 MOZ_ASSERT(aIndexId); 568 MOZ_ASSERT(Mode::VersionChange == mMode); 569 MOZ_ASSERT(mBackgroundActor.mVersionChangeBackgroundActor); 570 MOZ_ASSERT(IsActive()); 571 572 MOZ_ALWAYS_TRUE( 573 mBackgroundActor.mVersionChangeBackgroundActor->SendRenameIndex( 574 aObjectStore->Id(), aIndexId, nsString(aName))); 575 } 576 577 void IDBTransaction::AbortInternal(const nsresult aAbortCode, 578 RefPtr<DOMException> aError) { 579 AssertIsOnOwningThread(); 580 MOZ_ASSERT(NS_FAILED(aAbortCode)); 581 MOZ_ASSERT(!IsCommittingOrFinished()); 582 583 const bool isVersionChange = mMode == Mode::VersionChange; 584 const bool needToSendAbort = !mStarted; 585 586 mAbortCode = aAbortCode; 587 mReadyState = ReadyState::Finished; 588 mError = std::move(aError); 589 590 if (isVersionChange) { 591 // If a version change transaction is aborted, we must revert the world 592 // back to its previous state unless we're being invalidated after the 593 // transaction already completed. 594 if (!mDatabase->IsInvalidated()) { 595 mDatabase->RevertToPreviousState(); 596 } 597 598 // We do the reversion only for the mObjectStores/mDeletedObjectStores but 599 // not for the mIndexes/mDeletedIndexes of each IDBObjectStore because it's 600 // time-consuming(O(m*n)) and mIndexes/mDeletedIndexes won't be used anymore 601 // in IDBObjectStore::(Create|Delete)Index() and IDBObjectStore::Index() in 602 // which all the executions are returned earlier by 603 // !transaction->IsActive(). 604 605 const nsTArray<ObjectStoreSpec>& specArray = 606 mDatabase->Spec()->objectStores(); 607 608 if (specArray.IsEmpty()) { 609 // This case is specially handled as a performance optimization, it is 610 // equivalent to the else block. 611 mObjectStores.Clear(); 612 } else { 613 const auto validIds = TransformToHashtable<nsUint64HashKey>( 614 specArray, [](const auto& spec) { 615 const int64_t objectStoreId = spec.metadata().id(); 616 MOZ_ASSERT(objectStoreId); 617 return static_cast<uint64_t>(objectStoreId); 618 }); 619 620 mObjectStores.RemoveLastElements( 621 mObjectStores.end() - 622 std::remove_if(mObjectStores.begin(), mObjectStores.end(), 623 [&validIds](const auto& objectStore) { 624 return !validIds.Contains( 625 uint64_t(objectStore->Id())); 626 })); 627 628 std::copy_if(std::make_move_iterator(mDeletedObjectStores.begin()), 629 std::make_move_iterator(mDeletedObjectStores.end()), 630 MakeBackInserter(mObjectStores), 631 [&validIds](const auto& deletedObjectStore) { 632 const int64_t objectStoreId = deletedObjectStore->Id(); 633 MOZ_ASSERT(objectStoreId); 634 return validIds.Contains(uint64_t(objectStoreId)); 635 }); 636 } 637 mDeletedObjectStores.Clear(); 638 } 639 640 // Fire the abort event if there are no outstanding requests. Otherwise the 641 // abort event will be fired when all outstanding requests finish. 642 if (needToSendAbort) { 643 SendAbort(aAbortCode); 644 } 645 646 if (isVersionChange) { 647 mDatabase->Close(); 648 } 649 } 650 651 void IDBTransaction::Abort(IDBRequest* const aRequest) { 652 AssertIsOnOwningThread(); 653 MOZ_ASSERT(aRequest); 654 655 if (IsCommittingOrFinished()) { 656 // Already started (and maybe finished) the commit or abort so there is 657 // nothing to do here. 658 return; 659 } 660 661 ErrorResult rv; 662 RefPtr<DOMException> error = aRequest->GetError(rv); 663 664 // TODO: Do we deliberately ignore rv here? Isn't there a static analysis that 665 // prevents that? 666 667 AbortInternal(aRequest->GetErrorCode(), std::move(error)); 668 } 669 670 void IDBTransaction::Abort(const nsresult aErrorCode) { 671 AssertIsOnOwningThread(); 672 673 if (IsCommittingOrFinished()) { 674 // Already started (and maybe finished) the commit or abort so there is 675 // nothing to do here. 676 return; 677 } 678 679 AbortInternal(aErrorCode, DOMException::Create(aErrorCode)); 680 } 681 682 // Specified by https://w3c.github.io/IndexedDB/#dom-idbtransaction-abort. 683 void IDBTransaction::Abort(ErrorResult& aRv) { 684 AssertIsOnOwningThread(); 685 686 if (IsCommittingOrFinished()) { 687 aRv = NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR; 688 return; 689 } 690 691 mReadyState = ReadyState::Inactive; 692 693 AbortInternal(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR, nullptr); 694 695 mAbortedByScript.Flip(); 696 } 697 698 // Specified by https://w3c.github.io/IndexedDB/#dom-idbtransaction-commit. 699 void IDBTransaction::Commit(ErrorResult& aRv) { 700 AssertIsOnOwningThread(); 701 702 if (mReadyState != ReadyState::Active || !mNotedActiveTransaction) { 703 aRv = NS_ERROR_DOM_INVALID_STATE_ERR; 704 return; 705 } 706 707 MOZ_ASSERT(!mSentCommitOrAbort); 708 709 MOZ_ASSERT(mReadyState == ReadyState::Active); 710 mReadyState = ReadyState::Committing; 711 if (NS_WARN_IF(NS_FAILED(mAbortCode))) { 712 SendAbort(mAbortCode); 713 aRv = mAbortCode; 714 return; 715 } 716 717 #ifdef DEBUG 718 mWasExplicitlyCommitted.Flip(); 719 #endif 720 721 SendCommit(false); 722 } 723 724 void IDBTransaction::FireCompleteOrAbortEvents(const nsresult aResult) { 725 AssertIsOnOwningThread(); 726 MOZ_ASSERT(!mFiredCompleteOrAbort); 727 728 mReadyState = ReadyState::Finished; 729 730 #ifdef DEBUG 731 mFiredCompleteOrAbort.Flip(); 732 #endif 733 734 // Make sure we drop the WorkerRef when this function completes. 735 const auto scopeExit = MakeScopeExit([&] { mWorkerRef = nullptr; }); 736 737 RefPtr<Event> event; 738 if (NS_SUCCEEDED(aResult)) { 739 event = CreateGenericEvent(this, nsDependentString(kCompleteEventType), 740 eDoesNotBubble, eNotCancelable); 741 MOZ_ASSERT(event); 742 743 // If we hit this assertion, it probably means transaction object on the 744 // parent process doesn't propagate error properly. 745 MOZ_ASSERT(NS_SUCCEEDED(mAbortCode)); 746 } else { 747 if (aResult == NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR) { 748 mDatabase->SetQuotaExceeded(); 749 } 750 751 if (!mError && !mAbortedByScript) { 752 mError = DOMException::Create(aResult); 753 } 754 755 event = CreateGenericEvent(this, nsDependentString(kAbortEventType), 756 eDoesBubble, eNotCancelable); 757 MOZ_ASSERT(event); 758 759 if (NS_SUCCEEDED(mAbortCode)) { 760 mAbortCode = aResult; 761 } 762 } 763 764 if (NS_SUCCEEDED(mAbortCode)) { 765 IDB_LOG_MARK_CHILD_TRANSACTION("Firing 'complete' event", 766 "IDBTransaction 'complete' event", 767 mLoggingSerialNumber); 768 } else { 769 IDB_LOG_MARK_CHILD_TRANSACTION( 770 "Firing 'abort' event with error 0x%" PRIx32, 771 "IDBTransaction 'abort' event (0x%" PRIx32 ")", mLoggingSerialNumber, 772 static_cast<uint32_t>(mAbortCode)); 773 } 774 775 IgnoredErrorResult rv; 776 DispatchEvent(*event, rv); 777 if (rv.Failed()) { 778 NS_WARNING("DispatchEvent failed!"); 779 } 780 781 // Normally, we note inactive transaction here instead of 782 // IDBTransaction::ClearBackgroundActor() because here is the earliest place 783 // to know that it becomes non-blocking to allow the scheduler to start the 784 // preemption as soon as it can. 785 // Note: If the IDBTransaction object is held by the script, 786 // ClearBackgroundActor() will be done in ~IDBTransaction() until garbage 787 // collected after its window is closed which prevents us to preempt its 788 // window immediately after committed. 789 MaybeNoteInactiveTransaction(); 790 } 791 792 int64_t IDBTransaction::NextObjectStoreId() { 793 AssertIsOnOwningThread(); 794 MOZ_ASSERT(Mode::VersionChange == mMode); 795 796 return mNextObjectStoreId++; 797 } 798 799 int64_t IDBTransaction::NextIndexId() { 800 AssertIsOnOwningThread(); 801 MOZ_ASSERT(Mode::VersionChange == mMode); 802 803 return mNextIndexId++; 804 } 805 806 int64_t IDBTransaction::NextRequestId() { 807 AssertIsOnOwningThread(); 808 809 return mNextRequestId++; 810 } 811 812 void IDBTransaction::InvalidateCursorCaches() { 813 AssertIsOnOwningThread(); 814 815 for (const auto& cursor : mCursors) { 816 cursor->InvalidateCachedResponses(); 817 } 818 } 819 820 void IDBTransaction::RegisterCursor(IDBCursor& aCursor) { 821 AssertIsOnOwningThread(); 822 823 mCursors.AppendElement(WrapNotNullUnchecked(&aCursor)); 824 } 825 826 void IDBTransaction::UnregisterCursor(IDBCursor& aCursor) { 827 AssertIsOnOwningThread(); 828 829 DebugOnly<bool> removed = mCursors.RemoveElement(&aCursor); 830 MOZ_ASSERT(removed); 831 } 832 833 nsIGlobalObject* IDBTransaction::GetParentObject() const { 834 AssertIsOnOwningThread(); 835 836 return mDatabase->GetParentObject(); 837 } 838 839 IDBTransactionMode IDBTransaction::GetMode(ErrorResult& aRv) const { 840 AssertIsOnOwningThread(); 841 842 switch (mMode) { 843 case Mode::ReadOnly: 844 return IDBTransactionMode::Readonly; 845 846 case Mode::ReadWrite: 847 return IDBTransactionMode::Readwrite; 848 849 case Mode::ReadWriteFlush: 850 return IDBTransactionMode::Readwriteflush; 851 852 case Mode::Cleanup: 853 return IDBTransactionMode::Cleanup; 854 855 case Mode::VersionChange: 856 return IDBTransactionMode::Versionchange; 857 858 case Mode::Invalid: 859 default: 860 MOZ_CRASH("Bad mode!"); 861 } 862 } 863 864 IDBTransactionDurability IDBTransaction::GetDurability(ErrorResult& aRv) const { 865 AssertIsOnOwningThread(); 866 867 switch (mDurability) { 868 case Durability::Default: 869 return IDBTransactionDurability::Default; 870 871 case Durability::Strict: 872 return IDBTransactionDurability::Strict; 873 874 case Durability::Relaxed: 875 return IDBTransactionDurability::Relaxed; 876 877 default: 878 MOZ_CRASH("Bad mode!"); 879 } 880 } 881 882 DOMException* IDBTransaction::GetError() const { 883 AssertIsOnOwningThread(); 884 885 return mError; 886 } 887 888 RefPtr<DOMStringList> IDBTransaction::ObjectStoreNames() const { 889 AssertIsOnOwningThread(); 890 891 if (mMode == Mode::VersionChange) { 892 return mDatabase->ObjectStoreNames(); 893 } 894 895 auto list = MakeRefPtr<DOMStringList>(); 896 list->StringArray() = mObjectStoreNames.Clone(); 897 return list; 898 } 899 900 RefPtr<IDBObjectStore> IDBTransaction::ObjectStore(const nsAString& aName, 901 ErrorResult& aRv) { 902 AssertIsOnOwningThread(); 903 904 if (IsCommittingOrFinished()) { 905 aRv.ThrowInvalidStateError("Transaction is already committing or done."); 906 return nullptr; 907 } 908 909 auto* const spec = [this, &aName]() -> ObjectStoreSpec* { 910 if (IDBTransaction::Mode::VersionChange == mMode || 911 mObjectStoreNames.Contains(aName)) { 912 return mDatabase->LookupModifiableObjectStoreSpec( 913 [&aName](const auto& objectStore) { 914 return objectStore.metadata().name() == aName; 915 }); 916 } 917 return nullptr; 918 }(); 919 920 if (!spec) { 921 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR); 922 return nullptr; 923 } 924 925 RefPtr<IDBObjectStore> objectStore; 926 927 const auto foundIt = std::find_if( 928 mObjectStores.cbegin(), mObjectStores.cend(), 929 [desiredId = spec->metadata().id()](const auto& existingObjectStore) { 930 return existingObjectStore->Id() == desiredId; 931 }); 932 if (foundIt != mObjectStores.cend()) { 933 objectStore = *foundIt; 934 } else { 935 objectStore = IDBObjectStore::Create( 936 SafeRefPtr{this, AcquireStrongRefFromRawPtr{}}, *spec); 937 MOZ_ASSERT(objectStore); 938 939 mObjectStores.AppendElement(objectStore); 940 } 941 942 return objectStore; 943 } 944 945 NS_IMPL_ADDREF_INHERITED(IDBTransaction, DOMEventTargetHelper) 946 NS_IMPL_RELEASE_INHERITED(IDBTransaction, DOMEventTargetHelper) 947 948 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IDBTransaction) 949 NS_INTERFACE_MAP_ENTRY(nsIRunnable) 950 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) 951 952 NS_IMPL_CYCLE_COLLECTION_CLASS(IDBTransaction) 953 954 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(IDBTransaction, 955 DOMEventTargetHelper) 956 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDatabase) 957 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mError) 958 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObjectStores) 959 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDeletedObjectStores) 960 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 961 962 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(IDBTransaction, 963 DOMEventTargetHelper) 964 // Don't unlink mDatabase! 965 NS_IMPL_CYCLE_COLLECTION_UNLINK(mError) 966 NS_IMPL_CYCLE_COLLECTION_UNLINK(mObjectStores) 967 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDeletedObjectStores) 968 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 969 970 JSObject* IDBTransaction::WrapObject(JSContext* const aCx, 971 JS::Handle<JSObject*> aGivenProto) { 972 AssertIsOnOwningThread(); 973 974 return IDBTransaction_Binding::Wrap(aCx, this, std::move(aGivenProto)); 975 } 976 977 void IDBTransaction::GetEventTargetParent(EventChainPreVisitor& aVisitor) { 978 AssertIsOnOwningThread(); 979 980 aVisitor.mCanHandle = true; 981 aVisitor.SetParentTarget(mDatabase, false); 982 } 983 984 NS_IMETHODIMP 985 IDBTransaction::Run() { 986 AssertIsOnOwningThread(); 987 988 // TODO: Instead of checking for Finished and Committing states here, we could 989 // remove the transaction from the pending IDB transactions list on 990 // abort/commit. 991 992 if (ReadyState::Finished == mReadyState) { 993 // There are three cases where mReadyState is set to Finished: In 994 // FileCompleteOrAbortEvents, AbortInternal and in CommitIfNotStarted. We 995 // shouldn't get here after CommitIfNotStarted again. 996 MOZ_ASSERT(mFiredCompleteOrAbort || IsAborted()); 997 return NS_OK; 998 } 999 1000 if (ReadyState::Committing == mReadyState) { 1001 MOZ_ASSERT(mSentCommitOrAbort); 1002 return NS_OK; 1003 } 1004 // We're back at the event loop, no longer newborn, so 1005 // return to Inactive state: 1006 // https://w3c.github.io/IndexedDB/#cleanup-indexed-database-transactions. 1007 MOZ_ASSERT(ReadyState::Active == mReadyState); 1008 mReadyState = ReadyState::Inactive; 1009 1010 CommitIfNotStarted(); 1011 1012 return NS_OK; 1013 } 1014 1015 void IDBTransaction::CommitIfNotStarted() { 1016 AssertIsOnOwningThread(); 1017 1018 MOZ_ASSERT(ReadyState::Inactive == mReadyState); 1019 1020 // Maybe commit if there were no requests generated. 1021 if (!mStarted) { 1022 MOZ_ASSERT(!mPendingRequestCount); 1023 mReadyState = ReadyState::Finished; 1024 1025 SendCommit(true); 1026 } 1027 } 1028 1029 } // namespace mozilla::dom