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/Manager.h"
8 
9 #include "mozilla/AppShutdown.h"
10 #include "mozilla/AutoRestore.h"
11 #include "mozilla/Mutex.h"
12 #include "mozilla/StaticMutex.h"
13 #include "mozilla/StaticPtr.h"
14 #include "mozilla/Unused.h"
15 #include "mozilla/dom/cache/Context.h"
16 #include "mozilla/dom/cache/DBAction.h"
17 #include "mozilla/dom/cache/DBSchema.h"
18 #include "mozilla/dom/cache/FileUtils.h"
19 #include "mozilla/dom/cache/ManagerId.h"
20 #include "mozilla/dom/cache/CacheTypes.h"
21 #include "mozilla/dom/cache/SavedTypes.h"
22 #include "mozilla/dom/cache/StreamList.h"
23 #include "mozilla/dom/cache/Types.h"
24 #include "mozilla/dom/quota/Client.h"
25 #include "mozilla/dom/quota/ClientImpl.h"
26 #include "mozilla/dom/quota/QuotaManager.h"
27 #include "mozilla/ipc/BackgroundParent.h"
28 #include "mozStorageHelper.h"
29 #include "nsIInputStream.h"
30 #include "nsID.h"
31 #include "nsIFile.h"
32 #include "nsIThread.h"
33 #include "nsThreadUtils.h"
34 #include "nsTObserverArray.h"
35 #include "QuotaClientImpl.h"
36 
37 namespace mozilla::dom::cache {
38 
39 using mozilla::dom::quota::Client;
40 using mozilla::dom::quota::CloneFileAndAppend;
41 using mozilla::dom::quota::DirectoryLock;
42 
43 namespace {
44 
45 /**
46  * Note: The aCommitHook argument will be invoked while a lock is held. Callers
47  * should be careful not to pass a hook that might lock on something else and
48  * trigger a deadlock.
49  */
50 template <typename Callable>
MaybeUpdatePaddingFile(nsIFile * aBaseDir,mozIStorageConnection * aConn,const int64_t aIncreaseSize,const int64_t aDecreaseSize,Callable aCommitHook)51 nsresult MaybeUpdatePaddingFile(nsIFile* aBaseDir, mozIStorageConnection* aConn,
52                                 const int64_t aIncreaseSize,
53                                 const int64_t aDecreaseSize,
54                                 Callable aCommitHook) {
55   MOZ_ASSERT(!NS_IsMainThread());
56   MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
57   MOZ_DIAGNOSTIC_ASSERT(aConn);
58   MOZ_DIAGNOSTIC_ASSERT(aIncreaseSize >= 0);
59   MOZ_DIAGNOSTIC_ASSERT(aDecreaseSize >= 0);
60 
61   RefPtr<CacheQuotaClient> cacheQuotaClient = CacheQuotaClient::Get();
62   MOZ_DIAGNOSTIC_ASSERT(cacheQuotaClient);
63 
64   QM_TRY(MOZ_TO_RESULT(cacheQuotaClient->MaybeUpdatePaddingFileInternal(
65       *aBaseDir, *aConn, aIncreaseSize, aDecreaseSize, aCommitHook)));
66 
67   return NS_OK;
68 }
69 
70 // An Action that is executed when a Context is first created.  It ensures that
71 // the directory and database are setup properly.  This lets other actions
72 // not worry about these details.
73 class SetupAction final : public SyncDBAction {
74  public:
SetupAction()75   SetupAction() : SyncDBAction(DBAction::Create) {}
76 
RunSyncWithDBOnTarget(const CacheDirectoryMetadata & aDirectoryMetadata,nsIFile * aDBDir,mozIStorageConnection * aConn)77   virtual nsresult RunSyncWithDBOnTarget(
78       const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
79       mozIStorageConnection* aConn) override {
80     MOZ_DIAGNOSTIC_ASSERT(aDBDir);
81 
82     QM_TRY(MOZ_TO_RESULT(BodyCreateDir(*aDBDir)));
83 
84     // executes in its own transaction
85     QM_TRY(MOZ_TO_RESULT(db::CreateOrMigrateSchema(*aConn)));
86 
87     // If the Context marker file exists, then the last session was
88     // not cleanly shutdown.  In these cases sqlite will ensure that
89     // the database is valid, but we might still orphan data.  Both
90     // Cache objects and body files can be referenced by DOM objects
91     // after they are "removed" from their parent.  So we need to
92     // look and see if any of these late access objects have been
93     // orphaned.
94     //
95     // Note, this must be done after any schema version updates to
96     // ensure our DBSchema methods work correctly.
97     if (MarkerFileExists(aDirectoryMetadata)) {
98       NS_WARNING("Cache not shutdown cleanly! Cleaning up stale data...");
99       mozStorageTransaction trans(aConn, false,
100                                   mozIStorageConnection::TRANSACTION_IMMEDIATE);
101 
102       QM_TRY(MOZ_TO_RESULT(trans.Start()));
103 
104       // Clean up orphaned Cache objects
105       QM_TRY_INSPECT(const auto& orphanedCacheIdList,
106                      db::FindOrphanedCacheIds(*aConn));
107 
108       QM_TRY_INSPECT(
109           const CheckedInt64& overallDeletedPaddingSize,
110           Reduce(
111               orphanedCacheIdList, CheckedInt64(0),
112               [aConn, &aDirectoryMetadata, &aDBDir](
113                   CheckedInt64 oldValue, const Maybe<const CacheId&>& element)
114                   -> Result<CheckedInt64, nsresult> {
115                 QM_TRY_INSPECT(const auto& deletionInfo,
116                                db::DeleteCacheId(*aConn, *element));
117 
118                 QM_TRY(MOZ_TO_RESULT(
119                     BodyDeleteFiles(aDirectoryMetadata, *aDBDir,
120                                     deletionInfo.mDeletedBodyIdList)));
121 
122                 if (deletionInfo.mDeletedPaddingSize > 0) {
123                   DecreaseUsageForDirectoryMetadata(
124                       aDirectoryMetadata, deletionInfo.mDeletedPaddingSize);
125                 }
126 
127                 return oldValue + deletionInfo.mDeletedPaddingSize;
128               }));
129 
130       // Clean up orphaned body objects
131       QM_TRY_INSPECT(const auto& knownBodyIdList, db::GetKnownBodyIds(*aConn));
132 
133       QM_TRY(MOZ_TO_RESULT(BodyDeleteOrphanedFiles(aDirectoryMetadata, *aDBDir,
134                                                    knownBodyIdList)));
135 
136       // Commit() explicitly here, because we want to ensure the padding file
137       // has the correct content.
138       // We'll restore padding file below, so just warn here if failure happens.
139       //
140       // XXX Before, if MaybeUpdatePaddingFile failed but we didn't enter the if
141       // body below, we would have propagated the MaybeUpdatePaddingFile
142       // failure, but if we entered it and RestorePaddingFile succeeded, we
143       // would have returned NS_OK. Now, we will never propagate a
144       // MaybeUpdatePaddingFile failure.
145       QM_WARNONLY_TRY(QM_TO_RESULT(
146           MaybeUpdatePaddingFile(aDBDir, aConn, /* aIncreaceSize */ 0,
147                                  overallDeletedPaddingSize.value(),
148                                  [&trans]() { return trans.Commit(); })));
149     }
150 
151     if (DirectoryPaddingFileExists(*aDBDir, DirPaddingFile::TMP_FILE) ||
152         !DirectoryPaddingFileExists(*aDBDir, DirPaddingFile::FILE)) {
153       QM_TRY(MOZ_TO_RESULT(RestorePaddingFile(aDBDir, aConn)));
154     }
155 
156     return NS_OK;
157   }
158 };
159 
160 // ----------------------------------------------------------------------------
161 
162 // Action that is executed when we determine that content has stopped using
163 // a body file that has been orphaned.
164 class DeleteOrphanedBodyAction final : public Action {
165  public:
166   using DeletedBodyIdList = AutoTArray<nsID, 64>;
167 
DeleteOrphanedBodyAction(DeletedBodyIdList && aDeletedBodyIdList)168   explicit DeleteOrphanedBodyAction(DeletedBodyIdList&& aDeletedBodyIdList)
169       : mDeletedBodyIdList(std::move(aDeletedBodyIdList)) {}
170 
DeleteOrphanedBodyAction(const nsID & aBodyId)171   explicit DeleteOrphanedBodyAction(const nsID& aBodyId)
172       : mDeletedBodyIdList{aBodyId} {}
173 
RunOnTarget(SafeRefPtr<Resolver> aResolver,const Maybe<CacheDirectoryMetadata> & aDirectoryMetadata,Data *)174   void RunOnTarget(SafeRefPtr<Resolver> aResolver,
175                    const Maybe<CacheDirectoryMetadata>& aDirectoryMetadata,
176                    Data*) override {
177     MOZ_DIAGNOSTIC_ASSERT(aResolver);
178     MOZ_DIAGNOSTIC_ASSERT(aDirectoryMetadata);
179     MOZ_DIAGNOSTIC_ASSERT(aDirectoryMetadata->mDir);
180 
181     // Note that since DeleteOrphanedBodyAction isn't used while the context is
182     // being initialized, we don't need to check for cancellation here.
183 
184     const auto resolve = [&aResolver](const nsresult rv) {
185       aResolver->Resolve(rv);
186     };
187 
188     QM_TRY_INSPECT(const auto& dbDir,
189                    CloneFileAndAppend(*aDirectoryMetadata->mDir, u"cache"_ns),
190                    QM_VOID, resolve);
191 
192     QM_TRY(MOZ_TO_RESULT(BodyDeleteFiles(*aDirectoryMetadata, *dbDir,
193                                          mDeletedBodyIdList)),
194            QM_VOID, resolve);
195 
196     aResolver->Resolve(NS_OK);
197   }
198 
199  private:
200   DeletedBodyIdList mDeletedBodyIdList;
201 };
202 
IsHeadRequest(const CacheRequest & aRequest,const CacheQueryParams & aParams)203 bool IsHeadRequest(const CacheRequest& aRequest,
204                    const CacheQueryParams& aParams) {
205   return !aParams.ignoreMethod() &&
206          aRequest.method().LowerCaseEqualsLiteral("head");
207 }
208 
IsHeadRequest(const Maybe<CacheRequest> & aRequest,const CacheQueryParams & aParams)209 bool IsHeadRequest(const Maybe<CacheRequest>& aRequest,
210                    const CacheQueryParams& aParams) {
211   if (aRequest.isSome()) {
212     return !aParams.ignoreMethod() &&
213            aRequest.ref().method().LowerCaseEqualsLiteral("head");
214   }
215   return false;
216 }
217 
MatchByCacheId(CacheId aCacheId)218 auto MatchByCacheId(CacheId aCacheId) {
219   return [aCacheId](const auto& entry) { return entry.mCacheId == aCacheId; };
220 }
221 
MatchByBodyId(const nsID & aBodyId)222 auto MatchByBodyId(const nsID& aBodyId) {
223   return [&aBodyId](const auto& entry) { return entry.mBodyId == aBodyId; };
224 }
225 
226 }  // namespace
227 
228 // ----------------------------------------------------------------------------
229 
230 // Singleton class to track Manager instances and ensure there is only
231 // one for each unique ManagerId.
232 class Manager::Factory {
233  public:
234   friend class StaticAutoPtr<Manager::Factory>;
235 
AcquireCreateIfNonExistent(const SafeRefPtr<ManagerId> & aManagerId)236   static Result<SafeRefPtr<Manager>, nsresult> AcquireCreateIfNonExistent(
237       const SafeRefPtr<ManagerId>& aManagerId) {
238     mozilla::ipc::AssertIsOnBackgroundThread();
239 
240     // If we get here during/after quota manager shutdown, we bail out.
241     MOZ_ASSERT(AppShutdown::GetCurrentShutdownPhase() <
242                ShutdownPhase::AppShutdownQM);
243     if (AppShutdown::GetCurrentShutdownPhase() >=
244         ShutdownPhase::AppShutdownQM) {
245       NS_WARNING(
246           "Attempt to AcquireCreateIfNonExistent a Manager during QM "
247           "shutdown.");
248       return Err(NS_ERROR_ILLEGAL_DURING_SHUTDOWN);
249     }
250 
251     // Ensure there is a factory instance.  This forces the Acquire() call
252     // below to use the same factory.
253     QM_TRY(MOZ_TO_RESULT(MaybeCreateInstance()));
254 
255     SafeRefPtr<Manager> ref = Acquire(*aManagerId);
256     if (!ref) {
257       // TODO: replace this with a thread pool (bug 1119864)
258       // XXX Can't use QM_TRY_INSPECT because that causes a clang-plugin
259       // error of the NoNewThreadsChecker.
260       nsCOMPtr<nsIThread> ioThread;
261       QM_TRY(MOZ_TO_RESULT(
262           NS_NewNamedThread("DOMCacheThread", getter_AddRefs(ioThread))));
263 
264       ref = MakeSafeRefPtr<Manager>(aManagerId.clonePtr(), ioThread,
265                                     ConstructorGuard{});
266 
267       // There may be an old manager for this origin in the process of
268       // cleaning up.  We need to tell the new manager about this so
269       // that it won't actually start until the old manager is done.
270       const SafeRefPtr<Manager> oldManager = Acquire(*aManagerId, Closing);
271       ref->Init(oldManager.maybeDeref());
272 
273       MOZ_ASSERT(!sFactory->mManagerList.Contains(ref));
274       sFactory->mManagerList.AppendElement(
275           WrapNotNullUnchecked(ref.unsafeGetRawPtr()));
276     }
277 
278     return ref;
279   }
280 
Remove(Manager & aManager)281   static void Remove(Manager& aManager) {
282     mozilla::ipc::AssertIsOnBackgroundThread();
283     MOZ_DIAGNOSTIC_ASSERT(sFactory);
284 
285     MOZ_ALWAYS_TRUE(sFactory->mManagerList.RemoveElement(&aManager));
286 
287     // This might both happen in late shutdown such that this event
288     // is executed even after the QuotaManager singleton passed away
289     // or if the QuotaManager has not yet been created.
290     quota::QuotaManager::SafeMaybeRecordQuotaClientShutdownStep(
291         quota::Client::DOMCACHE, "Manager removed"_ns);
292 
293     // clean up the factory singleton if there are no more managers
294     MaybeDestroyInstance();
295   }
296 
Abort(const Client::DirectoryLockIdTable & aDirectoryLockIds)297   static void Abort(const Client::DirectoryLockIdTable& aDirectoryLockIds) {
298     mozilla::ipc::AssertIsOnBackgroundThread();
299 
300     AbortMatching([&aDirectoryLockIds](const auto& manager) {
301       // Check if the Manager holds an acquired DirectoryLock. Origin clearing
302       // can't be blocked by this Manager if there is no acquired DirectoryLock.
303       // If there is an acquired DirectoryLock, check if the table contains the
304       // lock for the Manager.
305       return Client::IsLockForObjectAcquiredAndContainedInLockTable(
306           manager, aDirectoryLockIds);
307     });
308   }
309 
AbortAll()310   static void AbortAll() {
311     mozilla::ipc::AssertIsOnBackgroundThread();
312 
313     AbortMatching([](const auto&) { return true; });
314   }
315 
ShutdownAll()316   static void ShutdownAll() {
317     mozilla::ipc::AssertIsOnBackgroundThread();
318 
319     if (!sFactory) {
320       return;
321     }
322 
323     MOZ_DIAGNOSTIC_ASSERT(!sFactory->mManagerList.IsEmpty());
324 
325     {
326       // Note that we are synchronously calling shutdown code here.  If any
327       // of the shutdown code synchronously decides to delete the Factory
328       // we need to delay that delete until the end of this method.
329       AutoRestore<bool> restore(sFactory->mInSyncAbortOrShutdown);
330       sFactory->mInSyncAbortOrShutdown = true;
331 
332       for (const auto& manager : sFactory->mManagerList.ForwardRange()) {
333         auto pinnedManager =
334             SafeRefPtr{manager.get(), AcquireStrongRefFromRawPtr{}};
335         pinnedManager->Shutdown();
336       }
337     }
338 
339     MaybeDestroyInstance();
340   }
341 
IsShutdownAllComplete()342   static bool IsShutdownAllComplete() {
343     mozilla::ipc::AssertIsOnBackgroundThread();
344     return !sFactory;
345   }
346 
347 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
RecordMayNotDeleteCSCP(int32_t aCacheStreamControlParentId)348   static void RecordMayNotDeleteCSCP(int32_t aCacheStreamControlParentId) {
349     if (sFactory) {
350       sFactory->mPotentiallyUnreleasedCSCP.AppendElement(
351           aCacheStreamControlParentId);
352     }
353   }
354 
RecordHaveDeletedCSCP(int32_t aCacheStreamControlParentId)355   static void RecordHaveDeletedCSCP(int32_t aCacheStreamControlParentId) {
356     if (sFactory) {
357       sFactory->mPotentiallyUnreleasedCSCP.RemoveElement(
358           aCacheStreamControlParentId);
359     }
360   }
361 #endif
GetShutdownStatus()362   static nsCString GetShutdownStatus() {
363     mozilla::ipc::AssertIsOnBackgroundThread();
364 
365     nsCString data;
366 
367     if (sFactory && !sFactory->mManagerList.IsEmpty()) {
368       data.Append(
369           "Managers: "_ns +
370           IntToCString(static_cast<uint64_t>(sFactory->mManagerList.Length())) +
371           " ("_ns);
372 
373       for (const auto& manager : sFactory->mManagerList.NonObservingRange()) {
374         data.Append(quota::AnonymizedOriginString(
375             manager->GetManagerId().QuotaOrigin()));
376 
377         data.AppendLiteral(": ");
378 
379         data.Append(manager->GetState() == State::Open ? "Open"_ns
380                                                        : "Closing"_ns);
381 
382         data.AppendLiteral(", ");
383       }
384 
385       data.AppendLiteral(" ) ");
386       if (sFactory->mPotentiallyUnreleasedCSCP.Length() > 0) {
387         data.Append(
388             "There have been CSCP instances whose"
389             "Send__delete__ might not have freed them.");
390       }
391     }
392 
393     return data;
394   }
395 
396  private:
Factory()397   Factory() : mInSyncAbortOrShutdown(false) {
398     MOZ_COUNT_CTOR(cache::Manager::Factory);
399   }
400 
~Factory()401   ~Factory() {
402     MOZ_COUNT_DTOR(cache::Manager::Factory);
403     MOZ_DIAGNOSTIC_ASSERT(mManagerList.IsEmpty());
404     MOZ_DIAGNOSTIC_ASSERT(!mInSyncAbortOrShutdown);
405   }
406 
MaybeCreateInstance()407   static nsresult MaybeCreateInstance() {
408     mozilla::ipc::AssertIsOnBackgroundThread();
409 
410     if (!sFactory) {
411       // We cannot use ClearOnShutdown() here because we're not on the main
412       // thread.  Instead, we delete sFactory in Factory::Remove() after the
413       // last manager is removed.  ShutdownObserver ensures this happens
414       // before shutdown.
415       sFactory = new Factory();
416     }
417 
418     // Never return sFactory to code outside Factory.  We need to delete it
419     // out from under ourselves just before we return from Remove().  This
420     // would be (even more) dangerous if other code had a pointer to the
421     // factory itself.
422 
423     return NS_OK;
424   }
425 
MaybeDestroyInstance()426   static void MaybeDestroyInstance() {
427     mozilla::ipc::AssertIsOnBackgroundThread();
428     MOZ_DIAGNOSTIC_ASSERT(sFactory);
429 
430     // If the factory is is still in use then we cannot delete yet.  This
431     // could be due to managers still existing or because we are in the
432     // middle of aborting or shutting down.  We need to be careful not to delete
433     // ourself synchronously during shutdown.
434     if (!sFactory->mManagerList.IsEmpty() || sFactory->mInSyncAbortOrShutdown) {
435       return;
436     }
437 
438     sFactory = nullptr;
439   }
440 
Acquire(const ManagerId & aManagerId,State aState=Open)441   static SafeRefPtr<Manager> Acquire(const ManagerId& aManagerId,
442                                      State aState = Open) {
443     mozilla::ipc::AssertIsOnBackgroundThread();
444 
445     QM_TRY(MOZ_TO_RESULT(MaybeCreateInstance()), nullptr);
446 
447     // Iterate in reverse to find the most recent, matching Manager.  This
448     // is important when looking for a Closing Manager.  If a new Manager
449     // chains to an old Manager we want it to be the most recent one.
450     const auto range = Reversed(sFactory->mManagerList.NonObservingRange());
451     const auto foundIt = std::find_if(
452         range.begin(), range.end(), [aState, &aManagerId](const auto& manager) {
453           return aState == manager->GetState() &&
454                  *manager->mManagerId == aManagerId;
455         });
456     return foundIt != range.end()
457                ? SafeRefPtr{foundIt->get(), AcquireStrongRefFromRawPtr{}}
458                : nullptr;
459   }
460 
461   template <typename Condition>
AbortMatching(const Condition & aCondition)462   static void AbortMatching(const Condition& aCondition) {
463     mozilla::ipc::AssertIsOnBackgroundThread();
464 
465     if (!sFactory) {
466       return;
467     }
468 
469     MOZ_DIAGNOSTIC_ASSERT(!sFactory->mManagerList.IsEmpty());
470 
471     {
472       // Note that we are synchronously calling abort code here.  If any
473       // of the shutdown code synchronously decides to delete the Factory
474       // we need to delay that delete until the end of this method.
475       AutoRestore<bool> restore(sFactory->mInSyncAbortOrShutdown);
476       sFactory->mInSyncAbortOrShutdown = true;
477 
478       for (const auto& manager : sFactory->mManagerList.ForwardRange()) {
479         if (aCondition(*manager)) {
480           auto pinnedManager =
481               SafeRefPtr{manager.get(), AcquireStrongRefFromRawPtr{}};
482           pinnedManager->Abort();
483         }
484       }
485     }
486 
487     MaybeDestroyInstance();
488   }
489 
490   // Singleton created on demand and deleted when last Manager is cleared
491   // in Remove().
492   // PBackground thread only.
493   static StaticAutoPtr<Factory> sFactory;
494 
495   // Weak references as we don't want to keep Manager objects alive forever.
496   // When a Manager is destroyed it calls Factory::Remove() to clear itself.
497   // PBackground thread only.
498   nsTObserverArray<NotNull<Manager*>> mManagerList;
499 
500   // This flag is set when we are looping through the list and calling Abort()
501   // or Shutdown() on each Manager.  We need to be careful not to synchronously
502   // trigger the deletion of the factory while still executing this loop.
503   bool mInSyncAbortOrShutdown;
504 
505   nsTArray<int32_t> mPotentiallyUnreleasedCSCP;
506 };
507 
508 // static
509 StaticAutoPtr<Manager::Factory> Manager::Factory::sFactory;
510 
511 // ----------------------------------------------------------------------------
512 
513 // Abstract class to help implement the various Actions.  The vast majority
514 // of Actions are synchronous and need to report back to a Listener on the
515 // Manager.
516 class Manager::BaseAction : public SyncDBAction {
517  protected:
BaseAction(SafeRefPtr<Manager> aManager,ListenerId aListenerId)518   BaseAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId)
519       : SyncDBAction(DBAction::Existing),
520         mManager(std::move(aManager)),
521         mListenerId(aListenerId) {}
522 
523   virtual void Complete(Listener* aListener, ErrorResult&& aRv) = 0;
524 
CompleteOnInitiatingThread(nsresult aRv)525   virtual void CompleteOnInitiatingThread(nsresult aRv) override {
526     NS_ASSERT_OWNINGTHREAD(Manager::BaseAction);
527     Listener* listener = mManager->GetListener(mListenerId);
528     if (listener) {
529       Complete(listener, ErrorResult(aRv));
530     }
531 
532     // ensure we release the manager on the initiating thread
533     mManager = nullptr;
534   }
535 
536   SafeRefPtr<Manager> mManager;
537   const ListenerId mListenerId;
538 };
539 
540 // ----------------------------------------------------------------------------
541 
542 // Action that is executed when we determine that content has stopped using
543 // a Cache object that has been orphaned.
544 class Manager::DeleteOrphanedCacheAction final : public SyncDBAction {
545  public:
DeleteOrphanedCacheAction(SafeRefPtr<Manager> aManager,CacheId aCacheId)546   DeleteOrphanedCacheAction(SafeRefPtr<Manager> aManager, CacheId aCacheId)
547       : SyncDBAction(DBAction::Existing),
548         mManager(std::move(aManager)),
549         mCacheId(aCacheId) {}
550 
RunSyncWithDBOnTarget(const CacheDirectoryMetadata & aDirectoryMetadata,nsIFile * aDBDir,mozIStorageConnection * aConn)551   virtual nsresult RunSyncWithDBOnTarget(
552       const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
553       mozIStorageConnection* aConn) override {
554     mDirectoryMetadata.emplace(aDirectoryMetadata);
555 
556     mozStorageTransaction trans(aConn, false,
557                                 mozIStorageConnection::TRANSACTION_IMMEDIATE);
558 
559     QM_TRY(MOZ_TO_RESULT(trans.Start()));
560 
561     QM_TRY_UNWRAP(mDeletionInfo, db::DeleteCacheId(*aConn, mCacheId));
562 
563     QM_TRY(MOZ_TO_RESULT(MaybeUpdatePaddingFile(
564         aDBDir, aConn, /* aIncreaceSize */ 0, mDeletionInfo.mDeletedPaddingSize,
565         [&trans]() mutable { return trans.Commit(); })));
566 
567     return NS_OK;
568   }
569 
CompleteOnInitiatingThread(nsresult aRv)570   virtual void CompleteOnInitiatingThread(nsresult aRv) override {
571     // If the transaction fails, we shouldn't delete the body files and decrease
572     // their padding size.
573     if (NS_FAILED(aRv)) {
574       mDeletionInfo.mDeletedBodyIdList.Clear();
575       mDeletionInfo.mDeletedPaddingSize = 0;
576     }
577 
578     mManager->NoteOrphanedBodyIdList(mDeletionInfo.mDeletedBodyIdList);
579 
580     if (mDeletionInfo.mDeletedPaddingSize > 0) {
581       DecreaseUsageForDirectoryMetadata(*mDirectoryMetadata,
582                                         mDeletionInfo.mDeletedPaddingSize);
583     }
584 
585     // ensure we release the manager on the initiating thread
586     mManager = nullptr;
587   }
588 
589  private:
590   SafeRefPtr<Manager> mManager;
591   const CacheId mCacheId;
592   DeletionInfo mDeletionInfo;
593   Maybe<CacheDirectoryMetadata> mDirectoryMetadata;
594 };
595 
596 // ----------------------------------------------------------------------------
597 
598 class Manager::CacheMatchAction final : public Manager::BaseAction {
599  public:
CacheMatchAction(SafeRefPtr<Manager> aManager,ListenerId aListenerId,CacheId aCacheId,const CacheMatchArgs & aArgs,SafeRefPtr<StreamList> aStreamList)600   CacheMatchAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
601                    CacheId aCacheId, const CacheMatchArgs& aArgs,
602                    SafeRefPtr<StreamList> aStreamList)
603       : BaseAction(std::move(aManager), aListenerId),
604         mCacheId(aCacheId),
605         mArgs(aArgs),
606         mStreamList(std::move(aStreamList)),
607         mFoundResponse(false) {}
608 
RunSyncWithDBOnTarget(const CacheDirectoryMetadata & aDirectoryMetadata,nsIFile * aDBDir,mozIStorageConnection * aConn)609   virtual nsresult RunSyncWithDBOnTarget(
610       const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
611       mozIStorageConnection* aConn) override {
612     MOZ_DIAGNOSTIC_ASSERT(aDBDir);
613 
614     QM_TRY_INSPECT(
615         const auto& maybeResponse,
616         db::CacheMatch(*aConn, mCacheId, mArgs.request(), mArgs.params()));
617 
618     mFoundResponse = maybeResponse.isSome();
619     if (mFoundResponse) {
620       mResponse = std::move(maybeResponse.ref());
621     }
622 
623     if (!mFoundResponse || !mResponse.mHasBodyId ||
624         IsHeadRequest(mArgs.request(), mArgs.params())) {
625       mResponse.mHasBodyId = false;
626       return NS_OK;
627     }
628 
629     nsCOMPtr<nsIInputStream> stream;
630     if (mArgs.openMode() == OpenMode::Eager) {
631       QM_TRY_UNWRAP(stream,
632                     BodyOpen(aDirectoryMetadata, *aDBDir, mResponse.mBodyId));
633     }
634 
635     mStreamList->Add(mResponse.mBodyId, std::move(stream));
636 
637     return NS_OK;
638   }
639 
Complete(Listener * aListener,ErrorResult && aRv)640   virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
641     if (!mFoundResponse) {
642       aListener->OnOpComplete(std::move(aRv), CacheMatchResult(Nothing()));
643     } else {
644       mStreamList->Activate(mCacheId);
645       aListener->OnOpComplete(std::move(aRv), CacheMatchResult(Nothing()),
646                               mResponse, *mStreamList);
647     }
648     mStreamList = nullptr;
649   }
650 
MatchesCacheId(CacheId aCacheId) const651   virtual bool MatchesCacheId(CacheId aCacheId) const override {
652     return aCacheId == mCacheId;
653   }
654 
655  private:
656   const CacheId mCacheId;
657   const CacheMatchArgs mArgs;
658   SafeRefPtr<StreamList> mStreamList;
659   bool mFoundResponse;
660   SavedResponse mResponse;
661 };
662 
663 // ----------------------------------------------------------------------------
664 
665 class Manager::CacheMatchAllAction final : public Manager::BaseAction {
666  public:
CacheMatchAllAction(SafeRefPtr<Manager> aManager,ListenerId aListenerId,CacheId aCacheId,const CacheMatchAllArgs & aArgs,SafeRefPtr<StreamList> aStreamList)667   CacheMatchAllAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
668                       CacheId aCacheId, const CacheMatchAllArgs& aArgs,
669                       SafeRefPtr<StreamList> aStreamList)
670       : BaseAction(std::move(aManager), aListenerId),
671         mCacheId(aCacheId),
672         mArgs(aArgs),
673         mStreamList(std::move(aStreamList)) {}
674 
RunSyncWithDBOnTarget(const CacheDirectoryMetadata & aDirectoryMetadata,nsIFile * aDBDir,mozIStorageConnection * aConn)675   virtual nsresult RunSyncWithDBOnTarget(
676       const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
677       mozIStorageConnection* aConn) override {
678     MOZ_DIAGNOSTIC_ASSERT(aDBDir);
679 
680     QM_TRY_UNWRAP(mSavedResponses,
681                   db::CacheMatchAll(*aConn, mCacheId, mArgs.maybeRequest(),
682                                     mArgs.params()));
683 
684     for (uint32_t i = 0; i < mSavedResponses.Length(); ++i) {
685       if (!mSavedResponses[i].mHasBodyId ||
686           IsHeadRequest(mArgs.maybeRequest(), mArgs.params())) {
687         mSavedResponses[i].mHasBodyId = false;
688         continue;
689       }
690 
691       nsCOMPtr<nsIInputStream> stream;
692       if (mArgs.openMode() == OpenMode::Eager) {
693         QM_TRY_UNWRAP(stream, BodyOpen(aDirectoryMetadata, *aDBDir,
694                                        mSavedResponses[i].mBodyId));
695       }
696 
697       mStreamList->Add(mSavedResponses[i].mBodyId, std::move(stream));
698     }
699 
700     return NS_OK;
701   }
702 
Complete(Listener * aListener,ErrorResult && aRv)703   virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
704     mStreamList->Activate(mCacheId);
705     aListener->OnOpComplete(std::move(aRv), CacheMatchAllResult(),
706                             mSavedResponses, *mStreamList);
707     mStreamList = nullptr;
708   }
709 
MatchesCacheId(CacheId aCacheId) const710   virtual bool MatchesCacheId(CacheId aCacheId) const override {
711     return aCacheId == mCacheId;
712   }
713 
714  private:
715   const CacheId mCacheId;
716   const CacheMatchAllArgs mArgs;
717   SafeRefPtr<StreamList> mStreamList;
718   nsTArray<SavedResponse> mSavedResponses;
719 };
720 
721 // ----------------------------------------------------------------------------
722 
723 // This is the most complex Action.  It puts a request/response pair into the
724 // Cache.  It does not complete until all of the body data has been saved to
725 // disk.  This means its an asynchronous Action.
726 class Manager::CachePutAllAction final : public DBAction {
727  public:
CachePutAllAction(SafeRefPtr<Manager> aManager,ListenerId aListenerId,CacheId aCacheId,const nsTArray<CacheRequestResponse> & aPutList,const nsTArray<nsCOMPtr<nsIInputStream>> & aRequestStreamList,const nsTArray<nsCOMPtr<nsIInputStream>> & aResponseStreamList)728   CachePutAllAction(
729       SafeRefPtr<Manager> aManager, ListenerId aListenerId, CacheId aCacheId,
730       const nsTArray<CacheRequestResponse>& aPutList,
731       const nsTArray<nsCOMPtr<nsIInputStream>>& aRequestStreamList,
732       const nsTArray<nsCOMPtr<nsIInputStream>>& aResponseStreamList)
733       : DBAction(DBAction::Existing),
734         mManager(std::move(aManager)),
735         mListenerId(aListenerId),
736         mCacheId(aCacheId),
737         mList(aPutList.Length()),
738         mExpectedAsyncCopyCompletions(1),
739         mAsyncResult(NS_OK),
740         mMutex("cache::Manager::CachePutAllAction"),
741         mUpdatedPaddingSize(0),
742         mDeletedPaddingSize(0) {
743     MOZ_DIAGNOSTIC_ASSERT(!aPutList.IsEmpty());
744     MOZ_DIAGNOSTIC_ASSERT(aPutList.Length() == aRequestStreamList.Length());
745     MOZ_DIAGNOSTIC_ASSERT(aPutList.Length() == aResponseStreamList.Length());
746 
747     for (uint32_t i = 0; i < aPutList.Length(); ++i) {
748       Entry* entry = mList.AppendElement();
749       entry->mRequest = aPutList[i].request();
750       entry->mRequestStream = aRequestStreamList[i];
751       entry->mResponse = aPutList[i].response();
752       entry->mResponseStream = aResponseStreamList[i];
753     }
754   }
755 
756  private:
757   ~CachePutAllAction() = default;
758 
RunWithDBOnTarget(SafeRefPtr<Resolver> aResolver,const CacheDirectoryMetadata & aDirectoryMetadata,nsIFile * aDBDir,mozIStorageConnection * aConn)759   virtual void RunWithDBOnTarget(
760       SafeRefPtr<Resolver> aResolver,
761       const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
762       mozIStorageConnection* aConn) override {
763     MOZ_DIAGNOSTIC_ASSERT(aResolver);
764     MOZ_DIAGNOSTIC_ASSERT(aDBDir);
765     MOZ_DIAGNOSTIC_ASSERT(aConn);
766     MOZ_DIAGNOSTIC_ASSERT(!mResolver);
767     MOZ_DIAGNOSTIC_ASSERT(!mDBDir);
768     MOZ_DIAGNOSTIC_ASSERT(!mConn);
769 
770     MOZ_DIAGNOSTIC_ASSERT(!mTarget);
771     mTarget = GetCurrentSerialEventTarget();
772     MOZ_DIAGNOSTIC_ASSERT(mTarget);
773 
774     // We should be pre-initialized to expect one async completion.  This is
775     // the "manual" completion we call at the end of this method in all
776     // cases.
777     MOZ_DIAGNOSTIC_ASSERT(mExpectedAsyncCopyCompletions == 1);
778 
779     mResolver = std::move(aResolver);
780     mDBDir = aDBDir;
781     mConn = aConn;
782     mDirectoryMetadata.emplace(aDirectoryMetadata);
783 
784     // File bodies are streamed to disk via asynchronous copying.  Start
785     // this copying now.  Each copy will eventually result in a call
786     // to OnAsyncCopyComplete().
787     const nsresult rv = [this, &aDirectoryMetadata]() -> nsresult {
788       QM_TRY(CollectEachInRange(
789           mList, [this, &aDirectoryMetadata](auto& entry) -> nsresult {
790             QM_TRY(MOZ_TO_RESULT(
791                 StartStreamCopy(aDirectoryMetadata, entry, RequestStream,
792                                 &mExpectedAsyncCopyCompletions)));
793 
794             QM_TRY(MOZ_TO_RESULT(
795                 StartStreamCopy(aDirectoryMetadata, entry, ResponseStream,
796                                 &mExpectedAsyncCopyCompletions)));
797 
798             return NS_OK;
799           }));
800 
801       return NS_OK;
802     }();
803 
804     // Always call OnAsyncCopyComplete() manually here.  This covers the
805     // case where there is no async copying and also reports any startup
806     // errors correctly.  If we hit an error, then OnAsyncCopyComplete()
807     // will cancel any async copying.
808     OnAsyncCopyComplete(rv);
809   }
810 
811   // Called once for each asynchronous file copy whether it succeeds or
812   // fails.  If a file copy is canceled, it still calls this method with
813   // an error code.
OnAsyncCopyComplete(nsresult aRv)814   void OnAsyncCopyComplete(nsresult aRv) {
815     MOZ_ASSERT(mTarget->IsOnCurrentThread());
816     MOZ_DIAGNOSTIC_ASSERT(mConn);
817     MOZ_DIAGNOSTIC_ASSERT(mResolver);
818     MOZ_DIAGNOSTIC_ASSERT(mExpectedAsyncCopyCompletions > 0);
819 
820     // Explicitly check for cancellation here to catch a race condition.
821     // Consider:
822     //
823     // 1) NS_AsyncCopy() executes on IO thread, but has not saved its
824     //    copy context yet.
825     // 2) CancelAllStreamCopying() occurs on PBackground thread
826     // 3) Copy context from (1) is saved on IO thread.
827     //
828     // Checking for cancellation here catches this condition when we
829     // first call OnAsyncCopyComplete() manually from RunWithDBOnTarget().
830     //
831     // This explicit cancellation check also handles the case where we
832     // are canceled just after all stream copying completes.  We should
833     // abort the synchronous DB operations in this case if we have not
834     // started them yet.
835     if (NS_SUCCEEDED(aRv) && IsCanceled()) {
836       aRv = NS_ERROR_ABORT;
837     }
838 
839     // If any of the async copies fail, we need to still wait for them all to
840     // complete.  Cancel any other streams still working and remember the
841     // error.  All canceled streams will call OnAsyncCopyComplete().
842     if (NS_FAILED(aRv) && NS_SUCCEEDED(mAsyncResult)) {
843       CancelAllStreamCopying();
844       mAsyncResult = aRv;
845     }
846 
847     // Check to see if async copying is still on-going.  If so, then simply
848     // return for now.  We must wait for a later OnAsyncCopyComplete() call.
849     mExpectedAsyncCopyCompletions -= 1;
850     if (mExpectedAsyncCopyCompletions > 0) {
851       return;
852     }
853 
854     // We have finished with all async copying.  Indicate this by clearing all
855     // our copy contexts.
856     {
857       MutexAutoLock lock(mMutex);
858       mCopyContextList.Clear();
859     }
860 
861     // An error occurred while async copying.  Terminate the Action.
862     // DoResolve() will clean up any files we may have written.
863     if (NS_FAILED(mAsyncResult)) {
864       DoResolve(mAsyncResult);
865       return;
866     }
867 
868     mozStorageTransaction trans(mConn, false,
869                                 mozIStorageConnection::TRANSACTION_IMMEDIATE);
870 
871     QM_TRY(MOZ_TO_RESULT(trans.Start()), QM_VOID);
872 
873     const nsresult rv = [this, &trans]() -> nsresult {
874       QM_TRY(CollectEachInRange(mList, [this](Entry& e) -> nsresult {
875         if (e.mRequestStream) {
876           QM_TRY(MOZ_TO_RESULT(BodyFinalizeWrite(*mDBDir, e.mRequestBodyId)));
877         }
878         if (e.mResponseStream) {
879           // Gerenate padding size for opaque response if needed.
880           if (e.mResponse.type() == ResponseType::Opaque) {
881             // It'll generate padding if we've not set it yet.
882             QM_TRY(MOZ_TO_RESULT(BodyMaybeUpdatePaddingSize(
883                 *mDirectoryMetadata, *mDBDir, e.mResponseBodyId,
884                 e.mResponse.paddingInfo(), &e.mResponse.paddingSize())));
885 
886             MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - e.mResponse.paddingSize() >=
887                                   mUpdatedPaddingSize);
888             mUpdatedPaddingSize += e.mResponse.paddingSize();
889           }
890 
891           QM_TRY(MOZ_TO_RESULT(BodyFinalizeWrite(*mDBDir, e.mResponseBodyId)));
892         }
893 
894         QM_TRY_UNWRAP(
895             auto deletionInfo,
896             db::CachePut(*mConn, mCacheId, e.mRequest,
897                          e.mRequestStream ? &e.mRequestBodyId : nullptr,
898                          e.mResponse,
899                          e.mResponseStream ? &e.mResponseBodyId : nullptr));
900 
901         const int64_t deletedPaddingSize = deletionInfo.mDeletedPaddingSize;
902         mDeletedBodyIdList = std::move(deletionInfo.mDeletedBodyIdList);
903 
904         MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - mDeletedPaddingSize >=
905                               deletedPaddingSize);
906         mDeletedPaddingSize += deletedPaddingSize;
907 
908         return NS_OK;
909       }));
910 
911       // Update padding file when it's necessary
912       QM_TRY(MOZ_TO_RESULT(MaybeUpdatePaddingFile(
913           mDBDir, mConn, mUpdatedPaddingSize, mDeletedPaddingSize,
914           [&trans]() mutable { return trans.Commit(); })));
915 
916       return NS_OK;
917     }();
918 
919     DoResolve(rv);
920   }
921 
CompleteOnInitiatingThread(nsresult aRv)922   virtual void CompleteOnInitiatingThread(nsresult aRv) override {
923     NS_ASSERT_OWNINGTHREAD(Action);
924 
925     for (uint32_t i = 0; i < mList.Length(); ++i) {
926       mList[i].mRequestStream = nullptr;
927       mList[i].mResponseStream = nullptr;
928     }
929 
930     // If the transaction fails, we shouldn't delete the body files and decrease
931     // their padding size.
932     if (NS_FAILED(aRv)) {
933       mDeletedBodyIdList.Clear();
934       mDeletedPaddingSize = 0;
935     }
936 
937     mManager->NoteOrphanedBodyIdList(mDeletedBodyIdList);
938 
939     if (mDeletedPaddingSize > 0) {
940       DecreaseUsageForDirectoryMetadata(*mDirectoryMetadata,
941                                         mDeletedPaddingSize);
942     }
943 
944     Listener* listener = mManager->GetListener(mListenerId);
945     mManager = nullptr;
946     if (listener) {
947       listener->OnOpComplete(ErrorResult(aRv), CachePutAllResult());
948     }
949   }
950 
CancelOnInitiatingThread()951   virtual void CancelOnInitiatingThread() override {
952     NS_ASSERT_OWNINGTHREAD(Action);
953     Action::CancelOnInitiatingThread();
954     CancelAllStreamCopying();
955   }
956 
MatchesCacheId(CacheId aCacheId) const957   virtual bool MatchesCacheId(CacheId aCacheId) const override {
958     NS_ASSERT_OWNINGTHREAD(Action);
959     return aCacheId == mCacheId;
960   }
961 
962   struct Entry {
963     CacheRequest mRequest;
964     nsCOMPtr<nsIInputStream> mRequestStream;
965     nsID mRequestBodyId;
966     nsCOMPtr<nsISupports> mRequestCopyContext;
967 
968     CacheResponse mResponse;
969     nsCOMPtr<nsIInputStream> mResponseStream;
970     nsID mResponseBodyId;
971     nsCOMPtr<nsISupports> mResponseCopyContext;
972   };
973 
974   enum StreamId { RequestStream, ResponseStream };
975 
StartStreamCopy(const CacheDirectoryMetadata & aDirectoryMetadata,Entry & aEntry,StreamId aStreamId,uint32_t * aCopyCountOut)976   nsresult StartStreamCopy(const CacheDirectoryMetadata& aDirectoryMetadata,
977                            Entry& aEntry, StreamId aStreamId,
978                            uint32_t* aCopyCountOut) {
979     MOZ_ASSERT(mTarget->IsOnCurrentThread());
980     MOZ_DIAGNOSTIC_ASSERT(aCopyCountOut);
981 
982     if (IsCanceled()) {
983       return NS_ERROR_ABORT;
984     }
985 
986     MOZ_DIAGNOSTIC_ASSERT(aStreamId == RequestStream ||
987                           aStreamId == ResponseStream);
988 
989     const auto& source = aStreamId == RequestStream ? aEntry.mRequestStream
990                                                     : aEntry.mResponseStream;
991 
992     if (!source) {
993       return NS_OK;
994     }
995 
996     QM_TRY_INSPECT((const auto& [bodyId, copyContext]),
997                    BodyStartWriteStream(aDirectoryMetadata, *mDBDir, *source,
998                                         this, AsyncCopyCompleteFunc));
999 
1000     if (aStreamId == RequestStream) {
1001       aEntry.mRequestBodyId = bodyId;
1002     } else {
1003       aEntry.mResponseBodyId = bodyId;
1004     }
1005 
1006     mBodyIdWrittenList.AppendElement(bodyId);
1007 
1008     if (copyContext) {
1009       MutexAutoLock lock(mMutex);
1010       mCopyContextList.AppendElement(copyContext);
1011     }
1012 
1013     *aCopyCountOut += 1;
1014 
1015     return NS_OK;
1016   }
1017 
CancelAllStreamCopying()1018   void CancelAllStreamCopying() {
1019     // May occur on either owning thread or target thread
1020     MutexAutoLock lock(mMutex);
1021     for (uint32_t i = 0; i < mCopyContextList.Length(); ++i) {
1022       MOZ_DIAGNOSTIC_ASSERT(mCopyContextList[i]);
1023       BodyCancelWrite(*mCopyContextList[i]);
1024     }
1025     mCopyContextList.Clear();
1026   }
1027 
AsyncCopyCompleteFunc(void * aClosure,nsresult aRv)1028   static void AsyncCopyCompleteFunc(void* aClosure, nsresult aRv) {
1029     // May be on any thread, including STS event target.
1030     MOZ_DIAGNOSTIC_ASSERT(aClosure);
1031     // Weak ref as we are guaranteed to the action is alive until
1032     // CompleteOnInitiatingThread is called.
1033     CachePutAllAction* action = static_cast<CachePutAllAction*>(aClosure);
1034     action->CallOnAsyncCopyCompleteOnTargetThread(aRv);
1035   }
1036 
CallOnAsyncCopyCompleteOnTargetThread(nsresult aRv)1037   void CallOnAsyncCopyCompleteOnTargetThread(nsresult aRv) {
1038     // May be on any thread, including STS event target.  Non-owning runnable
1039     // here since we are guaranteed the Action will survive until
1040     // CompleteOnInitiatingThread is called.
1041     nsCOMPtr<nsIRunnable> runnable = NewNonOwningRunnableMethod<nsresult>(
1042         "dom::cache::Manager::CachePutAllAction::OnAsyncCopyComplete", this,
1043         &CachePutAllAction::OnAsyncCopyComplete, aRv);
1044     MOZ_ALWAYS_SUCCEEDS(
1045         mTarget->Dispatch(runnable.forget(), nsIThread::DISPATCH_NORMAL));
1046   }
1047 
DoResolve(nsresult aRv)1048   void DoResolve(nsresult aRv) {
1049     MOZ_ASSERT(mTarget->IsOnCurrentThread());
1050 
1051     // DoResolve() must not be called until all async copying has completed.
1052 #ifdef DEBUG
1053     {
1054       MutexAutoLock lock(mMutex);
1055       MOZ_ASSERT(mCopyContextList.IsEmpty());
1056     }
1057 #endif
1058 
1059     // Clean up any files we might have written before hitting the error.
1060     if (NS_FAILED(aRv)) {
1061       BodyDeleteFiles(*mDirectoryMetadata, *mDBDir, mBodyIdWrittenList);
1062       if (mUpdatedPaddingSize > 0) {
1063         DecreaseUsageForDirectoryMetadata(*mDirectoryMetadata,
1064                                           mUpdatedPaddingSize);
1065       }
1066     }
1067 
1068     // Must be released on the target thread where it was opened.
1069     mConn = nullptr;
1070 
1071     // Drop our ref to the target thread as we are done with this thread.
1072     // Also makes our thread assertions catch any incorrect method calls
1073     // after resolve.
1074     mTarget = nullptr;
1075 
1076     // Make sure to de-ref the resolver per the Action API contract.
1077     SafeRefPtr<Action::Resolver> resolver = std::move(mResolver);
1078     resolver->Resolve(aRv);
1079   }
1080 
1081   // initiating thread only
1082   SafeRefPtr<Manager> mManager;
1083   const ListenerId mListenerId;
1084 
1085   // Set on initiating thread, read on target thread.  State machine guarantees
1086   // these are not modified while being read by the target thread.
1087   const CacheId mCacheId;
1088   nsTArray<Entry> mList;
1089   uint32_t mExpectedAsyncCopyCompletions;
1090 
1091   // target thread only
1092   SafeRefPtr<Resolver> mResolver;
1093   nsCOMPtr<nsIFile> mDBDir;
1094   nsCOMPtr<mozIStorageConnection> mConn;
1095   nsCOMPtr<nsISerialEventTarget> mTarget;
1096   nsresult mAsyncResult;
1097   nsTArray<nsID> mBodyIdWrittenList;
1098 
1099   // Written to on target thread, accessed on initiating thread after target
1100   // thread activity is guaranteed complete
1101   nsTArray<nsID> mDeletedBodyIdList;
1102 
1103   // accessed from any thread while mMutex locked
1104   Mutex mMutex;
1105   nsTArray<nsCOMPtr<nsISupports>> mCopyContextList;
1106 
1107   Maybe<CacheDirectoryMetadata> mDirectoryMetadata;
1108   // Track how much pad amount has been added for new entries so that it can be
1109   // removed if an error occurs.
1110   int64_t mUpdatedPaddingSize;
1111   // Track any pad amount associated with overwritten entries.
1112   int64_t mDeletedPaddingSize;
1113 };
1114 
1115 // ----------------------------------------------------------------------------
1116 
1117 class Manager::CacheDeleteAction final : public Manager::BaseAction {
1118  public:
CacheDeleteAction(SafeRefPtr<Manager> aManager,ListenerId aListenerId,CacheId aCacheId,const CacheDeleteArgs & aArgs)1119   CacheDeleteAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
1120                     CacheId aCacheId, const CacheDeleteArgs& aArgs)
1121       : BaseAction(std::move(aManager), aListenerId),
1122         mCacheId(aCacheId),
1123         mArgs(aArgs),
1124         mSuccess(false) {}
1125 
RunSyncWithDBOnTarget(const CacheDirectoryMetadata & aDirectoryMetadata,nsIFile * aDBDir,mozIStorageConnection * aConn)1126   virtual nsresult RunSyncWithDBOnTarget(
1127       const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
1128       mozIStorageConnection* aConn) override {
1129     mDirectoryMetadata.emplace(aDirectoryMetadata);
1130 
1131     mozStorageTransaction trans(aConn, false,
1132                                 mozIStorageConnection::TRANSACTION_IMMEDIATE);
1133 
1134     QM_TRY(MOZ_TO_RESULT(trans.Start()));
1135 
1136     QM_TRY_UNWRAP(
1137         auto maybeDeletionInfo,
1138         db::CacheDelete(*aConn, mCacheId, mArgs.request(), mArgs.params()));
1139 
1140     mSuccess = maybeDeletionInfo.isSome();
1141     if (mSuccess) {
1142       mDeletionInfo = std::move(maybeDeletionInfo.ref());
1143     }
1144 
1145     QM_TRY(MOZ_TO_RESULT(MaybeUpdatePaddingFile(
1146                aDBDir, aConn, /* aIncreaceSize */ 0,
1147                mDeletionInfo.mDeletedPaddingSize,
1148                [&trans]() mutable { return trans.Commit(); })),
1149            QM_PROPAGATE, [this](const nsresult) { mSuccess = false; });
1150 
1151     return NS_OK;
1152   }
1153 
Complete(Listener * aListener,ErrorResult && aRv)1154   virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
1155     // If the transaction fails, we shouldn't delete the body files and decrease
1156     // their padding size.
1157     if (aRv.Failed()) {
1158       mDeletionInfo.mDeletedBodyIdList.Clear();
1159       mDeletionInfo.mDeletedPaddingSize = 0;
1160     }
1161 
1162     mManager->NoteOrphanedBodyIdList(mDeletionInfo.mDeletedBodyIdList);
1163 
1164     if (mDeletionInfo.mDeletedPaddingSize > 0) {
1165       DecreaseUsageForDirectoryMetadata(*mDirectoryMetadata,
1166                                         mDeletionInfo.mDeletedPaddingSize);
1167     }
1168 
1169     aListener->OnOpComplete(std::move(aRv), CacheDeleteResult(mSuccess));
1170   }
1171 
MatchesCacheId(CacheId aCacheId) const1172   virtual bool MatchesCacheId(CacheId aCacheId) const override {
1173     return aCacheId == mCacheId;
1174   }
1175 
1176  private:
1177   const CacheId mCacheId;
1178   const CacheDeleteArgs mArgs;
1179   bool mSuccess;
1180   DeletionInfo mDeletionInfo;
1181   Maybe<CacheDirectoryMetadata> mDirectoryMetadata;
1182 };
1183 
1184 // ----------------------------------------------------------------------------
1185 
1186 class Manager::CacheKeysAction final : public Manager::BaseAction {
1187  public:
CacheKeysAction(SafeRefPtr<Manager> aManager,ListenerId aListenerId,CacheId aCacheId,const CacheKeysArgs & aArgs,SafeRefPtr<StreamList> aStreamList)1188   CacheKeysAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
1189                   CacheId aCacheId, const CacheKeysArgs& aArgs,
1190                   SafeRefPtr<StreamList> aStreamList)
1191       : BaseAction(std::move(aManager), aListenerId),
1192         mCacheId(aCacheId),
1193         mArgs(aArgs),
1194         mStreamList(std::move(aStreamList)) {}
1195 
RunSyncWithDBOnTarget(const CacheDirectoryMetadata & aDirectoryMetadata,nsIFile * aDBDir,mozIStorageConnection * aConn)1196   virtual nsresult RunSyncWithDBOnTarget(
1197       const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
1198       mozIStorageConnection* aConn) override {
1199     MOZ_DIAGNOSTIC_ASSERT(aDBDir);
1200 
1201     QM_TRY_UNWRAP(
1202         mSavedRequests,
1203         db::CacheKeys(*aConn, mCacheId, mArgs.maybeRequest(), mArgs.params()));
1204 
1205     for (uint32_t i = 0; i < mSavedRequests.Length(); ++i) {
1206       if (!mSavedRequests[i].mHasBodyId ||
1207           IsHeadRequest(mArgs.maybeRequest(), mArgs.params())) {
1208         mSavedRequests[i].mHasBodyId = false;
1209         continue;
1210       }
1211 
1212       nsCOMPtr<nsIInputStream> stream;
1213       if (mArgs.openMode() == OpenMode::Eager) {
1214         QM_TRY_UNWRAP(stream, BodyOpen(aDirectoryMetadata, *aDBDir,
1215                                        mSavedRequests[i].mBodyId));
1216       }
1217 
1218       mStreamList->Add(mSavedRequests[i].mBodyId, std::move(stream));
1219     }
1220 
1221     return NS_OK;
1222   }
1223 
Complete(Listener * aListener,ErrorResult && aRv)1224   virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
1225     mStreamList->Activate(mCacheId);
1226     aListener->OnOpComplete(std::move(aRv), CacheKeysResult(), mSavedRequests,
1227                             *mStreamList);
1228     mStreamList = nullptr;
1229   }
1230 
MatchesCacheId(CacheId aCacheId) const1231   virtual bool MatchesCacheId(CacheId aCacheId) const override {
1232     return aCacheId == mCacheId;
1233   }
1234 
1235  private:
1236   const CacheId mCacheId;
1237   const CacheKeysArgs mArgs;
1238   SafeRefPtr<StreamList> mStreamList;
1239   nsTArray<SavedRequest> mSavedRequests;
1240 };
1241 
1242 // ----------------------------------------------------------------------------
1243 
1244 class Manager::StorageMatchAction final : public Manager::BaseAction {
1245  public:
StorageMatchAction(SafeRefPtr<Manager> aManager,ListenerId aListenerId,Namespace aNamespace,const StorageMatchArgs & aArgs,SafeRefPtr<StreamList> aStreamList)1246   StorageMatchAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
1247                      Namespace aNamespace, const StorageMatchArgs& aArgs,
1248                      SafeRefPtr<StreamList> aStreamList)
1249       : BaseAction(std::move(aManager), aListenerId),
1250         mNamespace(aNamespace),
1251         mArgs(aArgs),
1252         mStreamList(std::move(aStreamList)),
1253         mFoundResponse(false) {}
1254 
RunSyncWithDBOnTarget(const CacheDirectoryMetadata & aDirectoryMetadata,nsIFile * aDBDir,mozIStorageConnection * aConn)1255   virtual nsresult RunSyncWithDBOnTarget(
1256       const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
1257       mozIStorageConnection* aConn) override {
1258     MOZ_DIAGNOSTIC_ASSERT(aDBDir);
1259 
1260     auto maybeResponse =
1261         db::StorageMatch(*aConn, mNamespace, mArgs.request(), mArgs.params());
1262     if (NS_WARN_IF(maybeResponse.isErr())) {
1263       return maybeResponse.unwrapErr();
1264     }
1265 
1266     mFoundResponse = maybeResponse.inspect().isSome();
1267     if (mFoundResponse) {
1268       mSavedResponse = maybeResponse.unwrap().ref();
1269     }
1270 
1271     if (!mFoundResponse || !mSavedResponse.mHasBodyId ||
1272         IsHeadRequest(mArgs.request(), mArgs.params())) {
1273       mSavedResponse.mHasBodyId = false;
1274       return NS_OK;
1275     }
1276 
1277     nsCOMPtr<nsIInputStream> stream;
1278     if (mArgs.openMode() == OpenMode::Eager) {
1279       QM_TRY_UNWRAP(stream, BodyOpen(aDirectoryMetadata, *aDBDir,
1280                                      mSavedResponse.mBodyId));
1281     }
1282 
1283     mStreamList->Add(mSavedResponse.mBodyId, std::move(stream));
1284 
1285     return NS_OK;
1286   }
1287 
Complete(Listener * aListener,ErrorResult && aRv)1288   virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
1289     if (!mFoundResponse) {
1290       aListener->OnOpComplete(std::move(aRv), StorageMatchResult(Nothing()));
1291     } else {
1292       mStreamList->Activate(mSavedResponse.mCacheId);
1293       aListener->OnOpComplete(std::move(aRv), StorageMatchResult(Nothing()),
1294                               mSavedResponse, *mStreamList);
1295     }
1296     mStreamList = nullptr;
1297   }
1298 
1299  private:
1300   const Namespace mNamespace;
1301   const StorageMatchArgs mArgs;
1302   SafeRefPtr<StreamList> mStreamList;
1303   bool mFoundResponse;
1304   SavedResponse mSavedResponse;
1305 };
1306 
1307 // ----------------------------------------------------------------------------
1308 
1309 class Manager::StorageHasAction final : public Manager::BaseAction {
1310  public:
StorageHasAction(SafeRefPtr<Manager> aManager,ListenerId aListenerId,Namespace aNamespace,const StorageHasArgs & aArgs)1311   StorageHasAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
1312                    Namespace aNamespace, const StorageHasArgs& aArgs)
1313       : BaseAction(std::move(aManager), aListenerId),
1314         mNamespace(aNamespace),
1315         mArgs(aArgs),
1316         mCacheFound(false) {}
1317 
RunSyncWithDBOnTarget(const CacheDirectoryMetadata & aDirectoryMetadata,nsIFile * aDBDir,mozIStorageConnection * aConn)1318   virtual nsresult RunSyncWithDBOnTarget(
1319       const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
1320       mozIStorageConnection* aConn) override {
1321     QM_TRY_INSPECT(const auto& maybeCacheId,
1322                    db::StorageGetCacheId(*aConn, mNamespace, mArgs.key()));
1323 
1324     mCacheFound = maybeCacheId.isSome();
1325 
1326     return NS_OK;
1327   }
1328 
Complete(Listener * aListener,ErrorResult && aRv)1329   virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
1330     aListener->OnOpComplete(std::move(aRv), StorageHasResult(mCacheFound));
1331   }
1332 
1333  private:
1334   const Namespace mNamespace;
1335   const StorageHasArgs mArgs;
1336   bool mCacheFound;
1337 };
1338 
1339 // ----------------------------------------------------------------------------
1340 
1341 class Manager::StorageOpenAction final : public Manager::BaseAction {
1342  public:
StorageOpenAction(SafeRefPtr<Manager> aManager,ListenerId aListenerId,Namespace aNamespace,const StorageOpenArgs & aArgs)1343   StorageOpenAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
1344                     Namespace aNamespace, const StorageOpenArgs& aArgs)
1345       : BaseAction(std::move(aManager), aListenerId),
1346         mNamespace(aNamespace),
1347         mArgs(aArgs),
1348         mCacheId(INVALID_CACHE_ID) {}
1349 
RunSyncWithDBOnTarget(const CacheDirectoryMetadata & aDirectoryMetadata,nsIFile * aDBDir,mozIStorageConnection * aConn)1350   virtual nsresult RunSyncWithDBOnTarget(
1351       const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
1352       mozIStorageConnection* aConn) override {
1353     // Cache does not exist, create it instead
1354     mozStorageTransaction trans(aConn, false,
1355                                 mozIStorageConnection::TRANSACTION_IMMEDIATE);
1356 
1357     QM_TRY(MOZ_TO_RESULT(trans.Start()));
1358 
1359     // Look for existing cache
1360     QM_TRY_INSPECT(const auto& maybeCacheId,
1361                    db::StorageGetCacheId(*aConn, mNamespace, mArgs.key()));
1362 
1363     if (maybeCacheId.isSome()) {
1364       mCacheId = maybeCacheId.ref();
1365       MOZ_DIAGNOSTIC_ASSERT(mCacheId != INVALID_CACHE_ID);
1366       return NS_OK;
1367     }
1368 
1369     QM_TRY_UNWRAP(mCacheId, db::CreateCacheId(*aConn));
1370 
1371     QM_TRY(MOZ_TO_RESULT(
1372         db::StoragePutCache(*aConn, mNamespace, mArgs.key(), mCacheId)));
1373 
1374     QM_TRY(MOZ_TO_RESULT(trans.Commit()));
1375 
1376     MOZ_DIAGNOSTIC_ASSERT(mCacheId != INVALID_CACHE_ID);
1377     return NS_OK;
1378   }
1379 
Complete(Listener * aListener,ErrorResult && aRv)1380   virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
1381     MOZ_DIAGNOSTIC_ASSERT(aRv.Failed() || mCacheId != INVALID_CACHE_ID);
1382     aListener->OnOpComplete(std::move(aRv),
1383                             StorageOpenResult(nullptr, nullptr, mNamespace),
1384                             mCacheId);
1385   }
1386 
1387  private:
1388   const Namespace mNamespace;
1389   const StorageOpenArgs mArgs;
1390   CacheId mCacheId;
1391 };
1392 
1393 // ----------------------------------------------------------------------------
1394 
1395 class Manager::StorageDeleteAction final : public Manager::BaseAction {
1396  public:
StorageDeleteAction(SafeRefPtr<Manager> aManager,ListenerId aListenerId,Namespace aNamespace,const StorageDeleteArgs & aArgs)1397   StorageDeleteAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
1398                       Namespace aNamespace, const StorageDeleteArgs& aArgs)
1399       : BaseAction(std::move(aManager), aListenerId),
1400         mNamespace(aNamespace),
1401         mArgs(aArgs),
1402         mCacheDeleted(false),
1403         mCacheId(INVALID_CACHE_ID) {}
1404 
RunSyncWithDBOnTarget(const CacheDirectoryMetadata & aDirectoryMetadata,nsIFile * aDBDir,mozIStorageConnection * aConn)1405   virtual nsresult RunSyncWithDBOnTarget(
1406       const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
1407       mozIStorageConnection* aConn) override {
1408     mozStorageTransaction trans(aConn, false,
1409                                 mozIStorageConnection::TRANSACTION_IMMEDIATE);
1410 
1411     QM_TRY(MOZ_TO_RESULT(trans.Start()));
1412 
1413     QM_TRY_INSPECT(const auto& maybeCacheId,
1414                    db::StorageGetCacheId(*aConn, mNamespace, mArgs.key()));
1415 
1416     if (maybeCacheId.isNothing()) {
1417       mCacheDeleted = false;
1418       return NS_OK;
1419     }
1420     mCacheId = maybeCacheId.ref();
1421 
1422     // Don't delete the removing padding size here, we'll delete it on
1423     // DeleteOrphanedCacheAction.
1424     QM_TRY(
1425         MOZ_TO_RESULT(db::StorageForgetCache(*aConn, mNamespace, mArgs.key())));
1426 
1427     QM_TRY(MOZ_TO_RESULT(trans.Commit()));
1428 
1429     mCacheDeleted = true;
1430     return NS_OK;
1431   }
1432 
Complete(Listener * aListener,ErrorResult && aRv)1433   virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
1434     if (mCacheDeleted) {
1435       // If content is referencing this cache, mark it orphaned to be
1436       // deleted later.
1437       if (!mManager->SetCacheIdOrphanedIfRefed(mCacheId)) {
1438         // no outstanding references, delete immediately
1439         const auto pinnedContext =
1440             SafeRefPtr{mManager->mContext, AcquireStrongRefFromRawPtr{}};
1441 
1442         if (pinnedContext->IsCanceled()) {
1443           pinnedContext->NoteOrphanedData();
1444         } else {
1445           pinnedContext->CancelForCacheId(mCacheId);
1446           pinnedContext->Dispatch(MakeSafeRefPtr<DeleteOrphanedCacheAction>(
1447               mManager.clonePtr(), mCacheId));
1448         }
1449       }
1450     }
1451 
1452     aListener->OnOpComplete(std::move(aRv), StorageDeleteResult(mCacheDeleted));
1453   }
1454 
1455  private:
1456   const Namespace mNamespace;
1457   const StorageDeleteArgs mArgs;
1458   bool mCacheDeleted;
1459   CacheId mCacheId;
1460 };
1461 
1462 // ----------------------------------------------------------------------------
1463 
1464 class Manager::StorageKeysAction final : public Manager::BaseAction {
1465  public:
StorageKeysAction(SafeRefPtr<Manager> aManager,ListenerId aListenerId,Namespace aNamespace)1466   StorageKeysAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
1467                     Namespace aNamespace)
1468       : BaseAction(std::move(aManager), aListenerId), mNamespace(aNamespace) {}
1469 
RunSyncWithDBOnTarget(const CacheDirectoryMetadata & aDirectoryMetadata,nsIFile * aDBDir,mozIStorageConnection * aConn)1470   virtual nsresult RunSyncWithDBOnTarget(
1471       const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
1472       mozIStorageConnection* aConn) override {
1473     QM_TRY_UNWRAP(mKeys, db::StorageGetKeys(*aConn, mNamespace));
1474 
1475     return NS_OK;
1476   }
1477 
Complete(Listener * aListener,ErrorResult && aRv)1478   virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
1479     if (aRv.Failed()) {
1480       mKeys.Clear();
1481     }
1482     aListener->OnOpComplete(std::move(aRv), StorageKeysResult(mKeys));
1483   }
1484 
1485  private:
1486   const Namespace mNamespace;
1487   nsTArray<nsString> mKeys;
1488 };
1489 
1490 // ----------------------------------------------------------------------------
1491 
1492 class Manager::OpenStreamAction final : public Manager::BaseAction {
1493  public:
OpenStreamAction(SafeRefPtr<Manager> aManager,ListenerId aListenerId,InputStreamResolver && aResolver,const nsID & aBodyId)1494   OpenStreamAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
1495                    InputStreamResolver&& aResolver, const nsID& aBodyId)
1496       : BaseAction(std::move(aManager), aListenerId),
1497         mResolver(std::move(aResolver)),
1498         mBodyId(aBodyId) {}
1499 
RunSyncWithDBOnTarget(const CacheDirectoryMetadata & aDirectoryMetadata,nsIFile * aDBDir,mozIStorageConnection * aConn)1500   virtual nsresult RunSyncWithDBOnTarget(
1501       const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
1502       mozIStorageConnection* aConn) override {
1503     MOZ_DIAGNOSTIC_ASSERT(aDBDir);
1504 
1505     QM_TRY_UNWRAP(mBodyStream, BodyOpen(aDirectoryMetadata, *aDBDir, mBodyId));
1506 
1507     return NS_OK;
1508   }
1509 
Complete(Listener * aListener,ErrorResult && aRv)1510   virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
1511     if (aRv.Failed()) {
1512       // Ignore the reason for fail and just pass a null input stream to let it
1513       // fail.
1514       aRv.SuppressException();
1515       mResolver(nullptr);
1516     } else {
1517       mResolver(std::move(mBodyStream));
1518     }
1519 
1520     mResolver = nullptr;
1521   }
1522 
1523  private:
1524   InputStreamResolver mResolver;
1525   const nsID mBodyId;
1526   nsCOMPtr<nsIInputStream> mBodyStream;
1527 };
1528 
1529 // ----------------------------------------------------------------------------
1530 
1531 // static
1532 Manager::ListenerId Manager::sNextListenerId = 0;
1533 
OnOpComplete(ErrorResult && aRv,const CacheOpResult & aResult)1534 void Manager::Listener::OnOpComplete(ErrorResult&& aRv,
1535                                      const CacheOpResult& aResult) {
1536   OnOpComplete(std::move(aRv), aResult, INVALID_CACHE_ID, Nothing());
1537 }
1538 
OnOpComplete(ErrorResult && aRv,const CacheOpResult & aResult,CacheId aOpenedCacheId)1539 void Manager::Listener::OnOpComplete(ErrorResult&& aRv,
1540                                      const CacheOpResult& aResult,
1541                                      CacheId aOpenedCacheId) {
1542   OnOpComplete(std::move(aRv), aResult, aOpenedCacheId, Nothing());
1543 }
1544 
OnOpComplete(ErrorResult && aRv,const CacheOpResult & aResult,const SavedResponse & aSavedResponse,StreamList & aStreamList)1545 void Manager::Listener::OnOpComplete(ErrorResult&& aRv,
1546                                      const CacheOpResult& aResult,
1547                                      const SavedResponse& aSavedResponse,
1548                                      StreamList& aStreamList) {
1549   AutoTArray<SavedResponse, 1> responseList;
1550   responseList.AppendElement(aSavedResponse);
1551   OnOpComplete(
1552       std::move(aRv), aResult, INVALID_CACHE_ID,
1553       Some(StreamInfo{responseList, nsTArray<SavedRequest>(), aStreamList}));
1554 }
1555 
OnOpComplete(ErrorResult && aRv,const CacheOpResult & aResult,const nsTArray<SavedResponse> & aSavedResponseList,StreamList & aStreamList)1556 void Manager::Listener::OnOpComplete(
1557     ErrorResult&& aRv, const CacheOpResult& aResult,
1558     const nsTArray<SavedResponse>& aSavedResponseList,
1559     StreamList& aStreamList) {
1560   OnOpComplete(std::move(aRv), aResult, INVALID_CACHE_ID,
1561                Some(StreamInfo{aSavedResponseList, nsTArray<SavedRequest>(),
1562                                aStreamList}));
1563 }
1564 
OnOpComplete(ErrorResult && aRv,const CacheOpResult & aResult,const nsTArray<SavedRequest> & aSavedRequestList,StreamList & aStreamList)1565 void Manager::Listener::OnOpComplete(
1566     ErrorResult&& aRv, const CacheOpResult& aResult,
1567     const nsTArray<SavedRequest>& aSavedRequestList, StreamList& aStreamList) {
1568   OnOpComplete(std::move(aRv), aResult, INVALID_CACHE_ID,
1569                Some(StreamInfo{nsTArray<SavedResponse>(), aSavedRequestList,
1570                                aStreamList}));
1571 }
1572 
1573 // static
AcquireCreateIfNonExistent(const SafeRefPtr<ManagerId> & aManagerId)1574 Result<SafeRefPtr<Manager>, nsresult> Manager::AcquireCreateIfNonExistent(
1575     const SafeRefPtr<ManagerId>& aManagerId) {
1576   mozilla::ipc::AssertIsOnBackgroundThread();
1577   return Factory::AcquireCreateIfNonExistent(aManagerId);
1578 }
1579 
1580 // static
InitiateShutdown()1581 void Manager::InitiateShutdown() {
1582   mozilla::ipc::AssertIsOnBackgroundThread();
1583 
1584   Factory::ShutdownAll();
1585 }
1586 
1587 // static
IsShutdownAllComplete()1588 bool Manager::IsShutdownAllComplete() {
1589   mozilla::ipc::AssertIsOnBackgroundThread();
1590 
1591   return Factory::IsShutdownAllComplete();
1592 }
1593 
1594 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
RecordMayNotDeleteCSCP(int32_t aCacheStreamControlParentId)1595 void Manager::RecordMayNotDeleteCSCP(int32_t aCacheStreamControlParentId) {
1596   Factory::RecordMayNotDeleteCSCP(aCacheStreamControlParentId);
1597 }
1598 
RecordHaveDeletedCSCP(int32_t aCacheStreamControlParentId)1599 void Manager::RecordHaveDeletedCSCP(int32_t aCacheStreamControlParentId) {
1600   Factory::RecordHaveDeletedCSCP(aCacheStreamControlParentId);
1601 }
1602 #endif
1603 
1604 // static
GetShutdownStatus()1605 nsCString Manager::GetShutdownStatus() {
1606   mozilla::ipc::AssertIsOnBackgroundThread();
1607 
1608   return Factory::GetShutdownStatus();
1609 }
1610 
1611 // static
Abort(const Client::DirectoryLockIdTable & aDirectoryLockIds)1612 void Manager::Abort(const Client::DirectoryLockIdTable& aDirectoryLockIds) {
1613   mozilla::ipc::AssertIsOnBackgroundThread();
1614 
1615   Factory::Abort(aDirectoryLockIds);
1616 }
1617 
1618 // static
AbortAll()1619 void Manager::AbortAll() {
1620   mozilla::ipc::AssertIsOnBackgroundThread();
1621 
1622   Factory::AbortAll();
1623 }
1624 
RemoveListener(Listener * aListener)1625 void Manager::RemoveListener(Listener* aListener) {
1626   NS_ASSERT_OWNINGTHREAD(Manager);
1627   // There may not be a listener here in the case where an actor is killed
1628   // before it can perform any actual async requests on Manager.
1629   mListeners.RemoveElement(aListener, ListenerEntryListenerComparator());
1630   MOZ_ASSERT(
1631       !mListeners.Contains(aListener, ListenerEntryListenerComparator()));
1632   MaybeAllowContextToClose();
1633 }
1634 
RemoveContext(Context & aContext)1635 void Manager::RemoveContext(Context& aContext) {
1636   NS_ASSERT_OWNINGTHREAD(Manager);
1637   MOZ_DIAGNOSTIC_ASSERT(mContext);
1638   MOZ_DIAGNOSTIC_ASSERT(mContext == &aContext);
1639 
1640   // Whether the Context destruction was triggered from the Manager going
1641   // idle or the underlying storage being invalidated, we should know we
1642   // are closing before the Context is destroyed.
1643   MOZ_DIAGNOSTIC_ASSERT(mState == Closing);
1644 
1645   // Before forgetting the Context, check to see if we have any outstanding
1646   // cache or body objects waiting for deletion.  If so, note that we've
1647   // orphaned data so it will be cleaned up on the next open.
1648   if (std::any_of(
1649           mCacheIdRefs.cbegin(), mCacheIdRefs.cend(),
1650           [](const auto& cacheIdRef) { return cacheIdRef.mOrphaned; }) ||
1651       std::any_of(mBodyIdRefs.cbegin(), mBodyIdRefs.cend(),
1652                   [](const auto& bodyIdRef) { return bodyIdRef.mOrphaned; })) {
1653     aContext.NoteOrphanedData();
1654   }
1655 
1656   mContext = nullptr;
1657 
1658   // Once the context is gone, we can immediately remove ourself from the
1659   // Factory list.  We don't need to block shutdown by staying in the list
1660   // any more.
1661   Factory::Remove(*this);
1662 }
1663 
NoteClosing()1664 void Manager::NoteClosing() {
1665   NS_ASSERT_OWNINGTHREAD(Manager);
1666   // This can be called more than once legitimately through different paths.
1667   mState = Closing;
1668 }
1669 
GetState() const1670 Manager::State Manager::GetState() const {
1671   NS_ASSERT_OWNINGTHREAD(Manager);
1672   return mState;
1673 }
1674 
AddRefCacheId(CacheId aCacheId)1675 void Manager::AddRefCacheId(CacheId aCacheId) {
1676   NS_ASSERT_OWNINGTHREAD(Manager);
1677 
1678   const auto end = mCacheIdRefs.end();
1679   const auto foundIt =
1680       std::find_if(mCacheIdRefs.begin(), end, MatchByCacheId(aCacheId));
1681   if (foundIt != end) {
1682     foundIt->mCount += 1;
1683     return;
1684   }
1685 
1686   mCacheIdRefs.AppendElement(CacheIdRefCounter{aCacheId, 1, false});
1687 }
1688 
ReleaseCacheId(CacheId aCacheId)1689 void Manager::ReleaseCacheId(CacheId aCacheId) {
1690   NS_ASSERT_OWNINGTHREAD(Manager);
1691 
1692   const auto end = mCacheIdRefs.end();
1693   const auto foundIt =
1694       std::find_if(mCacheIdRefs.begin(), end, MatchByCacheId(aCacheId));
1695   if (foundIt != end) {
1696 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1697     const uint32_t oldRef = foundIt->mCount;
1698 #endif
1699     foundIt->mCount -= 1;
1700     MOZ_DIAGNOSTIC_ASSERT(foundIt->mCount < oldRef);
1701     if (foundIt->mCount == 0) {
1702       const bool orphaned = foundIt->mOrphaned;
1703       mCacheIdRefs.RemoveElementAt(foundIt);
1704       const auto pinnedContext =
1705           SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
1706       // If the context is already gone, then orphan flag should have been
1707       // set in RemoveContext().
1708       if (orphaned && pinnedContext) {
1709         if (pinnedContext->IsCanceled()) {
1710           pinnedContext->NoteOrphanedData();
1711         } else {
1712           pinnedContext->CancelForCacheId(aCacheId);
1713           pinnedContext->Dispatch(MakeSafeRefPtr<DeleteOrphanedCacheAction>(
1714               SafeRefPtrFromThis(), aCacheId));
1715         }
1716       }
1717     }
1718     MaybeAllowContextToClose();
1719     return;
1720   }
1721 
1722   MOZ_ASSERT_UNREACHABLE("Attempt to release CacheId that is not referenced!");
1723 }
1724 
AddRefBodyId(const nsID & aBodyId)1725 void Manager::AddRefBodyId(const nsID& aBodyId) {
1726   NS_ASSERT_OWNINGTHREAD(Manager);
1727 
1728   const auto end = mBodyIdRefs.end();
1729   const auto foundIt =
1730       std::find_if(mBodyIdRefs.begin(), end, MatchByBodyId(aBodyId));
1731   if (foundIt != end) {
1732     foundIt->mCount += 1;
1733     return;
1734   }
1735 
1736   mBodyIdRefs.AppendElement(BodyIdRefCounter{aBodyId, 1, false});
1737 }
1738 
ReleaseBodyId(const nsID & aBodyId)1739 void Manager::ReleaseBodyId(const nsID& aBodyId) {
1740   NS_ASSERT_OWNINGTHREAD(Manager);
1741 
1742   const auto end = mBodyIdRefs.end();
1743   const auto foundIt =
1744       std::find_if(mBodyIdRefs.begin(), end, MatchByBodyId(aBodyId));
1745   if (foundIt != end) {
1746 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1747     const uint32_t oldRef = foundIt->mCount;
1748 #endif
1749     foundIt->mCount -= 1;
1750     MOZ_DIAGNOSTIC_ASSERT(foundIt->mCount < oldRef);
1751     if (foundIt->mCount < 1) {
1752       const bool orphaned = foundIt->mOrphaned;
1753       mBodyIdRefs.RemoveElementAt(foundIt);
1754       const auto pinnedContext =
1755           SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
1756       // If the context is already gone, then orphan flag should have been
1757       // set in RemoveContext().
1758       if (orphaned && pinnedContext) {
1759         if (pinnedContext->IsCanceled()) {
1760           pinnedContext->NoteOrphanedData();
1761         } else {
1762           pinnedContext->Dispatch(
1763               MakeSafeRefPtr<DeleteOrphanedBodyAction>(aBodyId));
1764         }
1765       }
1766     }
1767     MaybeAllowContextToClose();
1768     return;
1769   }
1770 
1771   MOZ_ASSERT_UNREACHABLE("Attempt to release BodyId that is not referenced!");
1772 }
1773 
GetManagerId() const1774 const ManagerId& Manager::GetManagerId() const { return *mManagerId; }
1775 
AddStreamList(StreamList & aStreamList)1776 void Manager::AddStreamList(StreamList& aStreamList) {
1777   NS_ASSERT_OWNINGTHREAD(Manager);
1778   mStreamLists.AppendElement(WrapNotNullUnchecked(&aStreamList));
1779 }
1780 
RemoveStreamList(StreamList & aStreamList)1781 void Manager::RemoveStreamList(StreamList& aStreamList) {
1782   NS_ASSERT_OWNINGTHREAD(Manager);
1783   mStreamLists.RemoveElement(&aStreamList);
1784 }
1785 
ExecuteCacheOp(Listener * aListener,CacheId aCacheId,const CacheOpArgs & aOpArgs)1786 void Manager::ExecuteCacheOp(Listener* aListener, CacheId aCacheId,
1787                              const CacheOpArgs& aOpArgs) {
1788   NS_ASSERT_OWNINGTHREAD(Manager);
1789   MOZ_DIAGNOSTIC_ASSERT(aListener);
1790   MOZ_DIAGNOSTIC_ASSERT(aOpArgs.type() != CacheOpArgs::TCachePutAllArgs);
1791 
1792   if (NS_WARN_IF(mState == Closing)) {
1793     aListener->OnOpComplete(ErrorResult(NS_ERROR_FAILURE), void_t());
1794     return;
1795   }
1796 
1797   const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
1798   MOZ_DIAGNOSTIC_ASSERT(!pinnedContext->IsCanceled());
1799 
1800   auto action = [this, aListener, aCacheId, &aOpArgs,
1801                  &pinnedContext]() -> SafeRefPtr<Action> {
1802     const ListenerId listenerId = SaveListener(aListener);
1803 
1804     if (CacheOpArgs::TCacheDeleteArgs == aOpArgs.type()) {
1805       return MakeSafeRefPtr<CacheDeleteAction>(SafeRefPtrFromThis(), listenerId,
1806                                                aCacheId,
1807                                                aOpArgs.get_CacheDeleteArgs());
1808     }
1809 
1810     auto streamList = MakeSafeRefPtr<StreamList>(SafeRefPtrFromThis(),
1811                                                  pinnedContext.clonePtr());
1812 
1813     switch (aOpArgs.type()) {
1814       case CacheOpArgs::TCacheMatchArgs:
1815         return MakeSafeRefPtr<CacheMatchAction>(
1816             SafeRefPtrFromThis(), listenerId, aCacheId,
1817             aOpArgs.get_CacheMatchArgs(), std::move(streamList));
1818       case CacheOpArgs::TCacheMatchAllArgs:
1819         return MakeSafeRefPtr<CacheMatchAllAction>(
1820             SafeRefPtrFromThis(), listenerId, aCacheId,
1821             aOpArgs.get_CacheMatchAllArgs(), std::move(streamList));
1822       case CacheOpArgs::TCacheKeysArgs:
1823         return MakeSafeRefPtr<CacheKeysAction>(
1824             SafeRefPtrFromThis(), listenerId, aCacheId,
1825             aOpArgs.get_CacheKeysArgs(), std::move(streamList));
1826       default:
1827         MOZ_CRASH("Unknown Cache operation!");
1828     }
1829   }();
1830 
1831   pinnedContext->Dispatch(std::move(action));
1832 }
1833 
ExecuteStorageOp(Listener * aListener,Namespace aNamespace,const CacheOpArgs & aOpArgs)1834 void Manager::ExecuteStorageOp(Listener* aListener, Namespace aNamespace,
1835                                const CacheOpArgs& aOpArgs) {
1836   NS_ASSERT_OWNINGTHREAD(Manager);
1837   MOZ_DIAGNOSTIC_ASSERT(aListener);
1838 
1839   if (NS_WARN_IF(mState == Closing)) {
1840     aListener->OnOpComplete(ErrorResult(NS_ERROR_FAILURE), void_t());
1841     return;
1842   }
1843 
1844   const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
1845   MOZ_DIAGNOSTIC_ASSERT(!pinnedContext->IsCanceled());
1846 
1847   auto action = [this, aListener, aNamespace, &aOpArgs,
1848                  &pinnedContext]() -> SafeRefPtr<Action> {
1849     const ListenerId listenerId = SaveListener(aListener);
1850 
1851     switch (aOpArgs.type()) {
1852       case CacheOpArgs::TStorageMatchArgs:
1853         return MakeSafeRefPtr<StorageMatchAction>(
1854             SafeRefPtrFromThis(), listenerId, aNamespace,
1855             aOpArgs.get_StorageMatchArgs(),
1856             MakeSafeRefPtr<StreamList>(SafeRefPtrFromThis(),
1857                                        pinnedContext.clonePtr()));
1858       case CacheOpArgs::TStorageHasArgs:
1859         return MakeSafeRefPtr<StorageHasAction>(SafeRefPtrFromThis(),
1860                                                 listenerId, aNamespace,
1861                                                 aOpArgs.get_StorageHasArgs());
1862       case CacheOpArgs::TStorageOpenArgs:
1863         return MakeSafeRefPtr<StorageOpenAction>(SafeRefPtrFromThis(),
1864                                                  listenerId, aNamespace,
1865                                                  aOpArgs.get_StorageOpenArgs());
1866       case CacheOpArgs::TStorageDeleteArgs:
1867         return MakeSafeRefPtr<StorageDeleteAction>(
1868             SafeRefPtrFromThis(), listenerId, aNamespace,
1869             aOpArgs.get_StorageDeleteArgs());
1870       case CacheOpArgs::TStorageKeysArgs:
1871         return MakeSafeRefPtr<StorageKeysAction>(SafeRefPtrFromThis(),
1872                                                  listenerId, aNamespace);
1873       default:
1874         MOZ_CRASH("Unknown CacheStorage operation!");
1875     }
1876   }();
1877 
1878   pinnedContext->Dispatch(std::move(action));
1879 }
1880 
ExecuteOpenStream(Listener * aListener,InputStreamResolver && aResolver,const nsID & aBodyId)1881 void Manager::ExecuteOpenStream(Listener* aListener,
1882                                 InputStreamResolver&& aResolver,
1883                                 const nsID& aBodyId) {
1884   NS_ASSERT_OWNINGTHREAD(Manager);
1885   MOZ_DIAGNOSTIC_ASSERT(aListener);
1886   MOZ_DIAGNOSTIC_ASSERT(aResolver);
1887 
1888   if (NS_WARN_IF(mState == Closing)) {
1889     aResolver(nullptr);
1890     return;
1891   }
1892 
1893   const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
1894   MOZ_DIAGNOSTIC_ASSERT(!pinnedContext->IsCanceled());
1895 
1896   // We save the listener simply to track the existence of the caller here.
1897   // Our returned value will really be passed to the resolver when the
1898   // operation completes.  In the future we should remove the Listener
1899   // mechanism in favor of std::function or MozPromise.
1900   ListenerId listenerId = SaveListener(aListener);
1901 
1902   pinnedContext->Dispatch(MakeSafeRefPtr<OpenStreamAction>(
1903       SafeRefPtrFromThis(), listenerId, std::move(aResolver), aBodyId));
1904 }
1905 
ExecutePutAll(Listener * aListener,CacheId aCacheId,const nsTArray<CacheRequestResponse> & aPutList,const nsTArray<nsCOMPtr<nsIInputStream>> & aRequestStreamList,const nsTArray<nsCOMPtr<nsIInputStream>> & aResponseStreamList)1906 void Manager::ExecutePutAll(
1907     Listener* aListener, CacheId aCacheId,
1908     const nsTArray<CacheRequestResponse>& aPutList,
1909     const nsTArray<nsCOMPtr<nsIInputStream>>& aRequestStreamList,
1910     const nsTArray<nsCOMPtr<nsIInputStream>>& aResponseStreamList) {
1911   NS_ASSERT_OWNINGTHREAD(Manager);
1912   MOZ_DIAGNOSTIC_ASSERT(aListener);
1913 
1914   if (NS_WARN_IF(mState == Closing)) {
1915     aListener->OnOpComplete(ErrorResult(NS_ERROR_FAILURE), CachePutAllResult());
1916     return;
1917   }
1918 
1919   const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
1920   MOZ_DIAGNOSTIC_ASSERT(!pinnedContext->IsCanceled());
1921 
1922   ListenerId listenerId = SaveListener(aListener);
1923   pinnedContext->Dispatch(MakeSafeRefPtr<CachePutAllAction>(
1924       SafeRefPtrFromThis(), listenerId, aCacheId, aPutList, aRequestStreamList,
1925       aResponseStreamList));
1926 }
1927 
Manager(SafeRefPtr<ManagerId> aManagerId,nsIThread * aIOThread,const ConstructorGuard &)1928 Manager::Manager(SafeRefPtr<ManagerId> aManagerId, nsIThread* aIOThread,
1929                  const ConstructorGuard&)
1930     : mManagerId(std::move(aManagerId)),
1931       mIOThread(aIOThread),
1932       mContext(nullptr),
1933       mShuttingDown(false),
1934       mState(Open) {
1935   MOZ_DIAGNOSTIC_ASSERT(mManagerId);
1936   MOZ_DIAGNOSTIC_ASSERT(mIOThread);
1937 }
1938 
~Manager()1939 Manager::~Manager() {
1940   NS_ASSERT_OWNINGTHREAD(Manager);
1941   MOZ_DIAGNOSTIC_ASSERT(mState == Closing);
1942   MOZ_DIAGNOSTIC_ASSERT(!mContext);
1943 
1944   nsCOMPtr<nsIThread> ioThread;
1945   mIOThread.swap(ioThread);
1946 
1947   // Don't spin the event loop in the destructor waiting for the thread to
1948   // shutdown.  Defer this to the main thread, instead.
1949   MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(NewRunnableMethod(
1950       "nsIThread::AsyncShutdown", ioThread, &nsIThread::AsyncShutdown)));
1951 }
1952 
Init(Maybe<Manager &> aOldManager)1953 void Manager::Init(Maybe<Manager&> aOldManager) {
1954   NS_ASSERT_OWNINGTHREAD(Manager);
1955 
1956   // Create the context immediately.  Since there can at most be one Context
1957   // per Manager now, this lets us cleanly call Factory::Remove() once the
1958   // Context goes away.
1959   SafeRefPtr<Context> ref = Context::Create(
1960       SafeRefPtrFromThis(), mIOThread->SerialEventTarget(),
1961       MakeSafeRefPtr<SetupAction>(),
1962       aOldManager ? SomeRef(*aOldManager->mContext) : Nothing());
1963   mContext = ref.unsafeGetRawPtr();
1964 }
1965 
Shutdown()1966 void Manager::Shutdown() {
1967   NS_ASSERT_OWNINGTHREAD(Manager);
1968 
1969   // Ignore duplicate attempts to shutdown.  This can occur when we start
1970   // a browser initiated shutdown and then run ~Manager() which also
1971   // calls Shutdown().
1972   if (mShuttingDown) {
1973     return;
1974   }
1975 
1976   mShuttingDown = true;
1977 
1978   // Note that we are closing to prevent any new requests from coming in and
1979   // creating a new Context.  We must ensure all Contexts and IO operations are
1980   // complete before shutdown proceeds.
1981   NoteClosing();
1982 
1983   // If there is a context, then cancel and only note that we are done after
1984   // its cleaned up.
1985   if (mContext) {
1986     const auto pinnedContext =
1987         SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
1988     pinnedContext->CancelAll();
1989     return;
1990   }
1991 }
1992 
MaybeDirectoryLockRef() const1993 Maybe<DirectoryLock&> Manager::MaybeDirectoryLockRef() const {
1994   NS_ASSERT_OWNINGTHREAD(Manager);
1995   MOZ_DIAGNOSTIC_ASSERT(mContext);
1996 
1997   return mContext->MaybeDirectoryLockRef();
1998 }
1999 
Abort()2000 void Manager::Abort() {
2001   NS_ASSERT_OWNINGTHREAD(Manager);
2002   MOZ_DIAGNOSTIC_ASSERT(mContext);
2003 
2004   // Note that we are closing to prevent any new requests from coming in and
2005   // creating a new Context.  We must ensure all Contexts and IO operations are
2006   // complete before origin clear proceeds.
2007   NoteClosing();
2008 
2009   // Cancel and only note that we are done after the context is cleaned up.
2010   const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
2011   pinnedContext->CancelAll();
2012 }
2013 
SaveListener(Listener * aListener)2014 Manager::ListenerId Manager::SaveListener(Listener* aListener) {
2015   NS_ASSERT_OWNINGTHREAD(Manager);
2016 
2017   // Once a Listener is added, we keep a reference to it until its
2018   // removed.  Since the same Listener might make multiple requests,
2019   // ensure we only have a single reference in our list.
2020   ListenerList::index_type index =
2021       mListeners.IndexOf(aListener, 0, ListenerEntryListenerComparator());
2022   if (index != ListenerList::NoIndex) {
2023     return mListeners[index].mId;
2024   }
2025 
2026   ListenerId id = sNextListenerId;
2027   sNextListenerId += 1;
2028 
2029   mListeners.AppendElement(ListenerEntry(id, aListener));
2030   return id;
2031 }
2032 
GetListener(ListenerId aListenerId) const2033 Manager::Listener* Manager::GetListener(ListenerId aListenerId) const {
2034   NS_ASSERT_OWNINGTHREAD(Manager);
2035   ListenerList::index_type index =
2036       mListeners.IndexOf(aListenerId, 0, ListenerEntryIdComparator());
2037   if (index != ListenerList::NoIndex) {
2038     return mListeners[index].mListener;
2039   }
2040 
2041   // This can legitimately happen if the actor is deleted while a request is
2042   // in process.  For example, the child process OOMs.
2043   return nullptr;
2044 }
2045 
SetCacheIdOrphanedIfRefed(CacheId aCacheId)2046 bool Manager::SetCacheIdOrphanedIfRefed(CacheId aCacheId) {
2047   NS_ASSERT_OWNINGTHREAD(Manager);
2048 
2049   const auto end = mCacheIdRefs.end();
2050   const auto foundIt =
2051       std::find_if(mCacheIdRefs.begin(), end, MatchByCacheId(aCacheId));
2052   if (foundIt != end) {
2053     MOZ_DIAGNOSTIC_ASSERT(foundIt->mCount > 0);
2054     MOZ_DIAGNOSTIC_ASSERT(!foundIt->mOrphaned);
2055     foundIt->mOrphaned = true;
2056     return true;
2057   }
2058 
2059   return false;
2060 }
2061 
2062 // TODO: provide way to set body non-orphaned if its added back to a cache (bug
2063 // 1110479)
2064 
SetBodyIdOrphanedIfRefed(const nsID & aBodyId)2065 bool Manager::SetBodyIdOrphanedIfRefed(const nsID& aBodyId) {
2066   NS_ASSERT_OWNINGTHREAD(Manager);
2067 
2068   const auto end = mBodyIdRefs.end();
2069   const auto foundIt =
2070       std::find_if(mBodyIdRefs.begin(), end, MatchByBodyId(aBodyId));
2071   if (foundIt != end) {
2072     MOZ_DIAGNOSTIC_ASSERT(foundIt->mCount > 0);
2073     MOZ_DIAGNOSTIC_ASSERT(!foundIt->mOrphaned);
2074     foundIt->mOrphaned = true;
2075     return true;
2076   }
2077 
2078   return false;
2079 }
2080 
NoteOrphanedBodyIdList(const nsTArray<nsID> & aDeletedBodyIdList)2081 void Manager::NoteOrphanedBodyIdList(const nsTArray<nsID>& aDeletedBodyIdList) {
2082   NS_ASSERT_OWNINGTHREAD(Manager);
2083 
2084   // XXX TransformIfIntoNewArray might be generalized to allow specifying the
2085   // type of nsTArray to create, so that it can create an AutoTArray as well; an
2086   // TransformIf (without AbortOnErr) might be added, which could be used here.
2087   DeleteOrphanedBodyAction::DeletedBodyIdList deleteNowList;
2088   deleteNowList.SetCapacity(aDeletedBodyIdList.Length());
2089 
2090   std::copy_if(aDeletedBodyIdList.cbegin(), aDeletedBodyIdList.cend(),
2091                MakeBackInserter(deleteNowList),
2092                [this](const auto& deletedBodyId) {
2093                  return !SetBodyIdOrphanedIfRefed(deletedBodyId);
2094                });
2095 
2096   // TODO: note that we need to check these bodies for staleness on startup (bug
2097   // 1110446)
2098   const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
2099   if (!deleteNowList.IsEmpty() && pinnedContext &&
2100       !pinnedContext->IsCanceled()) {
2101     pinnedContext->Dispatch(
2102         MakeSafeRefPtr<DeleteOrphanedBodyAction>(std::move(deleteNowList)));
2103   }
2104 }
2105 
MaybeAllowContextToClose()2106 void Manager::MaybeAllowContextToClose() {
2107   NS_ASSERT_OWNINGTHREAD(Manager);
2108 
2109   // If we have an active context, but we have no more users of the Manager,
2110   // then let it shut itself down.  We must wait for all possible users of
2111   // Cache state information to complete before doing this.  Once we allow
2112   // the Context to close we may not reliably get notified of storage
2113   // invalidation.
2114   const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
2115   if (pinnedContext && mListeners.IsEmpty() && mCacheIdRefs.IsEmpty() &&
2116       mBodyIdRefs.IsEmpty()) {
2117     // Mark this Manager as invalid so that it won't get used again.  We don't
2118     // want to start any new operations once we allow the Context to close since
2119     // it may race with the underlying storage getting invalidated.
2120     NoteClosing();
2121 
2122     pinnedContext->AllowToClose();
2123   }
2124 }
2125 
2126 }  // namespace mozilla::dom::cache
2127