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