Context.cpp (39422B)
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 "mozilla/dom/cache/Context.h" 8 9 #include "CacheCommon.h" 10 #include "NotifyUtils.h" 11 #include "QuotaClientImpl.h" 12 #include "mozIStorageConnection.h" 13 #include "mozilla/AutoRestore.h" 14 #include "mozilla/Maybe.h" 15 #include "mozilla/StaticPrefs_dom.h" 16 #include "mozilla/dom/SafeRefPtr.h" 17 #include "mozilla/dom/cache/Action.h" 18 #include "mozilla/dom/cache/FileUtils.h" 19 #include "mozilla/dom/cache/Manager.h" 20 #include "mozilla/dom/cache/ManagerId.h" 21 #include "mozilla/dom/quota/Assertions.h" 22 #include "mozilla/dom/quota/ClientDirectoryLock.h" 23 #include "mozilla/dom/quota/ClientDirectoryLockHandle.h" 24 #include "mozilla/dom/quota/PrincipalUtils.h" 25 #include "mozilla/dom/quota/QuotaManager.h" 26 #include "mozilla/dom/quota/ResultExtensions.h" 27 #include "mozilla/dom/quota/ThreadUtils.h" 28 #include "mozilla/ipc/PBackgroundSharedTypes.h" 29 #include "nsIPrincipal.h" 30 #include "nsIRunnable.h" 31 #include "nsIThread.h" 32 #include "nsThreadUtils.h" 33 34 namespace { 35 36 using mozilla::dom::cache::Action; 37 using mozilla::dom::cache::CacheDirectoryMetadata; 38 39 class NullAction final : public Action { 40 public: 41 NullAction() = default; 42 43 virtual void RunOnTarget(mozilla::SafeRefPtr<Resolver> aResolver, 44 const mozilla::Maybe<CacheDirectoryMetadata>&, Data*, 45 const mozilla::Maybe<mozilla::dom::cache::CipherKey>& 46 /* aMaybeCipherKey */) override { 47 // Resolve success immediately. This Action does no actual work. 48 MOZ_DIAGNOSTIC_ASSERT(aResolver); 49 aResolver->Resolve(NS_OK); 50 } 51 }; 52 53 } // namespace 54 55 namespace mozilla::dom::cache { 56 57 using mozilla::dom::quota::AssertIsOnIOThread; 58 using mozilla::dom::quota::ClientDirectoryLock; 59 using mozilla::dom::quota::ClientDirectoryLockHandle; 60 using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT; 61 using mozilla::dom::quota::PersistenceType; 62 using mozilla::dom::quota::QuotaManager; 63 using mozilla::dom::quota::SleepIfEnabled; 64 65 class Context::Data final : public Action::Data { 66 public: 67 explicit Data(nsISerialEventTarget* aTarget) : mTarget(aTarget) { 68 MOZ_DIAGNOSTIC_ASSERT(mTarget); 69 } 70 71 virtual mozIStorageConnection* GetConnection() const override { 72 MOZ_ASSERT(mTarget->IsOnCurrentThread()); 73 return mConnection; 74 } 75 76 virtual void SetConnection(mozIStorageConnection* aConn) override { 77 MOZ_ASSERT(mTarget->IsOnCurrentThread()); 78 MOZ_DIAGNOSTIC_ASSERT(!mConnection); 79 mConnection = aConn; 80 MOZ_DIAGNOSTIC_ASSERT(mConnection); 81 } 82 83 private: 84 ~Data() { 85 // We could proxy release our data here, but instead just assert. The 86 // Context code should guarantee that we are destroyed on the target 87 // thread once the connection is initialized. If we're not, then 88 // QuotaManager might race and try to clear the origin out from under us. 89 MOZ_ASSERT_IF(mConnection, mTarget->IsOnCurrentThread()); 90 } 91 92 nsCOMPtr<nsISerialEventTarget> mTarget; 93 nsCOMPtr<mozIStorageConnection> mConnection; 94 95 // Threadsafe counting because we're created on the PBackground thread 96 // and destroyed on the target IO thread. 97 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Context::Data) 98 }; 99 100 // Executed to perform the complicated dance of steps necessary to initialize 101 // the QuotaManager. This must be performed for each origin before any disk 102 // IO occurrs. 103 class Context::QuotaInitRunnable final : public nsIRunnable { 104 public: 105 QuotaInitRunnable(SafeRefPtr<Context> aContext, SafeRefPtr<Manager> aManager, 106 Data* aData, nsISerialEventTarget* aTarget, 107 SafeRefPtr<Action> aInitAction) 108 : mContext(std::move(aContext)), 109 mThreadsafeHandle(mContext->CreateThreadsafeHandle()), 110 mManager(std::move(aManager)), 111 mData(aData), 112 mTarget(aTarget), 113 mInitAction(std::move(aInitAction)), 114 mInitiatingEventTarget(GetCurrentSerialEventTarget()), 115 mResult(NS_OK), 116 mState(STATE_INIT), 117 mCanceled(false) { 118 MOZ_DIAGNOSTIC_ASSERT(mContext); 119 MOZ_DIAGNOSTIC_ASSERT(mManager); 120 MOZ_DIAGNOSTIC_ASSERT(mData); 121 MOZ_DIAGNOSTIC_ASSERT(mTarget); 122 MOZ_DIAGNOSTIC_ASSERT(mInitiatingEventTarget); 123 MOZ_DIAGNOSTIC_ASSERT(mInitAction); 124 } 125 126 Maybe<ClientDirectoryLock&> MaybeDirectoryLockRef() const { 127 NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable); 128 129 return ToMaybeRef(mDirectoryLockHandle.get()); 130 } 131 132 nsresult Dispatch() { 133 NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable); 134 MOZ_DIAGNOSTIC_ASSERT(mState == STATE_INIT); 135 136 mState = STATE_GET_INFO; 137 nsresult rv = NS_DispatchToMainThread(this, nsIThread::DISPATCH_NORMAL); 138 if (NS_WARN_IF(NS_FAILED(rv))) { 139 mState = STATE_COMPLETE; 140 Clear(); 141 } 142 return rv; 143 } 144 145 void Cancel() { 146 NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable); 147 MOZ_DIAGNOSTIC_ASSERT(!mCanceled); 148 mCanceled = true; 149 mInitAction->CancelOnInitiatingThread(); 150 } 151 152 void DirectoryLockAcquired(ClientDirectoryLockHandle aLockHandle); 153 154 void DirectoryLockFailed(); 155 156 private: 157 class SyncResolver final : public Action::Resolver { 158 public: 159 SyncResolver() : mResolved(false), mResult(NS_OK) {} 160 161 virtual void Resolve(nsresult aRv) override { 162 MOZ_DIAGNOSTIC_ASSERT(!mResolved); 163 mResolved = true; 164 mResult = aRv; 165 }; 166 167 bool Resolved() const { return mResolved; } 168 nsresult Result() const { return mResult; } 169 170 private: 171 ~SyncResolver() = default; 172 173 bool mResolved; 174 nsresult mResult; 175 176 NS_INLINE_DECL_REFCOUNTING(Context::QuotaInitRunnable::SyncResolver, 177 override) 178 }; 179 180 ~QuotaInitRunnable() { 181 MOZ_DIAGNOSTIC_ASSERT(mState == STATE_COMPLETE); 182 MOZ_DIAGNOSTIC_ASSERT(!mContext); 183 MOZ_DIAGNOSTIC_ASSERT(!mInitAction); 184 } 185 186 enum State { 187 STATE_INIT, 188 STATE_GET_INFO, 189 STATE_CREATE_QUOTA_MANAGER, 190 STATE_WAIT_FOR_DIRECTORY_LOCK, 191 STATE_ENSURE_ORIGIN_INITIALIZED, 192 STATE_RUN_ON_TARGET, 193 STATE_RUNNING, 194 STATE_COMPLETING, 195 STATE_COMPLETE 196 }; 197 198 void Complete(nsresult aResult) { 199 MOZ_DIAGNOSTIC_ASSERT(mState == STATE_RUNNING || NS_FAILED(aResult)); 200 201 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(mResult)); 202 mResult = aResult; 203 204 mState = STATE_COMPLETING; 205 MOZ_ALWAYS_SUCCEEDS( 206 mInitiatingEventTarget->Dispatch(this, nsIThread::DISPATCH_NORMAL)); 207 } 208 209 void Clear() { 210 NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable); 211 MOZ_DIAGNOSTIC_ASSERT(mContext); 212 mContext = nullptr; 213 mManager = nullptr; 214 mInitAction = nullptr; 215 } 216 217 SafeRefPtr<Context> mContext; 218 SafeRefPtr<ThreadsafeHandle> mThreadsafeHandle; 219 SafeRefPtr<Manager> mManager; 220 RefPtr<Data> mData; 221 nsCOMPtr<nsISerialEventTarget> mTarget; 222 SafeRefPtr<Action> mInitAction; 223 nsCOMPtr<nsIEventTarget> mInitiatingEventTarget; 224 nsresult mResult; 225 Maybe<mozilla::ipc::PrincipalInfo> mPrincipalInfo; 226 Maybe<CacheDirectoryMetadata> mDirectoryMetadata; 227 ClientDirectoryLockHandle mDirectoryLockHandle; 228 RefPtr<CipherKeyManager> mCipherKeyManager; 229 State mState; 230 Atomic<bool> mCanceled; 231 232 public: 233 NS_DECL_THREADSAFE_ISUPPORTS 234 NS_DECL_NSIRUNNABLE 235 }; 236 237 void Context::QuotaInitRunnable::DirectoryLockAcquired( 238 ClientDirectoryLockHandle aLockHandle) { 239 NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable); 240 MOZ_DIAGNOSTIC_ASSERT(aLockHandle); 241 MOZ_DIAGNOSTIC_ASSERT(mState == STATE_WAIT_FOR_DIRECTORY_LOCK); 242 MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLockHandle); 243 244 mDirectoryLockHandle = std::move(aLockHandle); 245 246 MOZ_DIAGNOSTIC_ASSERT(mDirectoryLockHandle->Id() >= 0); 247 mDirectoryMetadata->mDirectoryLockId = mDirectoryLockHandle->Id(); 248 249 if (mCanceled || mDirectoryLockHandle->Invalidated()) { 250 Complete(NS_ERROR_ABORT); 251 return; 252 } 253 254 QuotaManager* qm = QuotaManager::Get(); 255 MOZ_DIAGNOSTIC_ASSERT(qm); 256 257 mState = STATE_ENSURE_ORIGIN_INITIALIZED; 258 nsresult rv = qm->IOThread()->Dispatch(this, nsIThread::DISPATCH_NORMAL); 259 if (NS_WARN_IF(NS_FAILED(rv))) { 260 Complete(rv); 261 return; 262 } 263 264 NotifyDatabaseWorkStarted(); 265 } 266 267 void Context::QuotaInitRunnable::DirectoryLockFailed() { 268 NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable); 269 MOZ_DIAGNOSTIC_ASSERT(mState == STATE_WAIT_FOR_DIRECTORY_LOCK); 270 MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLockHandle); 271 272 NS_WARNING("Failed to acquire a directory lock!"); 273 274 Complete(NS_ERROR_FAILURE); 275 } 276 277 NS_IMPL_ISUPPORTS(mozilla::dom::cache::Context::QuotaInitRunnable, nsIRunnable); 278 279 // The QuotaManager init state machine is represented in the following diagram: 280 // 281 // +---------------+ 282 // | Start | Resolve(error) 283 // | (Orig Thread) +---------------------+ 284 // +-------+-------+ | 285 // | | 286 // +----------v-----------+ | 287 // | GetInfo | Resolve(error) | 288 // | (Main Thread) +-----------------+ 289 // +----------+-----------+ | 290 // | | 291 // +----------v-----------+ | 292 // | CreateQuotaManager | Resolve(error) | 293 // | (Orig Thread) +-----------------+ 294 // +----------+-----------+ | 295 // | | 296 // +----------v-----------+ | 297 // | WaitForDirectoryLock | Resolve(error) | 298 // | (Orig Thread) +-----------------+ 299 // +----------+-----------+ | 300 // | | 301 // +----------v------------+ | 302 // |EnsureOriginInitialized| Resolve(error) | 303 // | (Quota IO Thread) +----------------+ 304 // +----------+------------+ | 305 // | | 306 // +----------v------------+ | 307 // | RunOnTarget | Resolve(error) | 308 // | (Target Thread) +----------------+ 309 // +----------+------------+ | 310 // | | 311 // +---------v---------+ +------v------+ 312 // | Running | | Completing | 313 // | (Target Thread) +------------>(Orig Thread)| 314 // +-------------------+ +------+------+ 315 // | 316 // +-----v----+ 317 // | Complete | 318 // +----------+ 319 // 320 // The initialization process proceeds through the main states. If an error 321 // occurs, then we transition to Completing state back on the original thread. 322 NS_IMETHODIMP 323 Context::QuotaInitRunnable::Run() { 324 // May run on different threads depending on the state. See individual 325 // state cases for thread assertions. 326 327 SafeRefPtr<SyncResolver> resolver = MakeSafeRefPtr<SyncResolver>(); 328 329 switch (mState) { 330 // ----------------------------------- 331 case STATE_GET_INFO: { 332 MOZ_ASSERT(NS_IsMainThread()); 333 334 auto res = [this]() -> Result<Ok, nsresult> { 335 if (mCanceled) { 336 return Err(NS_ERROR_ABORT); 337 } 338 339 nsCOMPtr<nsIPrincipal> principal = mManager->GetManagerId().Principal(); 340 341 mozilla::ipc::PrincipalInfo principalInfo; 342 QM_TRY( 343 MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal, &principalInfo))); 344 345 mPrincipalInfo.emplace(std::move(principalInfo)); 346 347 mState = STATE_CREATE_QUOTA_MANAGER; 348 349 MOZ_ALWAYS_SUCCEEDS( 350 mInitiatingEventTarget->Dispatch(this, nsIThread::DISPATCH_NORMAL)); 351 352 return Ok{}; 353 }(); 354 355 if (res.isErr()) { 356 resolver->Resolve(res.inspectErr()); 357 } 358 359 break; 360 } 361 // ---------------------------------- 362 case STATE_CREATE_QUOTA_MANAGER: { 363 NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable); 364 365 if (mCanceled || QuotaManager::IsShuttingDown()) { 366 resolver->Resolve(NS_ERROR_ABORT); 367 break; 368 } 369 370 QM_TRY(QuotaManager::EnsureCreated(), QM_PROPAGATE, 371 [&resolver](const auto rv) { resolver->Resolve(rv); }); 372 373 auto* const quotaManager = QuotaManager::Get(); 374 MOZ_DIAGNOSTIC_ASSERT(quotaManager); 375 376 QM_TRY_UNWRAP(auto principalMetadata, 377 quota::GetInfoFromValidatedPrincipalInfo(*quotaManager, 378 *mPrincipalInfo)); 379 380 mDirectoryMetadata.emplace(std::move(principalMetadata)); 381 382 mState = STATE_WAIT_FOR_DIRECTORY_LOCK; 383 384 quotaManager 385 ->OpenClientDirectory({*mDirectoryMetadata, quota::Client::DOMCACHE}) 386 ->Then( 387 GetCurrentSerialEventTarget(), __func__, 388 [self = RefPtr(this)]( 389 QuotaManager::ClientDirectoryLockHandlePromise:: 390 ResolveOrRejectValue&& aValue) { 391 if (aValue.IsResolve()) { 392 self->DirectoryLockAcquired(std::move(aValue.ResolveValue())); 393 } else { 394 self->DirectoryLockFailed(); 395 } 396 }); 397 398 break; 399 } 400 // ---------------------------------- 401 case STATE_ENSURE_ORIGIN_INITIALIZED: { 402 AssertIsOnIOThread(); 403 404 auto res = [this]() -> Result<Ok, nsresult> { 405 if (mCanceled) { 406 return Err(NS_ERROR_ABORT); 407 } 408 409 QuotaManager* quotaManager = QuotaManager::Get(); 410 MOZ_DIAGNOSTIC_ASSERT(quotaManager); 411 412 QM_TRY_UNWRAP(mDirectoryMetadata->mDir, 413 quotaManager->GetOrCreateTemporaryOriginDirectory( 414 *mDirectoryMetadata)); 415 416 auto* cacheQuotaClient = CacheQuotaClient::Get(); 417 MOZ_DIAGNOSTIC_ASSERT(cacheQuotaClient); 418 419 mCipherKeyManager = 420 cacheQuotaClient->GetOrCreateCipherKeyManager(*mDirectoryMetadata); 421 422 SleepIfEnabled( 423 StaticPrefs::dom_cache_databaseInitialization_pauseOnIOThreadMs()); 424 425 mState = STATE_RUN_ON_TARGET; 426 427 MOZ_ALWAYS_SUCCEEDS( 428 mTarget->Dispatch(this, nsIThread::DISPATCH_NORMAL)); 429 430 return Ok{}; 431 }(); 432 433 if (res.isErr()) { 434 resolver->Resolve(res.inspectErr()); 435 } 436 437 break; 438 } 439 // ------------------- 440 case STATE_RUN_ON_TARGET: { 441 MOZ_ASSERT(mTarget->IsOnCurrentThread()); 442 443 mState = STATE_RUNNING; 444 445 // Execute the provided initialization Action. The Action must Resolve() 446 // before returning. 447 448 mInitAction->RunOnTarget( 449 resolver.clonePtr(), mDirectoryMetadata, mData, 450 mCipherKeyManager ? Some(mCipherKeyManager->Ensure()) : Nothing{}); 451 452 MOZ_DIAGNOSTIC_ASSERT(resolver->Resolved()); 453 454 mData = nullptr; 455 456 // If the database was opened, then we should always succeed when creating 457 // the marker file. If it wasn't opened successfully, then no need to 458 // create a marker file anyway. 459 if (NS_SUCCEEDED(resolver->Result())) { 460 MOZ_ALWAYS_SUCCEEDS(CreateMarkerFile(*mDirectoryMetadata)); 461 } 462 463 break; 464 } 465 // ------------------- 466 case STATE_COMPLETING: { 467 NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable); 468 mInitAction->CompleteOnInitiatingThread(mResult); 469 470 mContext->OnQuotaInit(mResult, mDirectoryMetadata, 471 std::move(mDirectoryLockHandle), 472 std::move(mCipherKeyManager)); 473 474 mState = STATE_COMPLETE; 475 476 // Explicitly cleanup here as the destructor could fire on any of 477 // the threads we have bounced through. 478 Clear(); 479 break; 480 } 481 // ----- 482 case STATE_WAIT_FOR_DIRECTORY_LOCK: 483 default: { 484 MOZ_CRASH("unexpected state in QuotaInitRunnable"); 485 } 486 } 487 488 if (resolver->Resolved()) { 489 Complete(resolver->Result()); 490 } 491 492 return NS_OK; 493 } 494 495 // Runnable wrapper around Action objects dispatched on the Context. This 496 // runnable executes the Action on the appropriate threads while the Context 497 // is initialized. 498 class Context::ActionRunnable final : public nsIRunnable, 499 public Action::Resolver, 500 public Context::Activity { 501 public: 502 ActionRunnable(SafeRefPtr<Context> aContext, Data* aData, 503 nsISerialEventTarget* aTarget, SafeRefPtr<Action> aAction, 504 const Maybe<CacheDirectoryMetadata>& aDirectoryMetadata, 505 RefPtr<CipherKeyManager> aCipherKeyManager) 506 : mContext(std::move(aContext)), 507 mData(aData), 508 mTarget(aTarget), 509 mAction(std::move(aAction)), 510 mDirectoryMetadata(aDirectoryMetadata), 511 mCipherKeyManager(std::move(aCipherKeyManager)), 512 mInitiatingThread(GetCurrentSerialEventTarget()), 513 mState(STATE_INIT), 514 mResult(NS_OK), 515 mExecutingRunOnTarget(false) { 516 MOZ_DIAGNOSTIC_ASSERT(mContext); 517 // mData may be nullptr 518 MOZ_DIAGNOSTIC_ASSERT(mTarget); 519 MOZ_DIAGNOSTIC_ASSERT(mAction); 520 // mDirectoryMetadata.mDir may be nullptr if QuotaInitRunnable failed 521 MOZ_DIAGNOSTIC_ASSERT(mInitiatingThread); 522 } 523 524 nsresult Dispatch() { 525 NS_ASSERT_OWNINGTHREAD(ActionRunnable); 526 MOZ_DIAGNOSTIC_ASSERT(mState == STATE_INIT); 527 528 mState = STATE_RUN_ON_TARGET; 529 nsresult rv = mTarget->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL); 530 if (NS_WARN_IF(NS_FAILED(rv))) { 531 mState = STATE_COMPLETE; 532 Clear(); 533 } 534 return rv; 535 } 536 537 virtual bool MatchesCacheId(CacheId aCacheId) const override { 538 NS_ASSERT_OWNINGTHREAD(ActionRunnable); 539 return mAction->MatchesCacheId(aCacheId); 540 } 541 542 virtual void Cancel() override { 543 NS_ASSERT_OWNINGTHREAD(ActionRunnable); 544 mAction->CancelOnInitiatingThread(); 545 } 546 547 virtual void Resolve(nsresult aRv) override { 548 MOZ_ASSERT(mTarget->IsOnCurrentThread()); 549 MOZ_DIAGNOSTIC_ASSERT(mState == STATE_RUNNING); 550 551 mResult = aRv; 552 553 // We ultimately must complete on the initiating thread, but bounce through 554 // the current thread again to ensure that we don't destroy objects and 555 // state out from under the currently running action's stack. 556 mState = STATE_RESOLVING; 557 558 // If we were resolved synchronously within Action::RunOnTarget() then we 559 // can avoid a thread bounce and just resolve once RunOnTarget() returns. 560 // The Run() method will handle this by looking at mState after 561 // RunOnTarget() returns. 562 if (mExecutingRunOnTarget) { 563 return; 564 } 565 566 // Otherwise we are in an asynchronous resolve. And must perform a thread 567 // bounce to run on the target thread again. 568 MOZ_ALWAYS_SUCCEEDS(mTarget->Dispatch(this, nsIThread::DISPATCH_NORMAL)); 569 } 570 571 private: 572 ~ActionRunnable() { 573 MOZ_DIAGNOSTIC_ASSERT(mState == STATE_COMPLETE); 574 MOZ_DIAGNOSTIC_ASSERT(!mContext); 575 MOZ_DIAGNOSTIC_ASSERT(!mAction); 576 } 577 578 void DoStringify(nsACString& aData) override { 579 aData.Append("ActionRunnable ("_ns + 580 // 581 "State:"_ns + IntToCString(mState) + kStringifyDelimiter + 582 // 583 "Action:"_ns + IntToCString(static_cast<bool>(mAction)) + 584 kStringifyDelimiter + 585 // TODO: We might want to have Action::Stringify, too. 586 // 587 "Context:"_ns + IntToCString(static_cast<bool>(mContext)) + 588 kStringifyDelimiter + 589 // We do not print out mContext as we most probably were called 590 // by its Stringify. 591 ")"_ns); 592 } 593 594 void Clear() { 595 NS_ASSERT_OWNINGTHREAD(ActionRunnable); 596 MOZ_DIAGNOSTIC_ASSERT(mContext); 597 MOZ_DIAGNOSTIC_ASSERT(mAction); 598 mContext->RemoveActivity(*this); 599 mContext = nullptr; 600 mAction = nullptr; 601 } 602 603 enum State { 604 STATE_INIT, 605 STATE_RUN_ON_TARGET, 606 STATE_RUNNING, 607 STATE_RESOLVING, 608 STATE_COMPLETING, 609 STATE_COMPLETE 610 }; 611 612 SafeRefPtr<Context> mContext; 613 RefPtr<Data> mData; 614 nsCOMPtr<nsISerialEventTarget> mTarget; 615 SafeRefPtr<Action> mAction; 616 const Maybe<CacheDirectoryMetadata> mDirectoryMetadata; 617 RefPtr<CipherKeyManager> mCipherKeyManager; 618 nsCOMPtr<nsIEventTarget> mInitiatingThread; 619 State mState; 620 nsresult mResult; 621 622 // Only accessible on target thread; 623 bool mExecutingRunOnTarget; 624 625 public: 626 NS_DECL_THREADSAFE_ISUPPORTS 627 NS_DECL_NSIRUNNABLE 628 }; 629 630 NS_IMPL_ISUPPORTS(mozilla::dom::cache::Context::ActionRunnable, nsIRunnable); 631 632 // The ActionRunnable has a simpler state machine. It basically needs to run 633 // the action on the target thread and then complete on the original thread. 634 // 635 // +-------------+ 636 // | Start | 637 // |(Orig Thread)| 638 // +-----+-------+ 639 // | 640 // +-------v---------+ 641 // | RunOnTarget | 642 // |Target IO Thread)+---+ Resolve() 643 // +-------+---------+ | 644 // | | 645 // +-------v----------+ | 646 // | Running | | 647 // |(Target IO Thread)| | 648 // +------------------+ | 649 // | Resolve() | 650 // +-------v----------+ | 651 // | Resolving <--+ +-------------+ 652 // | | | Completing | 653 // |(Target IO Thread)+---------------------->(Orig Thread)| 654 // +------------------+ +-------+-----+ 655 // | 656 // | 657 // +----v---+ 658 // |Complete| 659 // +--------+ 660 // 661 // Its important to note that synchronous actions will effectively Resolve() 662 // out of the Running state immediately. Asynchronous Actions may remain 663 // in the Running state for some time, but normally the ActionRunnable itself 664 // does not see any execution there. Its all handled internal to the Action. 665 NS_IMETHODIMP 666 Context::ActionRunnable::Run() { 667 switch (mState) { 668 // ---------------------- 669 case STATE_RUN_ON_TARGET: { 670 MOZ_ASSERT(mTarget->IsOnCurrentThread()); 671 MOZ_DIAGNOSTIC_ASSERT(!mExecutingRunOnTarget); 672 673 // Note that we are calling RunOnTarget(). This lets us detect 674 // if Resolve() is called synchronously. 675 AutoRestore<bool> executingRunOnTarget(mExecutingRunOnTarget); 676 mExecutingRunOnTarget = true; 677 678 mState = STATE_RUNNING; 679 mAction->RunOnTarget( 680 SafeRefPtrFromThis(), mDirectoryMetadata, mData, 681 mCipherKeyManager ? Some(mCipherKeyManager->Ensure()) : Nothing{}); 682 683 mData = nullptr; 684 685 // Resolve was called synchronously from RunOnTarget(). We can 686 // immediately move to completing now since we are sure RunOnTarget() 687 // completed. 688 if (mState == STATE_RESOLVING) { 689 // Use recursion instead of switch case fall-through... Seems slightly 690 // easier to understand. 691 Run(); 692 } 693 694 break; 695 } 696 // ----------------- 697 case STATE_RESOLVING: { 698 MOZ_ASSERT(mTarget->IsOnCurrentThread()); 699 // The call to Action::RunOnTarget() must have returned now if we 700 // are running on the target thread again. We may now proceed 701 // with completion. 702 mState = STATE_COMPLETING; 703 // Shutdown must be delayed until all Contexts are destroyed. Crash 704 // for this invariant violation. 705 MOZ_ALWAYS_SUCCEEDS( 706 mInitiatingThread->Dispatch(this, nsIThread::DISPATCH_NORMAL)); 707 break; 708 } 709 // ------------------- 710 case STATE_COMPLETING: { 711 NS_ASSERT_OWNINGTHREAD(ActionRunnable); 712 mAction->CompleteOnInitiatingThread(mResult); 713 mState = STATE_COMPLETE; 714 // Explicitly cleanup here as the destructor could fire on any of 715 // the threads we have bounced through. 716 Clear(); 717 break; 718 } 719 // ----------------- 720 default: { 721 MOZ_CRASH("unexpected state in ActionRunnable"); 722 break; 723 } 724 } 725 return NS_OK; 726 } 727 728 void Context::ThreadsafeHandle::AllowToClose() { 729 if (mOwningEventTarget->IsOnCurrentThread()) { 730 AllowToCloseOnOwningThread(); 731 return; 732 } 733 734 // Dispatch is guaranteed to succeed here because we block shutdown until 735 // all Contexts have been destroyed. 736 nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod( 737 "dom::cache::Context::ThreadsafeHandle::AllowToCloseOnOwningThread", this, 738 &ThreadsafeHandle::AllowToCloseOnOwningThread); 739 MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(runnable.forget(), 740 nsIThread::DISPATCH_NORMAL)); 741 } 742 743 void Context::ThreadsafeHandle::InvalidateAndAllowToClose() { 744 if (mOwningEventTarget->IsOnCurrentThread()) { 745 InvalidateAndAllowToCloseOnOwningThread(); 746 return; 747 } 748 749 // Dispatch is guaranteed to succeed here because we block shutdown until 750 // all Contexts have been destroyed. 751 nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod( 752 "dom::cache::Context::ThreadsafeHandle::" 753 "InvalidateAndAllowToCloseOnOwningThread", 754 this, &ThreadsafeHandle::InvalidateAndAllowToCloseOnOwningThread); 755 MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(runnable.forget(), 756 nsIThread::DISPATCH_NORMAL)); 757 } 758 759 Context::ThreadsafeHandle::ThreadsafeHandle(SafeRefPtr<Context> aContext) 760 : mStrongRef(std::move(aContext)), 761 mWeakRef(mStrongRef.unsafeGetRawPtr()), 762 mOwningEventTarget(GetCurrentSerialEventTarget()) {} 763 764 Context::ThreadsafeHandle::~ThreadsafeHandle() { 765 // Normally we only touch mStrongRef on the owning thread. This is safe, 766 // however, because when we do use mStrongRef on the owning thread we are 767 // always holding a strong ref to the ThreadsafeHandle via the owning 768 // runnable. So we cannot run the ThreadsafeHandle destructor simultaneously. 769 if (!mStrongRef || mOwningEventTarget->IsOnCurrentThread()) { 770 return; 771 } 772 773 // Dispatch in NS_ProxyRelease is guaranteed to succeed here because we block 774 // shutdown until all Contexts have been destroyed. Therefore it is ok to have 775 // MOZ_ALWAYS_SUCCEED here. 776 MOZ_ALWAYS_SUCCEEDS(NS_ProxyRelease("Context::ThreadsafeHandle::mStrongRef", 777 mOwningEventTarget, mStrongRef.forget())); 778 } 779 780 void Context::ThreadsafeHandle::AllowToCloseOnOwningThread() { 781 MOZ_ASSERT(mOwningEventTarget->IsOnCurrentThread()); 782 783 // A Context "closes" when its ref count drops to zero. Dropping this 784 // strong ref is necessary, but not sufficient for the close to occur. 785 // Any outstanding IO will continue and keep the Context alive. Once 786 // the Context is idle, it will be destroyed. 787 788 // First, tell the context to flush any target thread shared data. This 789 // data must be released on the target thread prior to running the Context 790 // destructor. This will schedule an Action which ensures that the 791 // ~Context() is not immediately executed when we drop the strong ref. 792 if (mStrongRef) { 793 mStrongRef->DoomTargetData(); 794 } 795 796 // Now drop our strong ref and let Context finish running any outstanding 797 // Actions. 798 mStrongRef = nullptr; 799 } 800 801 void Context::ThreadsafeHandle::InvalidateAndAllowToCloseOnOwningThread() { 802 MOZ_ASSERT(mOwningEventTarget->IsOnCurrentThread()); 803 // Cancel the Context through the weak reference. This means we can 804 // allow the Context to close by dropping the strong ref, but then 805 // still cancel ongoing IO if necessary. 806 if (mWeakRef) { 807 mWeakRef->Invalidate(); 808 } 809 // We should synchronously have AllowToCloseOnOwningThread called when 810 // the Context is canceled. 811 MOZ_DIAGNOSTIC_ASSERT(!mStrongRef); 812 } 813 814 void Context::ThreadsafeHandle::ContextDestroyed(Context& aContext) { 815 MOZ_ASSERT(mOwningEventTarget->IsOnCurrentThread()); 816 MOZ_DIAGNOSTIC_ASSERT(!mStrongRef); 817 MOZ_DIAGNOSTIC_ASSERT(mWeakRef); 818 MOZ_DIAGNOSTIC_ASSERT(mWeakRef == &aContext); 819 mWeakRef = nullptr; 820 } 821 822 // static 823 SafeRefPtr<Context> Context::Create(SafeRefPtr<Manager> aManager, 824 nsISerialEventTarget* aTarget, 825 SafeRefPtr<Action> aInitAction, 826 Maybe<Context&> aOldContext) { 827 auto context = MakeSafeRefPtr<Context>(std::move(aManager), aTarget, 828 std::move(aInitAction)); 829 context->Init(aOldContext); 830 return context; 831 } 832 833 Context::Context(SafeRefPtr<Manager> aManager, nsISerialEventTarget* aTarget, 834 SafeRefPtr<Action> aInitAction) 835 : mManager(std::move(aManager)), 836 mTarget(aTarget), 837 mData(new Data(aTarget)), 838 mState(STATE_CONTEXT_PREINIT), 839 mOrphanedData(false), 840 mInitAction(std::move(aInitAction)) { 841 MOZ_DIAGNOSTIC_ASSERT(mManager); 842 MOZ_DIAGNOSTIC_ASSERT(mTarget); 843 } 844 845 void Context::Dispatch(SafeRefPtr<Action> aAction) { 846 NS_ASSERT_OWNINGTHREAD(Context); 847 MOZ_DIAGNOSTIC_ASSERT(aAction); 848 MOZ_DIAGNOSTIC_ASSERT(mState != STATE_CONTEXT_CANCELED); 849 850 if (mState == STATE_CONTEXT_CANCELED) { 851 return; 852 } 853 854 if (mState == STATE_CONTEXT_INIT || mState == STATE_CONTEXT_PREINIT) { 855 PendingAction* pending = mPendingActions.AppendElement(); 856 pending->mAction = std::move(aAction); 857 return; 858 } 859 860 MOZ_DIAGNOSTIC_ASSERT(mState == STATE_CONTEXT_READY); 861 DispatchAction(std::move(aAction)); 862 } 863 864 Maybe<ClientDirectoryLock&> Context::MaybeDirectoryLockRef() const { 865 NS_ASSERT_OWNINGTHREAD(Context); 866 867 if (mState == STATE_CONTEXT_PREINIT) { 868 MOZ_DIAGNOSTIC_ASSERT(!mInitRunnable); 869 MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLockHandle); 870 871 return Nothing(); 872 } 873 874 if (mState == STATE_CONTEXT_INIT) { 875 MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLockHandle); 876 877 return mInitRunnable->MaybeDirectoryLockRef(); 878 } 879 880 return ToMaybeRef(mDirectoryLockHandle.get()); 881 } 882 883 CipherKeyManager& Context::MutableCipherKeyManagerRef() { 884 MOZ_ASSERT(mTarget->IsOnCurrentThread()); 885 MOZ_DIAGNOSTIC_ASSERT(mCipherKeyManager); 886 887 return *mCipherKeyManager; 888 } 889 890 const Maybe<CacheDirectoryMetadata>& Context::MaybeCacheDirectoryMetadataRef() 891 const { 892 MOZ_ASSERT(mTarget->IsOnCurrentThread()); 893 return mDirectoryMetadata; 894 } 895 896 void Context::CancelAll() { 897 NS_ASSERT_OWNINGTHREAD(Context); 898 899 // In PREINIT state we have not dispatch the init action yet. Just 900 // forget it. 901 if (mState == STATE_CONTEXT_PREINIT) { 902 MOZ_DIAGNOSTIC_ASSERT(!mInitRunnable); 903 mInitAction = nullptr; 904 905 // In INIT state we have dispatched the runnable, but not received the 906 // async completion yet. Cancel the runnable, but don't forget about it 907 // until we get OnQuotaInit() callback. 908 } else if (mState == STATE_CONTEXT_INIT) { 909 mInitRunnable->Cancel(); 910 } 911 912 mState = STATE_CONTEXT_CANCELED; 913 // Allow completion of pending actions in Context::OnQuotaInit 914 if (!mInitRunnable) { 915 mPendingActions.Clear(); 916 } 917 for (const auto& activity : mActivityList.ForwardRange()) { 918 activity->Cancel(); 919 } 920 AllowToClose(); 921 } 922 923 bool Context::IsCanceled() const { 924 NS_ASSERT_OWNINGTHREAD(Context); 925 return mState == STATE_CONTEXT_CANCELED; 926 } 927 928 void Context::Invalidate() { 929 NS_ASSERT_OWNINGTHREAD(Context); 930 mManager->NoteClosing(); 931 CancelAll(); 932 } 933 934 void Context::AllowToClose() { 935 NS_ASSERT_OWNINGTHREAD(Context); 936 if (mThreadsafeHandle) { 937 mThreadsafeHandle->AllowToClose(); 938 } 939 } 940 941 void Context::CancelForCacheId(CacheId aCacheId) { 942 NS_ASSERT_OWNINGTHREAD(Context); 943 944 // Remove matching pending actions 945 mPendingActions.RemoveElementsBy([aCacheId](const auto& pendingAction) { 946 return pendingAction.mAction->MatchesCacheId(aCacheId); 947 }); 948 949 // Cancel activities and let them remove themselves 950 for (const auto& activity : mActivityList.ForwardRange()) { 951 if (activity->MatchesCacheId(aCacheId)) { 952 activity->Cancel(); 953 } 954 } 955 } 956 957 Context::~Context() { 958 NS_ASSERT_OWNINGTHREAD(Context); 959 MOZ_DIAGNOSTIC_ASSERT(mManager); 960 MOZ_DIAGNOSTIC_ASSERT(!mData); 961 962 if (mThreadsafeHandle) { 963 mThreadsafeHandle->ContextDestroyed(*this); 964 } 965 966 { 967 auto destroyingDirectoryLockHandle = std::move(mDirectoryLockHandle); 968 } 969 970 // Note, this may set the mOrphanedData flag. 971 mManager->RemoveContext(*this); 972 973 if (mDirectoryMetadata && mDirectoryMetadata->mDir && !mOrphanedData) { 974 MOZ_ALWAYS_SUCCEEDS(DeleteMarkerFile(*mDirectoryMetadata)); 975 } 976 977 if (mNextContext) { 978 mNextContext->Start(); 979 } 980 } 981 982 void Context::Init(Maybe<Context&> aOldContext) { 983 NS_ASSERT_OWNINGTHREAD(Context); 984 985 if (aOldContext) { 986 aOldContext->SetNextContext(SafeRefPtrFromThis()); 987 return; 988 } 989 990 Start(); 991 } 992 993 void Context::Start() { 994 NS_ASSERT_OWNINGTHREAD(Context); 995 996 // Previous context closing delayed our start, but then we were canceled. 997 // In this case, just do nothing here. 998 if (mState == STATE_CONTEXT_CANCELED) { 999 MOZ_DIAGNOSTIC_ASSERT(!mInitRunnable); 1000 MOZ_DIAGNOSTIC_ASSERT(!mInitAction); 1001 // If we can't initialize the quota subsystem we will never be able to 1002 // clear our shared data object via the target IO thread. Instead just 1003 // clear it here to maintain the invariant that the shared data is 1004 // cleared before Context destruction. 1005 mData = nullptr; 1006 return; 1007 } 1008 1009 MOZ_DIAGNOSTIC_ASSERT(mState == STATE_CONTEXT_PREINIT); 1010 MOZ_DIAGNOSTIC_ASSERT(!mInitRunnable); 1011 1012 mInitRunnable = 1013 new QuotaInitRunnable(SafeRefPtrFromThis(), mManager.clonePtr(), mData, 1014 mTarget, std::move(mInitAction)); 1015 mState = STATE_CONTEXT_INIT; 1016 1017 nsresult rv = mInitRunnable->Dispatch(); 1018 if (NS_FAILED(rv)) { 1019 // Shutdown must be delayed until all Contexts are destroyed. Shutdown 1020 // must also prevent any new Contexts from being constructed. Crash 1021 // for this invariant violation. 1022 MOZ_CRASH("Failed to dispatch QuotaInitRunnable."); 1023 } 1024 } 1025 1026 void Context::DispatchAction(SafeRefPtr<Action> aAction, bool aDoomData) { 1027 NS_ASSERT_OWNINGTHREAD(Context); 1028 1029 auto runnable = MakeSafeRefPtr<ActionRunnable>( 1030 SafeRefPtrFromThis(), mData, mTarget, std::move(aAction), 1031 mDirectoryMetadata, mCipherKeyManager); 1032 1033 if (aDoomData) { 1034 mData = nullptr; 1035 } 1036 1037 nsresult rv = runnable->Dispatch(); 1038 if (NS_FAILED(rv)) { 1039 // Shutdown must be delayed until all Contexts are destroyed. Crash 1040 // for this invariant violation. 1041 MOZ_CRASH("Failed to dispatch ActionRunnable to target thread."); 1042 } 1043 AddActivity(*runnable); 1044 } 1045 1046 void Context::OnQuotaInit( 1047 nsresult aRv, const Maybe<CacheDirectoryMetadata>& aDirectoryMetadata, 1048 ClientDirectoryLockHandle aDirectoryLockHandle, 1049 RefPtr<CipherKeyManager> aCipherKeyManager) { 1050 NS_ASSERT_OWNINGTHREAD(Context); 1051 1052 MOZ_DIAGNOSTIC_ASSERT(mInitRunnable); 1053 mInitRunnable = nullptr; 1054 1055 MOZ_DIAGNOSTIC_ASSERT(!mDirectoryMetadata); 1056 mDirectoryMetadata = aDirectoryMetadata; 1057 1058 // Always save the directory lock to ensure QuotaManager does not shutdown 1059 // before the Context has gone away. 1060 MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLockHandle); 1061 mDirectoryLockHandle = std::move(aDirectoryLockHandle); 1062 1063 MOZ_DIAGNOSTIC_ASSERT(!mCipherKeyManager); 1064 mCipherKeyManager = std::move(aCipherKeyManager); 1065 1066 // If we opening the context failed, but we were not explicitly canceled, 1067 // still treat the entire context as canceled. We don't want to allow 1068 // new actions to be dispatched. We also cannot leave the context in 1069 // the INIT state after failing to open. 1070 if (NS_FAILED(aRv)) { 1071 mState = STATE_CONTEXT_CANCELED; 1072 } 1073 1074 if (mState == STATE_CONTEXT_CANCELED) { 1075 if (NS_SUCCEEDED(aRv)) { 1076 aRv = NS_ERROR_ABORT; 1077 } 1078 for (uint32_t i = 0; i < mPendingActions.Length(); ++i) { 1079 mPendingActions[i].mAction->CompleteOnInitiatingThread(aRv); 1080 } 1081 mPendingActions.Clear(); 1082 mThreadsafeHandle->AllowToClose(); 1083 // Context will destruct after return here and last ref is released. 1084 return; 1085 } 1086 1087 // We could only assert below if quota initialization was a success which 1088 // is ensured by NS_FAILED(aRv) above 1089 MOZ_DIAGNOSTIC_ASSERT(mDirectoryMetadata); 1090 MOZ_DIAGNOSTIC_ASSERT(mDirectoryLockHandle); 1091 MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLockHandle->Invalidated()); 1092 MOZ_DIAGNOSTIC_ASSERT_IF(mDirectoryMetadata->mIsPrivate, mCipherKeyManager); 1093 1094 MOZ_DIAGNOSTIC_ASSERT(mState == STATE_CONTEXT_INIT); 1095 mState = STATE_CONTEXT_READY; 1096 1097 for (uint32_t i = 0; i < mPendingActions.Length(); ++i) { 1098 DispatchAction(std::move(mPendingActions[i].mAction)); 1099 } 1100 mPendingActions.Clear(); 1101 } 1102 1103 void Context::AddActivity(Activity& aActivity) { 1104 NS_ASSERT_OWNINGTHREAD(Context); 1105 MOZ_ASSERT(!mActivityList.Contains(&aActivity)); 1106 mActivityList.AppendElement(WrapNotNullUnchecked(&aActivity)); 1107 } 1108 1109 void Context::RemoveActivity(Activity& aActivity) { 1110 NS_ASSERT_OWNINGTHREAD(Context); 1111 MOZ_ALWAYS_TRUE(mActivityList.RemoveElement(&aActivity)); 1112 MOZ_ASSERT(!mActivityList.Contains(&aActivity)); 1113 } 1114 1115 void Context::NoteOrphanedData() { 1116 NS_ASSERT_OWNINGTHREAD(Context); 1117 // This may be called more than once 1118 mOrphanedData = true; 1119 } 1120 1121 SafeRefPtr<Context::ThreadsafeHandle> Context::CreateThreadsafeHandle() { 1122 NS_ASSERT_OWNINGTHREAD(Context); 1123 if (!mThreadsafeHandle) { 1124 mThreadsafeHandle = MakeSafeRefPtr<ThreadsafeHandle>(SafeRefPtrFromThis()); 1125 } 1126 return mThreadsafeHandle.clonePtr(); 1127 } 1128 1129 void Context::SetNextContext(SafeRefPtr<Context> aNextContext) { 1130 NS_ASSERT_OWNINGTHREAD(Context); 1131 MOZ_DIAGNOSTIC_ASSERT(aNextContext); 1132 MOZ_DIAGNOSTIC_ASSERT(!mNextContext); 1133 mNextContext = std::move(aNextContext); 1134 } 1135 1136 void Context::DoomTargetData() { 1137 NS_ASSERT_OWNINGTHREAD(Context); 1138 MOZ_DIAGNOSTIC_ASSERT(mData); 1139 1140 // We are about to drop our reference to the Data. We need to ensure that 1141 // the ~Context() destructor does not run until contents of Data have been 1142 // released on the Target thread. 1143 1144 // Dispatch a no-op Action. This will hold the Context alive through a 1145 // roundtrip to the target thread and back to the owning thread. The 1146 // ref to the Data object is cleared on the owning thread after creating 1147 // the ActionRunnable, but before dispatching it. 1148 DispatchAction(MakeSafeRefPtr<NullAction>(), true /* doomed data */); 1149 1150 MOZ_DIAGNOSTIC_ASSERT(!mData); 1151 } 1152 1153 void Context::DoStringify(nsACString& aData) { 1154 NS_ASSERT_OWNINGTHREAD(Context); 1155 1156 aData.Append( 1157 "Context "_ns + kStringifyStartInstance + 1158 // 1159 "State:"_ns + IntToCString(mState) + kStringifyDelimiter + 1160 // 1161 "OrphanedData:"_ns + IntToCString(mOrphanedData) + kStringifyDelimiter + 1162 // 1163 "InitRunnable:"_ns + IntToCString(static_cast<bool>(mInitRunnable)) + 1164 kStringifyDelimiter + 1165 // 1166 "InitAction:"_ns + IntToCString(static_cast<bool>(mInitAction)) + 1167 kStringifyDelimiter + 1168 // 1169 "PendingActions:"_ns + 1170 IntToCString(static_cast<uint64_t>(mPendingActions.Length())) + 1171 kStringifyDelimiter + 1172 // 1173 "ActivityList:"_ns + 1174 IntToCString(static_cast<uint64_t>(mActivityList.Length()))); 1175 1176 if (mActivityList.Length() > 0) { 1177 aData.Append(kStringifyStartSet); 1178 for (auto activity : mActivityList.ForwardRange()) { 1179 activity->Stringify(aData); 1180 aData.Append(kStringifyDelimiter); 1181 } 1182 aData.Append(kStringifyEndSet); 1183 }; 1184 1185 aData.Append(kStringifyDelimiter + 1186 // 1187 "DirectoryLock:"_ns + 1188 IntToCString(static_cast<bool>(mDirectoryLockHandle)) + 1189 kStringifyDelimiter + 1190 // 1191 "NextContext:"_ns + 1192 IntToCString(static_cast<bool>(mNextContext)) + 1193 // 1194 kStringifyEndInstance); 1195 1196 if (mNextContext) { 1197 aData.Append(kStringifyDelimiter); 1198 mNextContext->Stringify(aData); 1199 }; 1200 1201 aData.Append(kStringifyEndInstance); 1202 } 1203 1204 } // namespace mozilla::dom::cache