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