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