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 "ServiceWorkerScriptCache.h"
8 
9 #include "js/Array.h"  // JS::GetArrayLength
10 #include "mozilla/Unused.h"
11 #include "mozilla/dom/CacheBinding.h"
12 #include "mozilla/dom/cache/CacheStorage.h"
13 #include "mozilla/dom/cache/Cache.h"
14 #include "mozilla/dom/Promise.h"
15 #include "mozilla/dom/PromiseWorkerProxy.h"
16 #include "mozilla/dom/ScriptLoader.h"
17 #include "mozilla/dom/WorkerCommon.h"
18 #include "mozilla/ipc/BackgroundUtils.h"
19 #include "mozilla/ipc/PBackgroundSharedTypes.h"
20 #include "mozilla/net/CookieJarSettings.h"
21 #include "nsICacheInfoChannel.h"
22 #include "nsIStreamLoader.h"
23 #include "nsIThreadRetargetableRequest.h"
24 #include "nsIUUIDGenerator.h"
25 #include "nsIXPConnect.h"
26 
27 #include "nsIInputStreamPump.h"
28 #include "nsIPrincipal.h"
29 #include "nsIScriptSecurityManager.h"
30 #include "nsContentUtils.h"
31 #include "nsNetUtil.h"
32 #include "ServiceWorkerManager.h"
33 #include "nsStringStream.h"
34 
35 using mozilla::dom::cache::Cache;
36 using mozilla::dom::cache::CacheStorage;
37 using mozilla::ipc::PrincipalInfo;
38 
39 namespace mozilla {
40 namespace dom {
41 
42 namespace serviceWorkerScriptCache {
43 
44 namespace {
45 
CreateCacheStorage(JSContext * aCx,nsIPrincipal * aPrincipal,ErrorResult & aRv)46 already_AddRefed<CacheStorage> CreateCacheStorage(JSContext* aCx,
47                                                   nsIPrincipal* aPrincipal,
48                                                   ErrorResult& aRv) {
49   MOZ_ASSERT(NS_IsMainThread());
50   MOZ_ASSERT(aPrincipal);
51 
52   nsIXPConnect* xpc = nsContentUtils::XPConnect();
53   MOZ_ASSERT(xpc, "This should never be null!");
54   JS::Rooted<JSObject*> sandbox(aCx);
55   aRv = xpc->CreateSandbox(aCx, aPrincipal, sandbox.address());
56   if (NS_WARN_IF(aRv.Failed())) {
57     return nullptr;
58   }
59 
60   // This is called when the JSContext is not in a realm, so CreateSandbox
61   // returned an unwrapped global.
62   MOZ_ASSERT(JS_IsGlobalObject(sandbox));
63 
64   nsCOMPtr<nsIGlobalObject> sandboxGlobalObject = xpc::NativeGlobal(sandbox);
65   if (!sandboxGlobalObject) {
66     aRv.Throw(NS_ERROR_FAILURE);
67     return nullptr;
68   }
69 
70   // We assume private browsing is not enabled here.  The ScriptLoader
71   // explicitly fails for private browsing so there should never be
72   // a service worker running in private browsing mode.  Therefore if
73   // we are purging scripts or running a comparison algorithm we cannot
74   // be in private browsing.
75   //
76   // Also, bypass the CacheStorage trusted origin checks.  The ServiceWorker
77   // has validated the origin prior to this point.  All the information
78   // to revalidate is not available now.
79   return CacheStorage::CreateOnMainThread(cache::CHROME_ONLY_NAMESPACE,
80                                           sandboxGlobalObject, aPrincipal,
81                                           true /* force trusted origin */, aRv);
82 }
83 
84 class CompareManager;
85 class CompareCache;
86 
87 // This class downloads a URL from the network, compare the downloaded script
88 // with an existing cache if provided, and report to CompareManager via calling
89 // ComparisonFinished().
90 class CompareNetwork final : public nsIStreamLoaderObserver,
91                              public nsIRequestObserver {
92  public:
93   NS_DECL_ISUPPORTS
94   NS_DECL_NSISTREAMLOADEROBSERVER
95   NS_DECL_NSIREQUESTOBSERVER
96 
CompareNetwork(CompareManager * aManager,ServiceWorkerRegistrationInfo * aRegistration,bool aIsMainScript)97   CompareNetwork(CompareManager* aManager,
98                  ServiceWorkerRegistrationInfo* aRegistration,
99                  bool aIsMainScript)
100       : mManager(aManager),
101         mRegistration(aRegistration),
102         mInternalHeaders(new InternalHeaders()),
103         mLoadFlags(nsIChannel::LOAD_BYPASS_SERVICE_WORKER),
104         mState(WaitingForInitialization),
105         mNetworkResult(NS_OK),
106         mCacheResult(NS_OK),
107         mIsMainScript(aIsMainScript),
108         mIsFromCache(false) {
109     MOZ_ASSERT(aManager);
110     MOZ_ASSERT(NS_IsMainThread());
111   }
112 
113   nsresult Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL,
114                       Cache* const aCache);
115 
116   void Abort();
117 
118   void NetworkFinish(nsresult aRv);
119 
120   void CacheFinish(nsresult aRv);
121 
URL() const122   const nsString& URL() const {
123     MOZ_ASSERT(NS_IsMainThread());
124     return mURL;
125   }
126 
Buffer() const127   const nsString& Buffer() const {
128     MOZ_ASSERT(NS_IsMainThread());
129     return mBuffer;
130   }
131 
GetChannelInfo() const132   const ChannelInfo& GetChannelInfo() const { return mChannelInfo; }
133 
GetInternalHeaders() const134   already_AddRefed<InternalHeaders> GetInternalHeaders() const {
135     RefPtr<InternalHeaders> internalHeaders = mInternalHeaders;
136     return internalHeaders.forget();
137   }
138 
TakePrincipalInfo()139   UniquePtr<PrincipalInfo> TakePrincipalInfo() {
140     return std::move(mPrincipalInfo);
141   }
142 
Succeeded() const143   bool Succeeded() const { return NS_SUCCEEDED(mNetworkResult); }
144 
URLList() const145   const nsTArray<nsCString>& URLList() const { return mURLList; }
146 
147  private:
~CompareNetwork()148   ~CompareNetwork() {
149     MOZ_ASSERT(NS_IsMainThread());
150     MOZ_ASSERT(!mCC);
151   }
152 
153   void Finish();
154 
155   nsresult SetPrincipalInfo(nsIChannel* aChannel);
156 
157   RefPtr<CompareManager> mManager;
158   RefPtr<CompareCache> mCC;
159   RefPtr<ServiceWorkerRegistrationInfo> mRegistration;
160 
161   nsCOMPtr<nsIChannel> mChannel;
162   nsString mBuffer;
163   nsString mURL;
164   ChannelInfo mChannelInfo;
165   RefPtr<InternalHeaders> mInternalHeaders;
166   UniquePtr<PrincipalInfo> mPrincipalInfo;
167   nsTArray<nsCString> mURLList;
168 
169   nsCString mMaxScope;
170   nsLoadFlags mLoadFlags;
171 
172   enum {
173     WaitingForInitialization,
174     WaitingForBothFinished,
175     WaitingForNetworkFinished,
176     WaitingForCacheFinished,
177     Finished
178   } mState;
179 
180   nsresult mNetworkResult;
181   nsresult mCacheResult;
182 
183   const bool mIsMainScript;
184   bool mIsFromCache;
185 };
186 
187 NS_IMPL_ISUPPORTS(CompareNetwork, nsIStreamLoaderObserver, nsIRequestObserver)
188 
189 // This class gets a cached Response from the CacheStorage and then it calls
190 // CacheFinish() in the CompareNetwork.
191 class CompareCache final : public PromiseNativeHandler,
192                            public nsIStreamLoaderObserver {
193  public:
194   NS_DECL_ISUPPORTS
195   NS_DECL_NSISTREAMLOADEROBSERVER
196 
CompareCache(CompareNetwork * aCN)197   explicit CompareCache(CompareNetwork* aCN)
198       : mCN(aCN), mState(WaitingForInitialization), mInCache(false) {
199     MOZ_ASSERT(aCN);
200     MOZ_ASSERT(NS_IsMainThread());
201   }
202 
203   nsresult Initialize(Cache* const aCache, const nsAString& aURL);
204 
205   void Finish(nsresult aStatus, bool aInCache);
206 
207   void Abort();
208 
209   virtual void ResolvedCallback(JSContext* aCx,
210                                 JS::Handle<JS::Value> aValue) override;
211 
212   virtual void RejectedCallback(JSContext* aCx,
213                                 JS::Handle<JS::Value> aValue) override;
214 
Buffer() const215   const nsString& Buffer() const {
216     MOZ_ASSERT(NS_IsMainThread());
217     return mBuffer;
218   }
219 
InCache()220   bool InCache() { return mInCache; }
221 
222  private:
~CompareCache()223   ~CompareCache() { MOZ_ASSERT(NS_IsMainThread()); }
224 
225   void ManageValueResult(JSContext* aCx, JS::Handle<JS::Value> aValue);
226 
227   RefPtr<CompareNetwork> mCN;
228   nsCOMPtr<nsIInputStreamPump> mPump;
229 
230   nsString mURL;
231   nsString mBuffer;
232 
233   enum {
234     WaitingForInitialization,
235     WaitingForScript,
236     Finished,
237   } mState;
238 
239   bool mInCache;
240 };
241 
242 NS_IMPL_ISUPPORTS(CompareCache, nsIStreamLoaderObserver)
243 
244 class CompareManager final : public PromiseNativeHandler {
245  public:
246   NS_DECL_ISUPPORTS
247 
CompareManager(ServiceWorkerRegistrationInfo * aRegistration,CompareCallback * aCallback)248   explicit CompareManager(ServiceWorkerRegistrationInfo* aRegistration,
249                           CompareCallback* aCallback)
250       : mRegistration(aRegistration),
251         mCallback(aCallback),
252         mLoadFlags(nsIChannel::LOAD_BYPASS_SERVICE_WORKER),
253         mState(WaitingForInitialization),
254         mPendingCount(0),
255         mOnFailure(OnFailure::DoNothing),
256         mAreScriptsEqual(true) {
257     MOZ_ASSERT(NS_IsMainThread());
258     MOZ_ASSERT(aRegistration);
259   }
260 
261   nsresult Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL,
262                       const nsAString& aCacheName);
263 
264   void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
265 
266   void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
267 
CacheStorage_()268   CacheStorage* CacheStorage_() {
269     MOZ_ASSERT(NS_IsMainThread());
270     MOZ_ASSERT(mCacheStorage);
271     return mCacheStorage;
272   }
273 
ComparisonFinished(nsresult aStatus,bool aIsMainScript,bool aIsEqual,const nsACString & aMaxScope,nsLoadFlags aLoadFlags)274   void ComparisonFinished(nsresult aStatus, bool aIsMainScript, bool aIsEqual,
275                           const nsACString& aMaxScope, nsLoadFlags aLoadFlags) {
276     MOZ_ASSERT(NS_IsMainThread());
277     if (mState == Finished) {
278       return;
279     }
280 
281     MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForScriptOrComparisonResult);
282 
283     if (NS_WARN_IF(NS_FAILED(aStatus))) {
284       Fail(aStatus);
285       return;
286     }
287 
288     mAreScriptsEqual = mAreScriptsEqual && aIsEqual;
289 
290     if (aIsMainScript) {
291       mMaxScope = aMaxScope;
292       mLoadFlags = aLoadFlags;
293     }
294 
295     // Check whether all CompareNetworks finished their jobs.
296     MOZ_DIAGNOSTIC_ASSERT(mPendingCount > 0);
297     if (--mPendingCount) {
298       return;
299     }
300 
301     if (mAreScriptsEqual) {
302       MOZ_ASSERT(mCallback);
303       mCallback->ComparisonResult(aStatus, true /* aSameScripts */, mOnFailure,
304                                   EmptyString(), mMaxScope, mLoadFlags);
305       Cleanup();
306       return;
307     }
308 
309     // Write to Cache so ScriptLoader reads succeed.
310     WriteNetworkBufferToNewCache();
311   }
312 
313  private:
~CompareManager()314   ~CompareManager() {
315     MOZ_ASSERT(NS_IsMainThread());
316     MOZ_ASSERT(mCNList.Length() == 0);
317   }
318 
319   void Fail(nsresult aStatus);
320 
321   void Cleanup();
322 
FetchScript(const nsAString & aURL,bool aIsMainScript,Cache * const aCache=nullptr)323   nsresult FetchScript(const nsAString& aURL, bool aIsMainScript,
324                        Cache* const aCache = nullptr) {
325     MOZ_ASSERT(NS_IsMainThread());
326 
327     MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForInitialization ||
328                           mState == WaitingForScriptOrComparisonResult);
329 
330     RefPtr<CompareNetwork> cn =
331         new CompareNetwork(this, mRegistration, aIsMainScript);
332     mCNList.AppendElement(cn);
333     mPendingCount += 1;
334 
335     nsresult rv = cn->Initialize(mPrincipal, aURL, aCache);
336     if (NS_WARN_IF(NS_FAILED(rv))) {
337       return rv;
338     }
339 
340     return NS_OK;
341   }
342 
ManageOldCache(JSContext * aCx,JS::Handle<JS::Value> aValue)343   void ManageOldCache(JSContext* aCx, JS::Handle<JS::Value> aValue) {
344     MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForExistingOpen);
345 
346     // RAII Cleanup when fails.
347     nsresult rv = NS_ERROR_FAILURE;
348     auto guard = MakeScopeExit([&] { Fail(rv); });
349 
350     if (NS_WARN_IF(!aValue.isObject())) {
351       return;
352     }
353 
354     MOZ_ASSERT(!mOldCache);
355     JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
356     if (NS_WARN_IF(!obj) ||
357         NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Cache, obj, mOldCache)))) {
358       return;
359     }
360 
361     Optional<RequestOrUSVString> request;
362     CacheQueryOptions options;
363     ErrorResult error;
364     RefPtr<Promise> promise = mOldCache->Keys(aCx, request, options, error);
365     if (NS_WARN_IF(error.Failed())) {
366       // No exception here because there are no ReadableStreams involved here.
367       MOZ_ASSERT(!error.IsJSException());
368       rv = error.StealNSResult();
369       return;
370     }
371 
372     mState = WaitingForExistingKeys;
373     promise->AppendNativeHandler(this);
374     guard.release();
375   }
376 
ManageOldKeys(JSContext * aCx,JS::Handle<JS::Value> aValue)377   void ManageOldKeys(JSContext* aCx, JS::Handle<JS::Value> aValue) {
378     MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForExistingKeys);
379 
380     // RAII Cleanup when fails.
381     nsresult rv = NS_ERROR_FAILURE;
382     auto guard = MakeScopeExit([&] { Fail(rv); });
383 
384     if (NS_WARN_IF(!aValue.isObject())) {
385       return;
386     }
387 
388     JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
389     if (NS_WARN_IF(!obj)) {
390       return;
391     }
392 
393     uint32_t len = 0;
394     if (!JS::GetArrayLength(aCx, obj, &len)) {
395       return;
396     }
397 
398     // Fetch and compare the source scripts.
399     MOZ_ASSERT(mPendingCount == 0);
400 
401     mState = WaitingForScriptOrComparisonResult;
402 
403     bool hasMainScript = false;
404     AutoTArray<nsString, 8> urlList;
405 
406     // Extract the list of URLs in the old cache.
407     for (uint32_t i = 0; i < len; ++i) {
408       JS::Rooted<JS::Value> val(aCx);
409       if (NS_WARN_IF(!JS_GetElement(aCx, obj, i, &val)) ||
410           NS_WARN_IF(!val.isObject())) {
411         return;
412       }
413 
414       Request* request;
415       JS::Rooted<JSObject*> requestObj(aCx, &val.toObject());
416       if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Request, &requestObj, request)))) {
417         return;
418       };
419 
420       nsString url;
421       request->GetUrl(url);
422 
423       if (!hasMainScript && url == mURL) {
424         hasMainScript = true;
425       }
426 
427       urlList.AppendElement(url);
428     }
429 
430     // If the main script is missing, then something has gone wrong.  We
431     // will try to continue with the update process to trigger a new
432     // installation.  If that fails, however, then uninstall the registration
433     // because it is broken in a way that cannot be fixed.
434     if (!hasMainScript) {
435       mOnFailure = OnFailure::Uninstall;
436     }
437 
438     // Always make sure to fetch the main script.  If the old cache has
439     // no entries or the main script entry is missing, then the loop below
440     // may not trigger it.  This should not really happen, but we handle it
441     // gracefully if it does occur.  Its possible the bad cache state is due
442     // to a crash or shutdown during an update, etc.
443     rv = FetchScript(mURL, true /* aIsMainScript */, mOldCache);
444     if (NS_WARN_IF(NS_FAILED(rv))) {
445       return;
446     }
447 
448     for (const auto& url : urlList) {
449       // We explicitly start the fetch for the main script above.
450       if (mURL == url) {
451         continue;
452       }
453 
454       rv = FetchScript(url, false /* aIsMainScript */, mOldCache);
455       if (NS_WARN_IF(NS_FAILED(rv))) {
456         return;
457       }
458     }
459 
460     guard.release();
461   }
462 
ManageNewCache(JSContext * aCx,JS::Handle<JS::Value> aValue)463   void ManageNewCache(JSContext* aCx, JS::Handle<JS::Value> aValue) {
464     MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForOpen);
465 
466     // RAII Cleanup when fails.
467     nsresult rv = NS_ERROR_FAILURE;
468     auto guard = MakeScopeExit([&] { Fail(rv); });
469 
470     if (NS_WARN_IF(!aValue.isObject())) {
471       return;
472     }
473 
474     JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
475     if (NS_WARN_IF(!obj)) {
476       return;
477     }
478 
479     Cache* cache = nullptr;
480     rv = UNWRAP_OBJECT(Cache, &obj, cache);
481     if (NS_WARN_IF(NS_FAILED(rv))) {
482       return;
483     }
484 
485     // Just to be safe.
486     RefPtr<Cache> kungfuDeathGrip = cache;
487 
488     MOZ_ASSERT(mPendingCount == 0);
489     for (uint32_t i = 0; i < mCNList.Length(); ++i) {
490       // We bail out immediately when something goes wrong.
491       rv = WriteToCache(aCx, cache, mCNList[i]);
492       if (NS_WARN_IF(NS_FAILED(rv))) {
493         return;
494       }
495     }
496 
497     mState = WaitingForPut;
498     guard.release();
499   }
500 
WriteNetworkBufferToNewCache()501   void WriteNetworkBufferToNewCache() {
502     MOZ_ASSERT(NS_IsMainThread());
503     MOZ_ASSERT(mCNList.Length() != 0);
504     MOZ_ASSERT(mCacheStorage);
505     MOZ_ASSERT(mNewCacheName.IsEmpty());
506 
507     ErrorResult result;
508     result = serviceWorkerScriptCache::GenerateCacheName(mNewCacheName);
509     if (NS_WARN_IF(result.Failed())) {
510       MOZ_ASSERT(!result.IsErrorWithMessage());
511       Fail(result.StealNSResult());
512       return;
513     }
514 
515     RefPtr<Promise> cacheOpenPromise =
516         mCacheStorage->Open(mNewCacheName, result);
517     if (NS_WARN_IF(result.Failed())) {
518       MOZ_ASSERT(!result.IsErrorWithMessage());
519       Fail(result.StealNSResult());
520       return;
521     }
522 
523     mState = WaitingForOpen;
524     cacheOpenPromise->AppendNativeHandler(this);
525   }
526 
WriteToCache(JSContext * aCx,Cache * aCache,CompareNetwork * aCN)527   nsresult WriteToCache(JSContext* aCx, Cache* aCache, CompareNetwork* aCN) {
528     MOZ_ASSERT(NS_IsMainThread());
529     MOZ_ASSERT(aCache);
530     MOZ_ASSERT(aCN);
531     MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForOpen);
532 
533     // We don't have to save any information from a failed CompareNetwork.
534     if (!aCN->Succeeded()) {
535       return NS_OK;
536     }
537 
538     nsCOMPtr<nsIInputStream> body;
539     nsresult rv = NS_NewCStringInputStream(
540         getter_AddRefs(body), NS_ConvertUTF16toUTF8(aCN->Buffer()));
541     if (NS_WARN_IF(NS_FAILED(rv))) {
542       return rv;
543     }
544 
545     RefPtr<InternalResponse> ir =
546         new InternalResponse(200, NS_LITERAL_CSTRING("OK"));
547     ir->SetBody(body, aCN->Buffer().Length());
548     ir->SetURLList(aCN->URLList());
549 
550     ir->InitChannelInfo(aCN->GetChannelInfo());
551     UniquePtr<PrincipalInfo> principalInfo = aCN->TakePrincipalInfo();
552     if (principalInfo) {
553       ir->SetPrincipalInfo(std::move(principalInfo));
554     }
555 
556     RefPtr<InternalHeaders> internalHeaders = aCN->GetInternalHeaders();
557     ir->Headers()->Fill(*(internalHeaders.get()), IgnoreErrors());
558 
559     RefPtr<Response> response =
560         new Response(aCache->GetGlobalObject(), ir, nullptr);
561 
562     RequestOrUSVString request;
563     request.SetAsUSVString().ShareOrDependUpon(aCN->URL());
564 
565     // For now we have to wait until the Put Promise is fulfilled before we can
566     // continue since Cache does not yet support starting a read that is being
567     // written to.
568     ErrorResult result;
569     RefPtr<Promise> cachePromise = aCache->Put(aCx, request, *response, result);
570     result.WouldReportJSException();
571     if (NS_WARN_IF(result.Failed())) {
572       // No exception here because there are no ReadableStreams involved here.
573       MOZ_ASSERT(!result.IsJSException());
574       MOZ_ASSERT(!result.IsErrorWithMessage());
575       return result.StealNSResult();
576     }
577 
578     mPendingCount += 1;
579     cachePromise->AppendNativeHandler(this);
580     return NS_OK;
581   }
582 
583   RefPtr<ServiceWorkerRegistrationInfo> mRegistration;
584   RefPtr<CompareCallback> mCallback;
585   RefPtr<CacheStorage> mCacheStorage;
586 
587   nsTArray<RefPtr<CompareNetwork>> mCNList;
588 
589   nsString mURL;
590   RefPtr<nsIPrincipal> mPrincipal;
591 
592   // Used for the old cache where saves the old source scripts.
593   RefPtr<Cache> mOldCache;
594 
595   // Only used if the network script has changed and needs to be cached.
596   nsString mNewCacheName;
597 
598   nsCString mMaxScope;
599   nsLoadFlags mLoadFlags;
600 
601   enum {
602     WaitingForInitialization,
603     WaitingForExistingOpen,
604     WaitingForExistingKeys,
605     WaitingForScriptOrComparisonResult,
606     WaitingForOpen,
607     WaitingForPut,
608     Finished
609   } mState;
610 
611   uint32_t mPendingCount;
612   OnFailure mOnFailure;
613   bool mAreScriptsEqual;
614 };
615 
NS_IMPL_ISUPPORTS0(CompareManager)616 NS_IMPL_ISUPPORTS0(CompareManager)
617 
618 nsresult CompareNetwork::Initialize(nsIPrincipal* aPrincipal,
619                                     const nsAString& aURL,
620                                     Cache* const aCache) {
621   MOZ_ASSERT(aPrincipal);
622   MOZ_ASSERT(NS_IsMainThread());
623 
624   nsCOMPtr<nsIURI> uri;
625   nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL);
626   if (NS_WARN_IF(NS_FAILED(rv))) {
627     return rv;
628   }
629 
630   mURL = aURL;
631   mURLList.AppendElement(NS_ConvertUTF16toUTF8(mURL));
632 
633   nsCOMPtr<nsILoadGroup> loadGroup;
634   rv = NS_NewLoadGroup(getter_AddRefs(loadGroup), aPrincipal);
635   if (NS_WARN_IF(NS_FAILED(rv))) {
636     return rv;
637   }
638 
639   // Update LoadFlags for propagating to ServiceWorkerInfo.
640   mLoadFlags = nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
641 
642   ServiceWorkerUpdateViaCache uvc = mRegistration->GetUpdateViaCache();
643   if (uvc == ServiceWorkerUpdateViaCache::None ||
644       (uvc == ServiceWorkerUpdateViaCache::Imports && mIsMainScript)) {
645     mLoadFlags |= nsIRequest::VALIDATE_ALWAYS;
646   }
647 
648   if (mRegistration->IsLastUpdateCheckTimeOverOneDay()) {
649     mLoadFlags |= nsIRequest::LOAD_BYPASS_CACHE;
650   }
651 
652   // Different settings are needed for fetching imported scripts, since they
653   // might be cross-origin scripts.
654   uint32_t secFlags = mIsMainScript
655                           ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
656                           : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
657 
658   nsContentPolicyType contentPolicyType =
659       mIsMainScript ? nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER
660                     : nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS;
661 
662   // Create a new cookieJarSettings.
663   nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
664       mozilla::net::CookieJarSettings::Create();
665 
666   // Note that because there is no "serviceworker" RequestContext type, we can
667   // use the TYPE_INTERNAL_SCRIPT content policy types when loading a service
668   // worker.
669   rv = NS_NewChannel(getter_AddRefs(mChannel), uri, aPrincipal, secFlags,
670                      contentPolicyType, cookieJarSettings,
671                      nullptr /* aPerformanceStorage */, loadGroup,
672                      nullptr /* aCallbacks */, mLoadFlags);
673   if (NS_WARN_IF(NS_FAILED(rv))) {
674     return rv;
675   }
676 
677   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
678   if (httpChannel) {
679     // Spec says no redirects allowed for top-level SW scripts.
680     if (mIsMainScript) {
681       rv = httpChannel->SetRedirectionLimit(0);
682       MOZ_ASSERT(NS_SUCCEEDED(rv));
683     }
684 
685     rv = httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Service-Worker"),
686                                        NS_LITERAL_CSTRING("script"),
687                                        /* merge */ false);
688     MOZ_ASSERT(NS_SUCCEEDED(rv));
689   }
690 
691   nsCOMPtr<nsIStreamLoader> loader;
692   rv = NS_NewStreamLoader(getter_AddRefs(loader), this, this);
693   if (NS_WARN_IF(NS_FAILED(rv))) {
694     return rv;
695   }
696 
697   rv = mChannel->AsyncOpen(loader);
698   if (NS_WARN_IF(NS_FAILED(rv))) {
699     return rv;
700   }
701 
702   // If we do have an existing cache to compare with.
703   if (aCache) {
704     mCC = new CompareCache(this);
705     rv = mCC->Initialize(aCache, aURL);
706     if (NS_WARN_IF(NS_FAILED(rv))) {
707       Abort();
708       return rv;
709     }
710 
711     mState = WaitingForBothFinished;
712     return NS_OK;
713   }
714 
715   mState = WaitingForNetworkFinished;
716   return NS_OK;
717 }
718 
Finish()719 void CompareNetwork::Finish() {
720   if (mState == Finished) {
721     return;
722   }
723 
724   bool same = true;
725   nsresult rv = NS_OK;
726 
727   // mNetworkResult is prior to mCacheResult, since it's needed for reporting
728   // various errors to web content.
729   if (NS_FAILED(mNetworkResult)) {
730     // An imported script could become offline, since it might no longer be
731     // needed by the new importing script. In that case, the importing script
732     // must be different, and thus, it's okay to report same script found here.
733     rv = mIsMainScript ? mNetworkResult : NS_OK;
734     same = true;
735   } else if (mCC && NS_FAILED(mCacheResult)) {
736     rv = mCacheResult;
737   } else {  // Both passed.
738     same = mCC && mCC->InCache() && mCC->Buffer().Equals(mBuffer);
739   }
740 
741   mManager->ComparisonFinished(rv, mIsMainScript, same, mMaxScope, mLoadFlags);
742 
743   // We have done with the CompareCache.
744   mCC = nullptr;
745 }
746 
NetworkFinish(nsresult aRv)747 void CompareNetwork::NetworkFinish(nsresult aRv) {
748   MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForBothFinished ||
749                         mState == WaitingForNetworkFinished);
750 
751   mNetworkResult = aRv;
752 
753   if (mState == WaitingForBothFinished) {
754     mState = WaitingForCacheFinished;
755     return;
756   }
757 
758   if (mState == WaitingForNetworkFinished) {
759     Finish();
760     return;
761   }
762 }
763 
CacheFinish(nsresult aRv)764 void CompareNetwork::CacheFinish(nsresult aRv) {
765   MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForBothFinished ||
766                         mState == WaitingForCacheFinished);
767 
768   mCacheResult = aRv;
769 
770   if (mState == WaitingForBothFinished) {
771     mState = WaitingForNetworkFinished;
772     return;
773   }
774 
775   if (mState == WaitingForCacheFinished) {
776     Finish();
777     return;
778   }
779 }
780 
Abort()781 void CompareNetwork::Abort() {
782   MOZ_ASSERT(NS_IsMainThread());
783 
784   if (mState != Finished) {
785     mState = Finished;
786 
787     MOZ_ASSERT(mChannel);
788     mChannel->Cancel(NS_BINDING_ABORTED);
789     mChannel = nullptr;
790 
791     if (mCC) {
792       mCC->Abort();
793       mCC = nullptr;
794     }
795   }
796 }
797 
798 NS_IMETHODIMP
OnStartRequest(nsIRequest * aRequest)799 CompareNetwork::OnStartRequest(nsIRequest* aRequest) {
800   MOZ_ASSERT(NS_IsMainThread());
801 
802   if (mState == Finished) {
803     return NS_OK;
804   }
805 
806   nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
807   MOZ_ASSERT_IF(mIsMainScript, channel == mChannel);
808   mChannel = channel;
809 
810   MOZ_ASSERT(!mChannelInfo.IsInitialized());
811   mChannelInfo.InitFromChannel(mChannel);
812 
813   nsresult rv = SetPrincipalInfo(mChannel);
814   if (NS_WARN_IF(NS_FAILED(rv))) {
815     return rv;
816   }
817 
818   mInternalHeaders->FillResponseHeaders(mChannel);
819 
820   nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(channel));
821   if (cacheChannel) {
822     cacheChannel->IsFromCache(&mIsFromCache);
823   }
824 
825   return NS_OK;
826 }
827 
SetPrincipalInfo(nsIChannel * aChannel)828 nsresult CompareNetwork::SetPrincipalInfo(nsIChannel* aChannel) {
829   nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
830   if (!ssm) {
831     return NS_ERROR_FAILURE;
832   }
833 
834   nsCOMPtr<nsIPrincipal> channelPrincipal;
835   nsresult rv = ssm->GetChannelResultPrincipal(
836       aChannel, getter_AddRefs(channelPrincipal));
837   if (NS_WARN_IF(NS_FAILED(rv))) {
838     return rv;
839   }
840 
841   UniquePtr<PrincipalInfo> principalInfo = MakeUnique<PrincipalInfo>();
842   rv = PrincipalToPrincipalInfo(channelPrincipal, principalInfo.get());
843 
844   if (NS_WARN_IF(NS_FAILED(rv))) {
845     return rv;
846   }
847 
848   mPrincipalInfo = std::move(principalInfo);
849   return NS_OK;
850 }
851 
852 NS_IMETHODIMP
OnStopRequest(nsIRequest * aRequest,nsresult aStatusCode)853 CompareNetwork::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
854   // Nothing to do here!
855   return NS_OK;
856 }
857 
858 NS_IMETHODIMP
OnStreamComplete(nsIStreamLoader * aLoader,nsISupports * aContext,nsresult aStatus,uint32_t aLen,const uint8_t * aString)859 CompareNetwork::OnStreamComplete(nsIStreamLoader* aLoader,
860                                  nsISupports* aContext, nsresult aStatus,
861                                  uint32_t aLen, const uint8_t* aString) {
862   MOZ_ASSERT(NS_IsMainThread());
863 
864   if (mState == Finished) {
865     return NS_OK;
866   }
867 
868   nsresult rv = NS_ERROR_FAILURE;
869   auto guard = MakeScopeExit([&] { NetworkFinish(rv); });
870 
871   if (NS_WARN_IF(NS_FAILED(aStatus))) {
872     rv = (aStatus == NS_ERROR_REDIRECT_LOOP) ? NS_ERROR_DOM_SECURITY_ERR
873                                              : aStatus;
874     return NS_OK;
875   }
876 
877   nsCOMPtr<nsIRequest> request;
878   rv = aLoader->GetRequest(getter_AddRefs(request));
879   if (NS_WARN_IF(NS_FAILED(rv))) {
880     return NS_OK;
881   }
882 
883   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request);
884   MOZ_ASSERT(httpChannel, "How come we don't have an HTTP channel?");
885 
886   bool requestSucceeded;
887   rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
888   if (NS_WARN_IF(NS_FAILED(rv))) {
889     return NS_OK;
890   }
891 
892   if (NS_WARN_IF(!requestSucceeded)) {
893     // Get the stringified numeric status code, not statusText which could be
894     // something misleading like OK for a 404.
895     uint32_t status = 0;
896     Unused << httpChannel->GetResponseStatus(
897         &status);  // don't care if this fails, use 0.
898     nsAutoString statusAsText;
899     statusAsText.AppendInt(status);
900 
901     ServiceWorkerManager::LocalizeAndReportToAllClients(
902         mRegistration->Scope(), "ServiceWorkerRegisterNetworkError",
903         nsTArray<nsString>{NS_ConvertUTF8toUTF16(mRegistration->Scope()),
904                            statusAsText, mURL});
905 
906     rv = NS_ERROR_FAILURE;
907     return NS_OK;
908   }
909 
910   // Note: we explicitly don't check for the return value here, because the
911   // absence of the header is not an error condition.
912   Unused << httpChannel->GetResponseHeader(
913       NS_LITERAL_CSTRING("Service-Worker-Allowed"), mMaxScope);
914 
915   // [9.2 Update]4.13, If response's cache state is not "local",
916   // set registration's last update check time to the current time
917   if (!mIsFromCache) {
918     mRegistration->RefreshLastUpdateCheckTime();
919   }
920 
921   nsAutoCString mimeType;
922   rv = httpChannel->GetContentType(mimeType);
923   if (NS_WARN_IF(NS_FAILED(rv))) {
924     // We should only end up here if !mResponseHead in the channel.  If headers
925     // were received but no content type was specified, we'll be given
926     // UNKNOWN_CONTENT_TYPE "application/x-unknown-content-type" and so fall
927     // into the next case with its better error message.
928     rv = NS_ERROR_DOM_SECURITY_ERR;
929     return rv;
930   }
931 
932   if (mimeType.IsEmpty() ||
933       !nsContentUtils::IsJavascriptMIMEType(NS_ConvertUTF8toUTF16(mimeType))) {
934     ServiceWorkerManager::LocalizeAndReportToAllClients(
935         mRegistration->Scope(), "ServiceWorkerRegisterMimeTypeError2",
936         nsTArray<nsString>{NS_ConvertUTF8toUTF16(mRegistration->Scope()),
937                            NS_ConvertUTF8toUTF16(mimeType), mURL});
938     rv = NS_ERROR_DOM_SECURITY_ERR;
939     return rv;
940   }
941 
942   nsCOMPtr<nsIURI> channelURL;
943   rv = httpChannel->GetURI(getter_AddRefs(channelURL));
944   if (NS_WARN_IF(NS_FAILED(rv))) {
945     return rv;
946   }
947 
948   nsCString channelURLSpec;
949   MOZ_ALWAYS_SUCCEEDS(channelURL->GetSpec(channelURLSpec));
950 
951   // Append the final URL if its different from the original
952   // request URL.  This lets us note that a redirect occurred
953   // even though we don't track every redirect URL here.
954   MOZ_DIAGNOSTIC_ASSERT(!mURLList.IsEmpty());
955   if (channelURLSpec != mURLList[0]) {
956     mURLList.AppendElement(channelURLSpec);
957   }
958 
959   char16_t* buffer = nullptr;
960   size_t len = 0;
961 
962   rv = ScriptLoader::ConvertToUTF16(httpChannel, aString, aLen,
963                                     NS_LITERAL_STRING("UTF-8"), nullptr, buffer,
964                                     len);
965   if (NS_WARN_IF(NS_FAILED(rv))) {
966     return rv;
967   }
968 
969   mBuffer.Adopt(buffer, len);
970 
971   rv = NS_OK;
972   return NS_OK;
973 }
974 
Initialize(Cache * const aCache,const nsAString & aURL)975 nsresult CompareCache::Initialize(Cache* const aCache, const nsAString& aURL) {
976   MOZ_ASSERT(NS_IsMainThread());
977   MOZ_ASSERT(aCache);
978   MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForInitialization);
979 
980   // This JSContext will not end up executing JS code because here there are
981   // no ReadableStreams involved.
982   AutoJSAPI jsapi;
983   jsapi.Init();
984 
985   RequestOrUSVString request;
986   request.SetAsUSVString().ShareOrDependUpon(aURL);
987   ErrorResult error;
988   CacheQueryOptions params;
989   RefPtr<Promise> promise = aCache->Match(jsapi.cx(), request, params, error);
990   if (NS_WARN_IF(error.Failed())) {
991     // No exception here because there are no ReadableStreams involved here.
992     MOZ_ASSERT(!error.IsJSException());
993     mState = Finished;
994     return error.StealNSResult();
995   }
996 
997   // Retrieve the script from aCache.
998   mState = WaitingForScript;
999   promise->AppendNativeHandler(this);
1000   return NS_OK;
1001 }
1002 
Finish(nsresult aStatus,bool aInCache)1003 void CompareCache::Finish(nsresult aStatus, bool aInCache) {
1004   if (mState != Finished) {
1005     mState = Finished;
1006     mInCache = aInCache;
1007     mCN->CacheFinish(aStatus);
1008   }
1009 }
1010 
Abort()1011 void CompareCache::Abort() {
1012   MOZ_ASSERT(NS_IsMainThread());
1013 
1014   if (mState != Finished) {
1015     mState = Finished;
1016 
1017     if (mPump) {
1018       mPump->Cancel(NS_BINDING_ABORTED);
1019       mPump = nullptr;
1020     }
1021   }
1022 }
1023 
1024 NS_IMETHODIMP
OnStreamComplete(nsIStreamLoader * aLoader,nsISupports * aContext,nsresult aStatus,uint32_t aLen,const uint8_t * aString)1025 CompareCache::OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext,
1026                                nsresult aStatus, uint32_t aLen,
1027                                const uint8_t* aString) {
1028   MOZ_ASSERT(NS_IsMainThread());
1029 
1030   if (mState == Finished) {
1031     return aStatus;
1032   }
1033 
1034   if (NS_WARN_IF(NS_FAILED(aStatus))) {
1035     Finish(aStatus, false);
1036     return aStatus;
1037   }
1038 
1039   char16_t* buffer = nullptr;
1040   size_t len = 0;
1041 
1042   nsresult rv = ScriptLoader::ConvertToUTF16(
1043       nullptr, aString, aLen, NS_LITERAL_STRING("UTF-8"), nullptr, buffer, len);
1044   if (NS_WARN_IF(NS_FAILED(rv))) {
1045     Finish(rv, false);
1046     return rv;
1047   }
1048 
1049   mBuffer.Adopt(buffer, len);
1050 
1051   Finish(NS_OK, true);
1052   return NS_OK;
1053 }
1054 
ResolvedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue)1055 void CompareCache::ResolvedCallback(JSContext* aCx,
1056                                     JS::Handle<JS::Value> aValue) {
1057   MOZ_ASSERT(NS_IsMainThread());
1058 
1059   switch (mState) {
1060     case Finished:
1061       return;
1062     case WaitingForScript:
1063       ManageValueResult(aCx, aValue);
1064       return;
1065     default:
1066       MOZ_CRASH("Unacceptable state.");
1067   }
1068 }
1069 
RejectedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue)1070 void CompareCache::RejectedCallback(JSContext* aCx,
1071                                     JS::Handle<JS::Value> aValue) {
1072   MOZ_ASSERT(NS_IsMainThread());
1073 
1074   if (mState != Finished) {
1075     Finish(NS_ERROR_FAILURE, false);
1076     return;
1077   }
1078 }
1079 
ManageValueResult(JSContext * aCx,JS::Handle<JS::Value> aValue)1080 void CompareCache::ManageValueResult(JSContext* aCx,
1081                                      JS::Handle<JS::Value> aValue) {
1082   MOZ_ASSERT(NS_IsMainThread());
1083 
1084   // The cache returns undefined if the object is not stored.
1085   if (aValue.isUndefined()) {
1086     Finish(NS_OK, false);
1087     return;
1088   }
1089 
1090   MOZ_ASSERT(aValue.isObject());
1091 
1092   JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
1093   if (NS_WARN_IF(!obj)) {
1094     Finish(NS_ERROR_FAILURE, false);
1095     return;
1096   }
1097 
1098   Response* response = nullptr;
1099   nsresult rv = UNWRAP_OBJECT(Response, &obj, response);
1100   if (NS_WARN_IF(NS_FAILED(rv))) {
1101     Finish(rv, false);
1102     return;
1103   }
1104 
1105   MOZ_ASSERT(response->Ok());
1106 
1107   nsCOMPtr<nsIInputStream> inputStream;
1108   response->GetBody(getter_AddRefs(inputStream));
1109   MOZ_ASSERT(inputStream);
1110 
1111   MOZ_ASSERT(!mPump);
1112   rv = NS_NewInputStreamPump(getter_AddRefs(mPump), inputStream.forget(),
1113                              0,     /* default segsize */
1114                              0,     /* default segcount */
1115                              false, /* default closeWhenDone */
1116                              GetMainThreadSerialEventTarget());
1117   if (NS_WARN_IF(NS_FAILED(rv))) {
1118     Finish(rv, false);
1119     return;
1120   }
1121 
1122   nsCOMPtr<nsIStreamLoader> loader;
1123   rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
1124   if (NS_WARN_IF(NS_FAILED(rv))) {
1125     Finish(rv, false);
1126     return;
1127   }
1128 
1129   rv = mPump->AsyncRead(loader, nullptr);
1130   if (NS_WARN_IF(NS_FAILED(rv))) {
1131     mPump = nullptr;
1132     Finish(rv, false);
1133     return;
1134   }
1135 
1136   nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(mPump);
1137   if (rr) {
1138     nsCOMPtr<nsIEventTarget> sts =
1139         do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
1140     rv = rr->RetargetDeliveryTo(sts);
1141     if (NS_WARN_IF(NS_FAILED(rv))) {
1142       mPump = nullptr;
1143       Finish(rv, false);
1144       return;
1145     }
1146   }
1147 }
1148 
Initialize(nsIPrincipal * aPrincipal,const nsAString & aURL,const nsAString & aCacheName)1149 nsresult CompareManager::Initialize(nsIPrincipal* aPrincipal,
1150                                     const nsAString& aURL,
1151                                     const nsAString& aCacheName) {
1152   MOZ_ASSERT(NS_IsMainThread());
1153   MOZ_ASSERT(aPrincipal);
1154   MOZ_ASSERT(mPendingCount == 0);
1155   MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForInitialization);
1156 
1157   // RAII Cleanup when fails.
1158   auto guard = MakeScopeExit([&] { Cleanup(); });
1159 
1160   mURL = aURL;
1161   mPrincipal = aPrincipal;
1162 
1163   // Always create a CacheStorage since we want to write the network entry to
1164   // the cache even if there isn't an existing one.
1165   AutoJSAPI jsapi;
1166   jsapi.Init();
1167   ErrorResult result;
1168   mCacheStorage = CreateCacheStorage(jsapi.cx(), aPrincipal, result);
1169   if (NS_WARN_IF(result.Failed())) {
1170     MOZ_ASSERT(!result.IsErrorWithMessage());
1171     return result.StealNSResult();
1172   }
1173 
1174   // If there is no existing cache, proceed to fetch the script directly.
1175   if (aCacheName.IsEmpty()) {
1176     mState = WaitingForScriptOrComparisonResult;
1177     nsresult rv = FetchScript(aURL, true /* aIsMainScript */);
1178     if (NS_WARN_IF(NS_FAILED(rv))) {
1179       return rv;
1180     }
1181 
1182     guard.release();
1183     return NS_OK;
1184   }
1185 
1186   // Open the cache saving the old source scripts.
1187   RefPtr<Promise> promise = mCacheStorage->Open(aCacheName, result);
1188   if (NS_WARN_IF(result.Failed())) {
1189     MOZ_ASSERT(!result.IsErrorWithMessage());
1190     return result.StealNSResult();
1191   }
1192 
1193   mState = WaitingForExistingOpen;
1194   promise->AppendNativeHandler(this);
1195 
1196   guard.release();
1197   return NS_OK;
1198 }
1199 
1200 // This class manages 4 promises if needed:
1201 // 1. Retrieve the Cache object by a given CacheName of OldCache.
1202 // 2. Retrieve the URLs saved in OldCache.
1203 // 3. Retrieve the Cache object of the NewCache for the newly created SW.
1204 // 4. Put the value in the cache.
1205 // For this reason we have mState to know what callback we are handling.
ResolvedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue)1206 void CompareManager::ResolvedCallback(JSContext* aCx,
1207                                       JS::Handle<JS::Value> aValue) {
1208   MOZ_ASSERT(NS_IsMainThread());
1209   MOZ_ASSERT(mCallback);
1210 
1211   switch (mState) {
1212     case Finished:
1213       return;
1214     case WaitingForExistingOpen:
1215       ManageOldCache(aCx, aValue);
1216       return;
1217     case WaitingForExistingKeys:
1218       ManageOldKeys(aCx, aValue);
1219       return;
1220     case WaitingForOpen:
1221       ManageNewCache(aCx, aValue);
1222       return;
1223     case WaitingForPut:
1224       MOZ_DIAGNOSTIC_ASSERT(mPendingCount > 0);
1225       if (--mPendingCount == 0) {
1226         mCallback->ComparisonResult(NS_OK, false /* aIsEqual */, mOnFailure,
1227                                     mNewCacheName, mMaxScope, mLoadFlags);
1228         Cleanup();
1229       }
1230       return;
1231     default:
1232       MOZ_DIAGNOSTIC_ASSERT(false);
1233   }
1234 }
1235 
RejectedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue)1236 void CompareManager::RejectedCallback(JSContext* aCx,
1237                                       JS::Handle<JS::Value> aValue) {
1238   MOZ_ASSERT(NS_IsMainThread());
1239   switch (mState) {
1240     case Finished:
1241       return;
1242     case WaitingForExistingOpen:
1243       NS_WARNING("Could not open the existing cache.");
1244       break;
1245     case WaitingForExistingKeys:
1246       NS_WARNING("Could not get the existing URLs.");
1247       break;
1248     case WaitingForOpen:
1249       NS_WARNING("Could not open cache.");
1250       break;
1251     case WaitingForPut:
1252       NS_WARNING("Could not write to cache.");
1253       break;
1254     default:
1255       MOZ_DIAGNOSTIC_ASSERT(false);
1256   }
1257 
1258   Fail(NS_ERROR_FAILURE);
1259 }
1260 
Fail(nsresult aStatus)1261 void CompareManager::Fail(nsresult aStatus) {
1262   MOZ_ASSERT(NS_IsMainThread());
1263   mCallback->ComparisonResult(aStatus, false /* aIsEqual */, mOnFailure,
1264                               EmptyString(), EmptyCString(), mLoadFlags);
1265   Cleanup();
1266 }
1267 
Cleanup()1268 void CompareManager::Cleanup() {
1269   MOZ_ASSERT(NS_IsMainThread());
1270 
1271   if (mState != Finished) {
1272     mState = Finished;
1273 
1274     MOZ_ASSERT(mCallback);
1275     mCallback = nullptr;
1276 
1277     // Abort and release CompareNetworks.
1278     for (uint32_t i = 0; i < mCNList.Length(); ++i) {
1279       mCNList[i]->Abort();
1280     }
1281     mCNList.Clear();
1282   }
1283 }
1284 
1285 class NoopPromiseHandler final : public PromiseNativeHandler {
1286  public:
1287   NS_DECL_ISUPPORTS
1288 
NoopPromiseHandler()1289   NoopPromiseHandler() { AssertIsOnMainThread(); }
1290 
ResolvedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue)1291   void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
1292   }
RejectedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue)1293   void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
1294   }
1295 
1296  private:
~NoopPromiseHandler()1297   ~NoopPromiseHandler() { AssertIsOnMainThread(); }
1298 };
1299 
1300 NS_IMPL_ISUPPORTS0(NoopPromiseHandler)
1301 
1302 }  // namespace
1303 
PurgeCache(nsIPrincipal * aPrincipal,const nsAString & aCacheName)1304 nsresult PurgeCache(nsIPrincipal* aPrincipal, const nsAString& aCacheName) {
1305   MOZ_ASSERT(NS_IsMainThread());
1306   MOZ_ASSERT(aPrincipal);
1307 
1308   if (aCacheName.IsEmpty()) {
1309     return NS_OK;
1310   }
1311 
1312   AutoJSAPI jsapi;
1313   jsapi.Init();
1314   ErrorResult rv;
1315   RefPtr<CacheStorage> cacheStorage =
1316       CreateCacheStorage(jsapi.cx(), aPrincipal, rv);
1317   if (NS_WARN_IF(rv.Failed())) {
1318     return rv.StealNSResult();
1319   }
1320 
1321   // We use the ServiceWorker scope as key for the cacheStorage.
1322   RefPtr<Promise> promise = cacheStorage->Delete(aCacheName, rv);
1323   if (NS_WARN_IF(rv.Failed())) {
1324     return rv.StealNSResult();
1325   }
1326 
1327   // Add a no-op promise handler to ensure that if this promise gets rejected,
1328   // we don't end up reporting a rejected promise to the console.
1329   RefPtr<NoopPromiseHandler> promiseHandler = new NoopPromiseHandler();
1330   promise->AppendNativeHandler(promiseHandler);
1331 
1332   // We don't actually care about the result of the delete operation.
1333   return NS_OK;
1334 }
1335 
GenerateCacheName(nsAString & aName)1336 nsresult GenerateCacheName(nsAString& aName) {
1337   nsresult rv;
1338   nsCOMPtr<nsIUUIDGenerator> uuidGenerator =
1339       do_GetService("@mozilla.org/uuid-generator;1", &rv);
1340   if (NS_WARN_IF(NS_FAILED(rv))) {
1341     return rv;
1342   }
1343 
1344   nsID id;
1345   rv = uuidGenerator->GenerateUUIDInPlace(&id);
1346   if (NS_WARN_IF(NS_FAILED(rv))) {
1347     return rv;
1348   }
1349 
1350   char chars[NSID_LENGTH];
1351   id.ToProvidedString(chars);
1352 
1353   // NSID_LENGTH counts the null terminator.
1354   aName.AssignASCII(chars, NSID_LENGTH - 1);
1355 
1356   return NS_OK;
1357 }
1358 
Compare(ServiceWorkerRegistrationInfo * aRegistration,nsIPrincipal * aPrincipal,const nsAString & aCacheName,const nsAString & aURL,CompareCallback * aCallback)1359 nsresult Compare(ServiceWorkerRegistrationInfo* aRegistration,
1360                  nsIPrincipal* aPrincipal, const nsAString& aCacheName,
1361                  const nsAString& aURL, CompareCallback* aCallback) {
1362   MOZ_ASSERT(NS_IsMainThread());
1363   MOZ_ASSERT(aRegistration);
1364   MOZ_ASSERT(aPrincipal);
1365   MOZ_ASSERT(!aURL.IsEmpty());
1366   MOZ_ASSERT(aCallback);
1367 
1368   RefPtr<CompareManager> cm = new CompareManager(aRegistration, aCallback);
1369 
1370   nsresult rv = cm->Initialize(aPrincipal, aURL, aCacheName);
1371   if (NS_WARN_IF(NS_FAILED(rv))) {
1372     return rv;
1373   }
1374 
1375   return NS_OK;
1376 }
1377 
1378 }  // namespace serviceWorkerScriptCache
1379 
1380 }  // namespace dom
1381 }  // namespace mozilla
1382