no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / dom / serviceworkers / ServiceWorkerScriptCache.cpp
blobf1569ffe5fadfbf4e0334ab821613a2bfdfd0581
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/. */
7 #include "ServiceWorkerScriptCache.h"
9 #include "js/Array.h" // JS::GetArrayLength
10 #include "js/PropertyAndElement.h" // JS_GetElement
11 #include "js/Utility.h" // JS::FreePolicy
12 #include "mozilla/TaskQueue.h"
13 #include "mozilla/Unused.h"
14 #include "mozilla/UniquePtr.h"
15 #include "mozilla/dom/CacheBinding.h"
16 #include "mozilla/dom/cache/CacheStorage.h"
17 #include "mozilla/dom/cache/Cache.h"
18 #include "mozilla/dom/Promise.h"
19 #include "mozilla/dom/PromiseWorkerProxy.h"
20 #include "mozilla/dom/ScriptLoader.h"
21 #include "mozilla/dom/WorkerCommon.h"
22 #include "mozilla/ipc/BackgroundUtils.h"
23 #include "mozilla/ipc/PBackgroundSharedTypes.h"
24 #include "mozilla/net/CookieJarSettings.h"
25 #include "mozilla/ScopeExit.h"
26 #include "mozilla/StaticPrefs_extensions.h"
27 #include "nsICacheInfoChannel.h"
28 #include "nsIHttpChannel.h"
29 #include "nsIStreamLoader.h"
30 #include "nsIThreadRetargetableRequest.h"
31 #include "nsIUUIDGenerator.h"
32 #include "nsIXPConnect.h"
34 #include "nsIInputStreamPump.h"
35 #include "nsIPrincipal.h"
36 #include "nsIScriptSecurityManager.h"
37 #include "nsContentUtils.h"
38 #include "nsNetUtil.h"
39 #include "ServiceWorkerManager.h"
40 #include "nsStringStream.h"
42 using mozilla::dom::cache::Cache;
43 using mozilla::dom::cache::CacheStorage;
44 using mozilla::ipc::PrincipalInfo;
46 namespace mozilla::dom::serviceWorkerScriptCache {
48 namespace {
50 already_AddRefed<CacheStorage> CreateCacheStorage(JSContext* aCx,
51 nsIPrincipal* aPrincipal,
52 ErrorResult& aRv) {
53 MOZ_ASSERT(NS_IsMainThread());
54 MOZ_ASSERT(aPrincipal);
56 nsIXPConnect* xpc = nsContentUtils::XPConnect();
57 MOZ_ASSERT(xpc, "This should never be null!");
58 JS::Rooted<JSObject*> sandbox(aCx);
59 aRv = xpc->CreateSandbox(aCx, aPrincipal, sandbox.address());
60 if (NS_WARN_IF(aRv.Failed())) {
61 return nullptr;
64 // This is called when the JSContext is not in a realm, so CreateSandbox
65 // returned an unwrapped global.
66 MOZ_ASSERT(JS_IsGlobalObject(sandbox));
68 nsCOMPtr<nsIGlobalObject> sandboxGlobalObject = xpc::NativeGlobal(sandbox);
69 if (!sandboxGlobalObject) {
70 aRv.Throw(NS_ERROR_FAILURE);
71 return nullptr;
74 // We assume private browsing is not enabled here. The ScriptLoader
75 // explicitly fails for private browsing so there should never be
76 // a service worker running in private browsing mode. Therefore if
77 // we are purging scripts or running a comparison algorithm we cannot
78 // be in private browsing.
80 // Also, bypass the CacheStorage trusted origin checks. The ServiceWorker
81 // has validated the origin prior to this point. All the information
82 // to revalidate is not available now.
83 return CacheStorage::CreateOnMainThread(cache::CHROME_ONLY_NAMESPACE,
84 sandboxGlobalObject, aPrincipal,
85 true /* force trusted origin */, aRv);
88 class CompareManager;
89 class CompareCache;
91 // This class downloads a URL from the network, compare the downloaded script
92 // with an existing cache if provided, and report to CompareManager via calling
93 // ComparisonFinished().
94 class CompareNetwork final : public nsIStreamLoaderObserver,
95 public nsIRequestObserver {
96 public:
97 NS_DECL_ISUPPORTS
98 NS_DECL_NSISTREAMLOADEROBSERVER
99 NS_DECL_NSIREQUESTOBSERVER
101 CompareNetwork(CompareManager* aManager,
102 ServiceWorkerRegistrationInfo* aRegistration,
103 bool aIsMainScript)
104 : mManager(aManager),
105 mRegistration(aRegistration),
106 mInternalHeaders(new InternalHeaders()),
107 mLoadFlags(nsIChannel::LOAD_BYPASS_SERVICE_WORKER),
108 mState(WaitingForInitialization),
109 mNetworkResult(NS_OK),
110 mCacheResult(NS_OK),
111 mIsMainScript(aIsMainScript),
112 mIsFromCache(false) {
113 MOZ_ASSERT(aManager);
114 MOZ_ASSERT(NS_IsMainThread());
117 nsresult Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL,
118 Cache* const aCache);
120 void Abort();
122 void NetworkFinish(nsresult aRv);
124 void CacheFinish(nsresult aRv);
126 const nsString& URL() const {
127 MOZ_ASSERT(NS_IsMainThread());
128 return mURL;
131 const nsString& Buffer() const {
132 MOZ_ASSERT(NS_IsMainThread());
133 return mBuffer;
136 const ChannelInfo& GetChannelInfo() const { return mChannelInfo; }
138 already_AddRefed<InternalHeaders> GetInternalHeaders() const {
139 RefPtr<InternalHeaders> internalHeaders = mInternalHeaders;
140 return internalHeaders.forget();
143 UniquePtr<PrincipalInfo> TakePrincipalInfo() {
144 return std::move(mPrincipalInfo);
147 bool Succeeded() const { return NS_SUCCEEDED(mNetworkResult); }
149 const nsTArray<nsCString>& URLList() const { return mURLList; }
151 private:
152 ~CompareNetwork() {
153 MOZ_ASSERT(NS_IsMainThread());
154 MOZ_ASSERT(!mCC);
157 void Finish();
159 nsresult SetPrincipalInfo(nsIChannel* aChannel);
161 RefPtr<CompareManager> mManager;
162 RefPtr<CompareCache> mCC;
163 RefPtr<ServiceWorkerRegistrationInfo> mRegistration;
165 nsCOMPtr<nsIChannel> mChannel;
166 nsString mBuffer;
167 nsString mURL;
168 ChannelInfo mChannelInfo;
169 RefPtr<InternalHeaders> mInternalHeaders;
170 UniquePtr<PrincipalInfo> mPrincipalInfo;
171 nsTArray<nsCString> mURLList;
173 nsCString mMaxScope;
174 nsLoadFlags mLoadFlags;
176 enum {
177 WaitingForInitialization,
178 WaitingForBothFinished,
179 WaitingForNetworkFinished,
180 WaitingForCacheFinished,
181 Finished
182 } mState;
184 nsresult mNetworkResult;
185 nsresult mCacheResult;
187 const bool mIsMainScript;
188 bool mIsFromCache;
191 NS_IMPL_ISUPPORTS(CompareNetwork, nsIStreamLoaderObserver, nsIRequestObserver)
193 // This class gets a cached Response from the CacheStorage and then it calls
194 // CacheFinish() in the CompareNetwork.
195 class CompareCache final : public PromiseNativeHandler,
196 public nsIStreamLoaderObserver {
197 public:
198 NS_DECL_ISUPPORTS
199 NS_DECL_NSISTREAMLOADEROBSERVER
201 explicit CompareCache(CompareNetwork* aCN)
202 : mCN(aCN), mState(WaitingForInitialization), mInCache(false) {
203 MOZ_ASSERT(aCN);
204 MOZ_ASSERT(NS_IsMainThread());
207 nsresult Initialize(Cache* const aCache, const nsAString& aURL);
209 void Finish(nsresult aStatus, bool aInCache);
211 void Abort();
213 virtual void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
214 ErrorResult& aRv) override;
216 virtual void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
217 ErrorResult& aRv) override;
219 const nsString& Buffer() const {
220 MOZ_ASSERT(NS_IsMainThread());
221 return mBuffer;
224 bool InCache() { return mInCache; }
226 private:
227 ~CompareCache() { MOZ_ASSERT(NS_IsMainThread()); }
229 void ManageValueResult(JSContext* aCx, JS::Handle<JS::Value> aValue);
231 RefPtr<CompareNetwork> mCN;
232 nsCOMPtr<nsIInputStreamPump> mPump;
234 nsString mURL;
235 nsString mBuffer;
237 enum {
238 WaitingForInitialization,
239 WaitingForScript,
240 Finished,
241 } mState;
243 bool mInCache;
246 NS_IMPL_ISUPPORTS(CompareCache, nsIStreamLoaderObserver)
248 class CompareManager final : public PromiseNativeHandler {
249 public:
250 NS_DECL_ISUPPORTS
252 explicit CompareManager(ServiceWorkerRegistrationInfo* aRegistration,
253 CompareCallback* aCallback)
254 : mRegistration(aRegistration),
255 mCallback(aCallback),
256 mLoadFlags(nsIChannel::LOAD_BYPASS_SERVICE_WORKER),
257 mState(WaitingForInitialization),
258 mPendingCount(0),
259 mOnFailure(OnFailure::DoNothing),
260 mAreScriptsEqual(true) {
261 MOZ_ASSERT(NS_IsMainThread());
262 MOZ_ASSERT(aRegistration);
265 nsresult Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL,
266 const nsAString& aCacheName);
268 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
269 ErrorResult& aRv) override;
271 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
272 ErrorResult& aRv) override;
274 CacheStorage* CacheStorage_() {
275 MOZ_ASSERT(NS_IsMainThread());
276 MOZ_ASSERT(mCacheStorage);
277 return mCacheStorage;
280 void ComparisonFinished(nsresult aStatus, bool aIsMainScript, bool aIsEqual,
281 const nsACString& aMaxScope, nsLoadFlags aLoadFlags) {
282 MOZ_ASSERT(NS_IsMainThread());
283 if (mState == Finished) {
284 return;
287 MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForScriptOrComparisonResult);
289 if (NS_WARN_IF(NS_FAILED(aStatus))) {
290 Fail(aStatus);
291 return;
294 mAreScriptsEqual = mAreScriptsEqual && aIsEqual;
296 if (aIsMainScript) {
297 mMaxScope = aMaxScope;
298 mLoadFlags = aLoadFlags;
301 // Check whether all CompareNetworks finished their jobs.
302 MOZ_DIAGNOSTIC_ASSERT(mPendingCount > 0);
303 if (--mPendingCount) {
304 return;
307 if (mAreScriptsEqual) {
308 MOZ_ASSERT(mCallback);
309 mCallback->ComparisonResult(aStatus, true /* aSameScripts */, mOnFailure,
310 u""_ns, mMaxScope, mLoadFlags);
311 Cleanup();
312 return;
315 // Write to Cache so ScriptLoader reads succeed.
316 WriteNetworkBufferToNewCache();
319 private:
320 ~CompareManager() {
321 MOZ_ASSERT(NS_IsMainThread());
322 MOZ_ASSERT(mCNList.Length() == 0);
325 void Fail(nsresult aStatus);
327 void Cleanup();
329 nsresult FetchScript(const nsAString& aURL, bool aIsMainScript,
330 Cache* const aCache = nullptr) {
331 MOZ_ASSERT(NS_IsMainThread());
333 MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForInitialization ||
334 mState == WaitingForScriptOrComparisonResult);
336 RefPtr<CompareNetwork> cn =
337 new CompareNetwork(this, mRegistration, aIsMainScript);
338 mCNList.AppendElement(cn);
339 mPendingCount += 1;
341 nsresult rv = cn->Initialize(mPrincipal, aURL, aCache);
342 if (NS_WARN_IF(NS_FAILED(rv))) {
343 return rv;
346 return NS_OK;
349 void ManageOldCache(JSContext* aCx, JS::Handle<JS::Value> aValue) {
350 MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForExistingOpen);
352 // RAII Cleanup when fails.
353 nsresult rv = NS_ERROR_FAILURE;
354 auto guard = MakeScopeExit([&] { Fail(rv); });
356 if (NS_WARN_IF(!aValue.isObject())) {
357 return;
360 MOZ_ASSERT(!mOldCache);
361 JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
362 if (NS_WARN_IF(!obj) ||
363 NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Cache, obj, mOldCache)))) {
364 return;
367 Optional<RequestOrUSVString> request;
368 CacheQueryOptions options;
369 ErrorResult error;
370 RefPtr<Promise> promise = mOldCache->Keys(aCx, request, options, error);
371 if (NS_WARN_IF(error.Failed())) {
372 // No exception here because there are no ReadableStreams involved here.
373 MOZ_ASSERT(!error.IsJSException());
374 rv = error.StealNSResult();
375 return;
378 mState = WaitingForExistingKeys;
379 promise->AppendNativeHandler(this);
380 guard.release();
383 void ManageOldKeys(JSContext* aCx, JS::Handle<JS::Value> aValue) {
384 MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForExistingKeys);
386 // RAII Cleanup when fails.
387 nsresult rv = NS_ERROR_FAILURE;
388 auto guard = MakeScopeExit([&] { Fail(rv); });
390 if (NS_WARN_IF(!aValue.isObject())) {
391 return;
394 JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
395 if (NS_WARN_IF(!obj)) {
396 return;
399 uint32_t len = 0;
400 if (!JS::GetArrayLength(aCx, obj, &len)) {
401 return;
404 // Fetch and compare the source scripts.
405 MOZ_ASSERT(mPendingCount == 0);
407 mState = WaitingForScriptOrComparisonResult;
409 bool hasMainScript = false;
410 AutoTArray<nsString, 8> urlList;
412 // Extract the list of URLs in the old cache.
413 for (uint32_t i = 0; i < len; ++i) {
414 JS::Rooted<JS::Value> val(aCx);
415 if (NS_WARN_IF(!JS_GetElement(aCx, obj, i, &val)) ||
416 NS_WARN_IF(!val.isObject())) {
417 return;
420 Request* request;
421 JS::Rooted<JSObject*> requestObj(aCx, &val.toObject());
422 if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Request, &requestObj, request)))) {
423 return;
426 nsString url;
427 request->GetUrl(url);
429 if (!hasMainScript && url == mURL) {
430 hasMainScript = true;
433 urlList.AppendElement(url);
436 // If the main script is missing, then something has gone wrong. We
437 // will try to continue with the update process to trigger a new
438 // installation. If that fails, however, then uninstall the registration
439 // because it is broken in a way that cannot be fixed.
440 if (!hasMainScript) {
441 mOnFailure = OnFailure::Uninstall;
444 // Always make sure to fetch the main script. If the old cache has
445 // no entries or the main script entry is missing, then the loop below
446 // may not trigger it. This should not really happen, but we handle it
447 // gracefully if it does occur. Its possible the bad cache state is due
448 // to a crash or shutdown during an update, etc.
449 rv = FetchScript(mURL, true /* aIsMainScript */, mOldCache);
450 if (NS_WARN_IF(NS_FAILED(rv))) {
451 return;
454 for (const auto& url : urlList) {
455 // We explicitly start the fetch for the main script above.
456 if (mURL == url) {
457 continue;
460 rv = FetchScript(url, false /* aIsMainScript */, mOldCache);
461 if (NS_WARN_IF(NS_FAILED(rv))) {
462 return;
466 guard.release();
469 void ManageNewCache(JSContext* aCx, JS::Handle<JS::Value> aValue) {
470 MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForOpen);
472 // RAII Cleanup when fails.
473 nsresult rv = NS_ERROR_FAILURE;
474 auto guard = MakeScopeExit([&] { Fail(rv); });
476 if (NS_WARN_IF(!aValue.isObject())) {
477 return;
480 JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
481 if (NS_WARN_IF(!obj)) {
482 return;
485 Cache* cache = nullptr;
486 rv = UNWRAP_OBJECT(Cache, &obj, cache);
487 if (NS_WARN_IF(NS_FAILED(rv))) {
488 return;
491 // Just to be safe.
492 RefPtr<Cache> kungfuDeathGrip = cache;
494 MOZ_ASSERT(mPendingCount == 0);
495 for (uint32_t i = 0; i < mCNList.Length(); ++i) {
496 // We bail out immediately when something goes wrong.
497 rv = WriteToCache(aCx, cache, mCNList[i]);
498 if (NS_WARN_IF(NS_FAILED(rv))) {
499 return;
503 mState = WaitingForPut;
504 guard.release();
507 void WriteNetworkBufferToNewCache() {
508 MOZ_ASSERT(NS_IsMainThread());
509 MOZ_ASSERT(mCNList.Length() != 0);
510 MOZ_ASSERT(mCacheStorage);
511 MOZ_ASSERT(mNewCacheName.IsEmpty());
513 ErrorResult result;
514 result = serviceWorkerScriptCache::GenerateCacheName(mNewCacheName);
515 if (NS_WARN_IF(result.Failed())) {
516 MOZ_ASSERT(!result.IsErrorWithMessage());
517 Fail(result.StealNSResult());
518 return;
521 RefPtr<Promise> cacheOpenPromise =
522 mCacheStorage->Open(mNewCacheName, result);
523 if (NS_WARN_IF(result.Failed())) {
524 MOZ_ASSERT(!result.IsErrorWithMessage());
525 Fail(result.StealNSResult());
526 return;
529 mState = WaitingForOpen;
530 cacheOpenPromise->AppendNativeHandler(this);
533 nsresult WriteToCache(JSContext* aCx, Cache* aCache, CompareNetwork* aCN) {
534 MOZ_ASSERT(NS_IsMainThread());
535 MOZ_ASSERT(aCache);
536 MOZ_ASSERT(aCN);
537 MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForOpen);
539 // We don't have to save any information from a failed CompareNetwork.
540 if (!aCN->Succeeded()) {
541 return NS_OK;
544 nsCOMPtr<nsIInputStream> body;
545 nsresult rv = NS_NewCStringInputStream(
546 getter_AddRefs(body), NS_ConvertUTF16toUTF8(aCN->Buffer()));
547 if (NS_WARN_IF(NS_FAILED(rv))) {
548 return rv;
551 SafeRefPtr<InternalResponse> ir =
552 MakeSafeRefPtr<InternalResponse>(200, "OK"_ns);
553 ir->SetBody(body, aCN->Buffer().Length());
554 ir->SetURLList(aCN->URLList());
556 ir->InitChannelInfo(aCN->GetChannelInfo());
557 UniquePtr<PrincipalInfo> principalInfo = aCN->TakePrincipalInfo();
558 if (principalInfo) {
559 ir->SetPrincipalInfo(std::move(principalInfo));
562 RefPtr<InternalHeaders> internalHeaders = aCN->GetInternalHeaders();
563 ir->Headers()->Fill(*(internalHeaders.get()), IgnoreErrors());
565 RefPtr<Response> response =
566 new Response(aCache->GetGlobalObject(), std::move(ir), nullptr);
568 RequestOrUSVString request;
569 request.SetAsUSVString().ShareOrDependUpon(aCN->URL());
571 // For now we have to wait until the Put Promise is fulfilled before we can
572 // continue since Cache does not yet support starting a read that is being
573 // written to.
574 ErrorResult result;
575 RefPtr<Promise> cachePromise = aCache->Put(aCx, request, *response, result);
576 result.WouldReportJSException();
577 if (NS_WARN_IF(result.Failed())) {
578 // No exception here because there are no ReadableStreams involved here.
579 MOZ_ASSERT(!result.IsJSException());
580 MOZ_ASSERT(!result.IsErrorWithMessage());
581 return result.StealNSResult();
584 mPendingCount += 1;
585 cachePromise->AppendNativeHandler(this);
586 return NS_OK;
589 RefPtr<ServiceWorkerRegistrationInfo> mRegistration;
590 RefPtr<CompareCallback> mCallback;
591 RefPtr<CacheStorage> mCacheStorage;
593 nsTArray<RefPtr<CompareNetwork>> mCNList;
595 nsString mURL;
596 RefPtr<nsIPrincipal> mPrincipal;
598 // Used for the old cache where saves the old source scripts.
599 RefPtr<Cache> mOldCache;
601 // Only used if the network script has changed and needs to be cached.
602 nsString mNewCacheName;
604 nsCString mMaxScope;
605 nsLoadFlags mLoadFlags;
607 enum {
608 WaitingForInitialization,
609 WaitingForExistingOpen,
610 WaitingForExistingKeys,
611 WaitingForScriptOrComparisonResult,
612 WaitingForOpen,
613 WaitingForPut,
614 Finished
615 } mState;
617 uint32_t mPendingCount;
618 OnFailure mOnFailure;
619 bool mAreScriptsEqual;
622 NS_IMPL_ISUPPORTS0(CompareManager)
624 nsresult CompareNetwork::Initialize(nsIPrincipal* aPrincipal,
625 const nsAString& aURL,
626 Cache* const aCache) {
627 MOZ_ASSERT(aPrincipal);
628 MOZ_ASSERT(NS_IsMainThread());
630 nsCOMPtr<nsIURI> uri;
631 nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL);
632 if (NS_WARN_IF(NS_FAILED(rv))) {
633 return rv;
636 mURL = aURL;
637 mURLList.AppendElement(NS_ConvertUTF16toUTF8(mURL));
639 nsCOMPtr<nsILoadGroup> loadGroup;
640 rv = NS_NewLoadGroup(getter_AddRefs(loadGroup), aPrincipal);
641 if (NS_WARN_IF(NS_FAILED(rv))) {
642 return rv;
645 // Update LoadFlags for propagating to ServiceWorkerInfo.
646 mLoadFlags = nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
648 ServiceWorkerUpdateViaCache uvc = mRegistration->GetUpdateViaCache();
649 if (uvc == ServiceWorkerUpdateViaCache::None ||
650 (uvc == ServiceWorkerUpdateViaCache::Imports && mIsMainScript)) {
651 mLoadFlags |= nsIRequest::VALIDATE_ALWAYS;
654 if (mRegistration->IsLastUpdateCheckTimeOverOneDay()) {
655 mLoadFlags |= nsIRequest::LOAD_BYPASS_CACHE;
658 // Different settings are needed for fetching imported scripts, since they
659 // might be cross-origin scripts.
660 uint32_t secFlags =
661 mIsMainScript ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
662 : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT;
664 nsContentPolicyType contentPolicyType =
665 mIsMainScript ? nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER
666 : nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS;
668 // Create a new cookieJarSettings.
669 nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
670 mozilla::net::CookieJarSettings::Create(aPrincipal);
672 // Populate the partitionKey by using the given prinicpal. The ServiceWorkers
673 // are using the foreign partitioned principal, so we can get the partitionKey
674 // from it and the partitionKey will only exist if it's in the third-party
675 // context. In first-party context, we can still use the uri to set the
676 // partitionKey.
677 if (!aPrincipal->OriginAttributesRef().mPartitionKey.IsEmpty()) {
678 net::CookieJarSettings::Cast(cookieJarSettings)
679 ->SetPartitionKey(aPrincipal->OriginAttributesRef().mPartitionKey);
680 } else {
681 net::CookieJarSettings::Cast(cookieJarSettings)->SetPartitionKey(uri);
684 // Note that because there is no "serviceworker" RequestContext type, we can
685 // use the TYPE_INTERNAL_SCRIPT content policy types when loading a service
686 // worker.
687 rv = NS_NewChannel(getter_AddRefs(mChannel), uri, aPrincipal, secFlags,
688 contentPolicyType, cookieJarSettings,
689 nullptr /* aPerformanceStorage */, loadGroup,
690 nullptr /* aCallbacks */, mLoadFlags);
691 if (NS_WARN_IF(NS_FAILED(rv))) {
692 return rv;
695 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
696 if (httpChannel) {
697 // Spec says no redirects allowed for top-level SW scripts.
698 if (mIsMainScript) {
699 rv = httpChannel->SetRedirectionLimit(0);
700 MOZ_ASSERT(NS_SUCCEEDED(rv));
703 rv = httpChannel->SetRequestHeader("Service-Worker"_ns, "script"_ns,
704 /* merge */ false);
705 MOZ_ASSERT(NS_SUCCEEDED(rv));
708 nsCOMPtr<nsIStreamLoader> loader;
709 rv = NS_NewStreamLoader(getter_AddRefs(loader), this, this);
710 if (NS_WARN_IF(NS_FAILED(rv))) {
711 return rv;
714 rv = mChannel->AsyncOpen(loader);
715 if (NS_WARN_IF(NS_FAILED(rv))) {
716 return rv;
719 // If we do have an existing cache to compare with.
720 if (aCache) {
721 mCC = new CompareCache(this);
722 rv = mCC->Initialize(aCache, aURL);
723 if (NS_WARN_IF(NS_FAILED(rv))) {
724 Abort();
725 return rv;
728 mState = WaitingForBothFinished;
729 return NS_OK;
732 mState = WaitingForNetworkFinished;
733 return NS_OK;
736 void CompareNetwork::Finish() {
737 if (mState == Finished) {
738 return;
741 bool same = true;
742 nsresult rv = NS_OK;
744 // mNetworkResult is prior to mCacheResult, since it's needed for reporting
745 // various errors to web content.
746 if (NS_FAILED(mNetworkResult)) {
747 // An imported script could become offline, since it might no longer be
748 // needed by the new importing script. In that case, the importing script
749 // must be different, and thus, it's okay to report same script found here.
750 rv = mIsMainScript ? mNetworkResult : NS_OK;
751 same = true;
752 } else if (mCC && NS_FAILED(mCacheResult)) {
753 rv = mCacheResult;
754 } else { // Both passed.
755 same = mCC && mCC->InCache() && mCC->Buffer().Equals(mBuffer);
758 mManager->ComparisonFinished(rv, mIsMainScript, same, mMaxScope, mLoadFlags);
760 // We have done with the CompareCache.
761 mCC = nullptr;
764 void CompareNetwork::NetworkFinish(nsresult aRv) {
765 MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForBothFinished ||
766 mState == WaitingForNetworkFinished);
768 mNetworkResult = aRv;
770 if (mState == WaitingForBothFinished) {
771 mState = WaitingForCacheFinished;
772 return;
775 if (mState == WaitingForNetworkFinished) {
776 Finish();
777 return;
781 void CompareNetwork::CacheFinish(nsresult aRv) {
782 MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForBothFinished ||
783 mState == WaitingForCacheFinished);
785 mCacheResult = aRv;
787 if (mState == WaitingForBothFinished) {
788 mState = WaitingForNetworkFinished;
789 return;
792 if (mState == WaitingForCacheFinished) {
793 Finish();
794 return;
798 void CompareNetwork::Abort() {
799 MOZ_ASSERT(NS_IsMainThread());
801 if (mState != Finished) {
802 mState = Finished;
804 MOZ_ASSERT(mChannel);
805 mChannel->CancelWithReason(NS_BINDING_ABORTED, "CompareNetwork::Abort"_ns);
806 mChannel = nullptr;
808 if (mCC) {
809 mCC->Abort();
810 mCC = nullptr;
815 NS_IMETHODIMP
816 CompareNetwork::OnStartRequest(nsIRequest* aRequest) {
817 MOZ_ASSERT(NS_IsMainThread());
819 if (mState == Finished) {
820 return NS_OK;
823 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
824 MOZ_ASSERT_IF(mIsMainScript, channel == mChannel);
825 mChannel = channel;
827 MOZ_ASSERT(!mChannelInfo.IsInitialized());
828 mChannelInfo.InitFromChannel(mChannel);
830 nsresult rv = SetPrincipalInfo(mChannel);
831 if (NS_WARN_IF(NS_FAILED(rv))) {
832 return rv;
835 mInternalHeaders->FillResponseHeaders(mChannel);
837 nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(channel));
838 if (cacheChannel) {
839 cacheChannel->IsFromCache(&mIsFromCache);
842 return NS_OK;
845 nsresult CompareNetwork::SetPrincipalInfo(nsIChannel* aChannel) {
846 nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
847 if (!ssm) {
848 return NS_ERROR_FAILURE;
851 nsCOMPtr<nsIPrincipal> channelPrincipal;
852 nsresult rv = ssm->GetChannelResultPrincipal(
853 aChannel, getter_AddRefs(channelPrincipal));
854 if (NS_WARN_IF(NS_FAILED(rv))) {
855 return rv;
858 UniquePtr<PrincipalInfo> principalInfo = MakeUnique<PrincipalInfo>();
859 rv = PrincipalToPrincipalInfo(channelPrincipal, principalInfo.get());
861 if (NS_WARN_IF(NS_FAILED(rv))) {
862 return rv;
865 mPrincipalInfo = std::move(principalInfo);
866 return NS_OK;
869 NS_IMETHODIMP
870 CompareNetwork::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
871 // Nothing to do here!
872 return NS_OK;
875 NS_IMETHODIMP
876 CompareNetwork::OnStreamComplete(nsIStreamLoader* aLoader,
877 nsISupports* aContext, nsresult aStatus,
878 uint32_t aLen, const uint8_t* aString) {
879 MOZ_ASSERT(NS_IsMainThread());
881 if (mState == Finished) {
882 return NS_OK;
885 nsresult rv = NS_ERROR_FAILURE;
886 auto guard = MakeScopeExit([&] { NetworkFinish(rv); });
888 if (NS_WARN_IF(NS_FAILED(aStatus))) {
889 rv = (aStatus == NS_ERROR_REDIRECT_LOOP) ? NS_ERROR_DOM_SECURITY_ERR
890 : aStatus;
891 return NS_OK;
894 nsCOMPtr<nsIRequest> request;
895 rv = aLoader->GetRequest(getter_AddRefs(request));
896 if (NS_WARN_IF(NS_FAILED(rv))) {
897 return NS_OK;
900 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
901 MOZ_ASSERT(channel, "How come we don't have any channel?");
903 nsCOMPtr<nsIURI> uri;
904 channel->GetOriginalURI(getter_AddRefs(uri));
905 bool isExtension = uri->SchemeIs("moz-extension");
907 if (isExtension &&
908 !StaticPrefs::extensions_backgroundServiceWorker_enabled_AtStartup()) {
909 // Return earlier with error is the worker script is a moz-extension url
910 // but the feature isn't enabled by prefs.
911 return NS_ERROR_FAILURE;
914 if (isExtension) {
915 // NOTE: trying to register any moz-extension use that doesn't ends
916 // with .js/.jsm/.mjs seems to be already completing with an error
917 // in aStatus and they never reach this point.
919 // TODO: look into avoid duplicated parts that could be shared with the HTTP
920 // channel scenario.
921 nsCOMPtr<nsIURI> channelURL;
922 rv = channel->GetURI(getter_AddRefs(channelURL));
923 if (NS_WARN_IF(NS_FAILED(rv))) {
924 return rv;
927 nsCString channelURLSpec;
928 MOZ_ALWAYS_SUCCEEDS(channelURL->GetSpec(channelURLSpec));
930 // Append the final URL (which for an extension worker script is going to
931 // be a file or jar url).
932 MOZ_DIAGNOSTIC_ASSERT(!mURLList.IsEmpty());
933 if (channelURLSpec != mURLList[0]) {
934 mURLList.AppendElement(channelURLSpec);
937 UniquePtr<char16_t[], JS::FreePolicy> buffer;
938 size_t len = 0;
940 rv = ScriptLoader::ConvertToUTF16(channel, aString, aLen, u"UTF-8"_ns,
941 nullptr, buffer, len);
942 if (NS_WARN_IF(NS_FAILED(rv))) {
943 return rv;
946 mBuffer.Adopt(buffer.release(), len);
948 rv = NS_OK;
949 return NS_OK;
952 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request);
954 // Main scripts cannot be redirected successfully, however extensions
955 // may successfuly redirect imported scripts to a moz-extension url
956 // (if listed in the web_accessible_resources manifest property).
958 // When the service worker is initially registered the imported scripts
959 // will be loaded from the child process (see dom/workers/ScriptLoader.cpp)
960 // and in that case this method will only be called for the main script.
962 // When a registered worker is loaded again (e.g. when the webpage calls
963 // the ServiceWorkerRegistration's update method):
965 // - both the main and imported scripts are loaded by the
966 // CompareManager::FetchScript
967 // - the update requests for the imported scripts will also be calling this
968 // method and we should expect scripts redirected to an extension script
969 // to have a null httpChannel.
971 // The request that triggers this method is:
973 // - the one that is coming from the network (which may be intercepted by
974 // WebRequest listeners in extensions and redirected to a web_accessible
975 // moz-extension url)
976 // - it will then be compared with a previous response that we may have
977 // in the cache
979 // When the next service worker update occurs, if the request (for an imported
980 // script) is not redirected by an extension the cache entry is invalidated
981 // and a network request is triggered for the import.
982 if (!httpChannel) {
983 // Redirecting a service worker main script should fail before reaching this
984 // method.
985 // If a main script is somehow redirected, the diagnostic assert will crash
986 // in non-release builds. Release builds will return an explicit error.
987 MOZ_DIAGNOSTIC_ASSERT(!mIsMainScript,
988 "Unexpected ServiceWorker main script redirected");
989 if (mIsMainScript) {
990 return NS_ERROR_UNEXPECTED;
993 nsCOMPtr<nsIPrincipal> channelPrincipal;
995 nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
996 if (!ssm) {
997 return NS_ERROR_UNEXPECTED;
1000 nsresult rv = ssm->GetChannelResultPrincipal(
1001 channel, getter_AddRefs(channelPrincipal));
1003 // An extension did redirect a non-MainScript request to a moz-extension url
1004 // (in that case the originalURL is the resolved jar URI and so we have to
1005 // look to the channel principal instead).
1006 if (channelPrincipal->SchemeIs("moz-extension")) {
1007 UniquePtr<char16_t[], JS::FreePolicy> buffer;
1008 size_t len = 0;
1010 rv = ScriptLoader::ConvertToUTF16(channel, aString, aLen, u"UTF-8"_ns,
1011 nullptr, buffer, len);
1012 if (NS_WARN_IF(NS_FAILED(rv))) {
1013 return rv;
1016 mBuffer.Adopt(buffer.release(), len);
1018 return NS_OK;
1021 // Make non-release and debug builds to crash if this happens and fail
1022 // explicitly on release builds.
1023 MOZ_DIAGNOSTIC_ASSERT(false,
1024 "ServiceWorker imported script redirected to an url "
1025 "with an unexpected scheme");
1026 return NS_ERROR_UNEXPECTED;
1029 bool requestSucceeded;
1030 rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
1031 if (NS_WARN_IF(NS_FAILED(rv))) {
1032 return NS_OK;
1035 if (NS_WARN_IF(!requestSucceeded)) {
1036 // Get the stringified numeric status code, not statusText which could be
1037 // something misleading like OK for a 404.
1038 uint32_t status = 0;
1039 Unused << httpChannel->GetResponseStatus(
1040 &status); // don't care if this fails, use 0.
1041 nsAutoString statusAsText;
1042 statusAsText.AppendInt(status);
1044 ServiceWorkerManager::LocalizeAndReportToAllClients(
1045 mRegistration->Scope(), "ServiceWorkerRegisterNetworkError",
1046 nsTArray<nsString>{NS_ConvertUTF8toUTF16(mRegistration->Scope()),
1047 statusAsText, mURL});
1049 rv = NS_ERROR_FAILURE;
1050 return NS_OK;
1053 // Note: we explicitly don't check for the return value here, because the
1054 // absence of the header is not an error condition.
1055 Unused << httpChannel->GetResponseHeader("Service-Worker-Allowed"_ns,
1056 mMaxScope);
1058 // [9.2 Update]4.13, If response's cache state is not "local",
1059 // set registration's last update check time to the current time
1060 if (!mIsFromCache) {
1061 mRegistration->RefreshLastUpdateCheckTime();
1064 nsAutoCString mimeType;
1065 rv = httpChannel->GetContentType(mimeType);
1066 if (NS_WARN_IF(NS_FAILED(rv))) {
1067 // We should only end up here if !mResponseHead in the channel. If headers
1068 // were received but no content type was specified, we'll be given
1069 // UNKNOWN_CONTENT_TYPE "application/x-unknown-content-type" and so fall
1070 // into the next case with its better error message.
1071 rv = NS_ERROR_DOM_SECURITY_ERR;
1072 return rv;
1075 if (mimeType.IsEmpty() ||
1076 !nsContentUtils::IsJavascriptMIMEType(NS_ConvertUTF8toUTF16(mimeType))) {
1077 ServiceWorkerManager::LocalizeAndReportToAllClients(
1078 mRegistration->Scope(), "ServiceWorkerRegisterMimeTypeError2",
1079 nsTArray<nsString>{NS_ConvertUTF8toUTF16(mRegistration->Scope()),
1080 NS_ConvertUTF8toUTF16(mimeType), mURL});
1081 rv = NS_ERROR_DOM_SECURITY_ERR;
1082 return rv;
1085 nsCOMPtr<nsIURI> channelURL;
1086 rv = httpChannel->GetURI(getter_AddRefs(channelURL));
1087 if (NS_WARN_IF(NS_FAILED(rv))) {
1088 return rv;
1091 nsCString channelURLSpec;
1092 MOZ_ALWAYS_SUCCEEDS(channelURL->GetSpec(channelURLSpec));
1094 // Append the final URL if its different from the original
1095 // request URL. This lets us note that a redirect occurred
1096 // even though we don't track every redirect URL here.
1097 MOZ_DIAGNOSTIC_ASSERT(!mURLList.IsEmpty());
1098 if (channelURLSpec != mURLList[0]) {
1099 mURLList.AppendElement(channelURLSpec);
1102 UniquePtr<char16_t[], JS::FreePolicy> buffer;
1103 size_t len = 0;
1105 rv = ScriptLoader::ConvertToUTF16(httpChannel, aString, aLen, u"UTF-8"_ns,
1106 nullptr, buffer, len);
1107 if (NS_WARN_IF(NS_FAILED(rv))) {
1108 return rv;
1111 mBuffer.Adopt(buffer.release(), len);
1113 rv = NS_OK;
1114 return NS_OK;
1117 nsresult CompareCache::Initialize(Cache* const aCache, const nsAString& aURL) {
1118 MOZ_ASSERT(NS_IsMainThread());
1119 MOZ_ASSERT(aCache);
1120 MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForInitialization);
1122 // This JSContext will not end up executing JS code because here there are
1123 // no ReadableStreams involved.
1124 AutoJSAPI jsapi;
1125 jsapi.Init();
1127 RequestOrUSVString request;
1128 request.SetAsUSVString().ShareOrDependUpon(aURL);
1129 ErrorResult error;
1130 CacheQueryOptions params;
1131 RefPtr<Promise> promise = aCache->Match(jsapi.cx(), request, params, error);
1132 if (NS_WARN_IF(error.Failed())) {
1133 // No exception here because there are no ReadableStreams involved here.
1134 MOZ_ASSERT(!error.IsJSException());
1135 mState = Finished;
1136 return error.StealNSResult();
1139 // Retrieve the script from aCache.
1140 mState = WaitingForScript;
1141 promise->AppendNativeHandler(this);
1142 return NS_OK;
1145 void CompareCache::Finish(nsresult aStatus, bool aInCache) {
1146 if (mState != Finished) {
1147 mState = Finished;
1148 mInCache = aInCache;
1149 mCN->CacheFinish(aStatus);
1153 void CompareCache::Abort() {
1154 MOZ_ASSERT(NS_IsMainThread());
1156 if (mState != Finished) {
1157 mState = Finished;
1159 if (mPump) {
1160 mPump->CancelWithReason(NS_BINDING_ABORTED, "CompareCache::Abort"_ns);
1161 mPump = nullptr;
1166 NS_IMETHODIMP
1167 CompareCache::OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext,
1168 nsresult aStatus, uint32_t aLen,
1169 const uint8_t* aString) {
1170 MOZ_ASSERT(NS_IsMainThread());
1172 if (mState == Finished) {
1173 return aStatus;
1176 if (NS_WARN_IF(NS_FAILED(aStatus))) {
1177 Finish(aStatus, false);
1178 return aStatus;
1181 UniquePtr<char16_t[], JS::FreePolicy> buffer;
1182 size_t len = 0;
1184 nsresult rv = ScriptLoader::ConvertToUTF16(nullptr, aString, aLen,
1185 u"UTF-8"_ns, nullptr, buffer, len);
1186 if (NS_WARN_IF(NS_FAILED(rv))) {
1187 Finish(rv, false);
1188 return rv;
1191 mBuffer.Adopt(buffer.release(), len);
1193 Finish(NS_OK, true);
1194 return NS_OK;
1197 void CompareCache::ResolvedCallback(JSContext* aCx,
1198 JS::Handle<JS::Value> aValue,
1199 ErrorResult& aRv) {
1200 MOZ_ASSERT(NS_IsMainThread());
1202 switch (mState) {
1203 case Finished:
1204 return;
1205 case WaitingForScript:
1206 ManageValueResult(aCx, aValue);
1207 return;
1208 default:
1209 MOZ_CRASH("Unacceptable state.");
1213 void CompareCache::RejectedCallback(JSContext* aCx,
1214 JS::Handle<JS::Value> aValue,
1215 ErrorResult& aRv) {
1216 MOZ_ASSERT(NS_IsMainThread());
1218 if (mState != Finished) {
1219 Finish(NS_ERROR_FAILURE, false);
1220 return;
1224 void CompareCache::ManageValueResult(JSContext* aCx,
1225 JS::Handle<JS::Value> aValue) {
1226 MOZ_ASSERT(NS_IsMainThread());
1228 // The cache returns undefined if the object is not stored.
1229 if (aValue.isUndefined()) {
1230 Finish(NS_OK, false);
1231 return;
1234 MOZ_ASSERT(aValue.isObject());
1236 JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
1237 if (NS_WARN_IF(!obj)) {
1238 Finish(NS_ERROR_FAILURE, false);
1239 return;
1242 Response* response = nullptr;
1243 nsresult rv = UNWRAP_OBJECT(Response, &obj, response);
1244 if (NS_WARN_IF(NS_FAILED(rv))) {
1245 Finish(rv, false);
1246 return;
1249 MOZ_ASSERT(response->Ok());
1251 nsCOMPtr<nsIInputStream> inputStream;
1252 response->GetBody(getter_AddRefs(inputStream));
1253 MOZ_ASSERT(inputStream);
1255 MOZ_ASSERT(!mPump);
1256 rv = NS_NewInputStreamPump(getter_AddRefs(mPump), inputStream.forget(),
1257 0, /* default segsize */
1258 0, /* default segcount */
1259 false, /* default closeWhenDone */
1260 GetMainThreadSerialEventTarget());
1261 if (NS_WARN_IF(NS_FAILED(rv))) {
1262 Finish(rv, false);
1263 return;
1266 nsCOMPtr<nsIStreamLoader> loader;
1267 rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
1268 if (NS_WARN_IF(NS_FAILED(rv))) {
1269 Finish(rv, false);
1270 return;
1273 rv = mPump->AsyncRead(loader);
1274 if (NS_WARN_IF(NS_FAILED(rv))) {
1275 mPump = nullptr;
1276 Finish(rv, false);
1277 return;
1280 nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(mPump);
1281 if (rr) {
1282 nsCOMPtr<nsIEventTarget> sts =
1283 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
1284 RefPtr<TaskQueue> queue =
1285 TaskQueue::Create(sts.forget(), "CompareCache STS Delivery Queue");
1286 rv = rr->RetargetDeliveryTo(queue);
1287 if (NS_WARN_IF(NS_FAILED(rv))) {
1288 mPump = nullptr;
1289 Finish(rv, false);
1290 return;
1295 nsresult CompareManager::Initialize(nsIPrincipal* aPrincipal,
1296 const nsAString& aURL,
1297 const nsAString& aCacheName) {
1298 MOZ_ASSERT(NS_IsMainThread());
1299 MOZ_ASSERT(aPrincipal);
1300 MOZ_ASSERT(mPendingCount == 0);
1301 MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForInitialization);
1303 // RAII Cleanup when fails.
1304 auto guard = MakeScopeExit([&] { Cleanup(); });
1306 mURL = aURL;
1307 mPrincipal = aPrincipal;
1309 // Always create a CacheStorage since we want to write the network entry to
1310 // the cache even if there isn't an existing one.
1311 AutoJSAPI jsapi;
1312 jsapi.Init();
1313 ErrorResult result;
1314 mCacheStorage = CreateCacheStorage(jsapi.cx(), aPrincipal, result);
1315 if (NS_WARN_IF(result.Failed())) {
1316 MOZ_ASSERT(!result.IsErrorWithMessage());
1317 return result.StealNSResult();
1320 // If there is no existing cache, proceed to fetch the script directly.
1321 if (aCacheName.IsEmpty()) {
1322 mState = WaitingForScriptOrComparisonResult;
1323 nsresult rv = FetchScript(aURL, true /* aIsMainScript */);
1324 if (NS_WARN_IF(NS_FAILED(rv))) {
1325 return rv;
1328 guard.release();
1329 return NS_OK;
1332 // Open the cache saving the old source scripts.
1333 RefPtr<Promise> promise = mCacheStorage->Open(aCacheName, result);
1334 if (NS_WARN_IF(result.Failed())) {
1335 MOZ_ASSERT(!result.IsErrorWithMessage());
1336 return result.StealNSResult();
1339 mState = WaitingForExistingOpen;
1340 promise->AppendNativeHandler(this);
1342 guard.release();
1343 return NS_OK;
1346 // This class manages 4 promises if needed:
1347 // 1. Retrieve the Cache object by a given CacheName of OldCache.
1348 // 2. Retrieve the URLs saved in OldCache.
1349 // 3. Retrieve the Cache object of the NewCache for the newly created SW.
1350 // 4. Put the value in the cache.
1351 // For this reason we have mState to know what callback we are handling.
1352 void CompareManager::ResolvedCallback(JSContext* aCx,
1353 JS::Handle<JS::Value> aValue,
1354 ErrorResult& aRv) {
1355 MOZ_ASSERT(NS_IsMainThread());
1356 MOZ_ASSERT(mCallback);
1358 switch (mState) {
1359 case Finished:
1360 return;
1361 case WaitingForExistingOpen:
1362 ManageOldCache(aCx, aValue);
1363 return;
1364 case WaitingForExistingKeys:
1365 ManageOldKeys(aCx, aValue);
1366 return;
1367 case WaitingForOpen:
1368 ManageNewCache(aCx, aValue);
1369 return;
1370 case WaitingForPut:
1371 MOZ_DIAGNOSTIC_ASSERT(mPendingCount > 0);
1372 if (--mPendingCount == 0) {
1373 mCallback->ComparisonResult(NS_OK, false /* aIsEqual */, mOnFailure,
1374 mNewCacheName, mMaxScope, mLoadFlags);
1375 Cleanup();
1377 return;
1378 default:
1379 MOZ_DIAGNOSTIC_ASSERT(false);
1383 void CompareManager::RejectedCallback(JSContext* aCx,
1384 JS::Handle<JS::Value> aValue,
1385 ErrorResult& aRv) {
1386 MOZ_ASSERT(NS_IsMainThread());
1387 switch (mState) {
1388 case Finished:
1389 return;
1390 case WaitingForExistingOpen:
1391 NS_WARNING("Could not open the existing cache.");
1392 break;
1393 case WaitingForExistingKeys:
1394 NS_WARNING("Could not get the existing URLs.");
1395 break;
1396 case WaitingForOpen:
1397 NS_WARNING("Could not open cache.");
1398 break;
1399 case WaitingForPut:
1400 NS_WARNING("Could not write to cache.");
1401 break;
1402 default:
1403 MOZ_DIAGNOSTIC_ASSERT(false);
1406 Fail(NS_ERROR_FAILURE);
1409 void CompareManager::Fail(nsresult aStatus) {
1410 MOZ_ASSERT(NS_IsMainThread());
1411 mCallback->ComparisonResult(aStatus, false /* aIsEqual */, mOnFailure, u""_ns,
1412 ""_ns, mLoadFlags);
1413 Cleanup();
1416 void CompareManager::Cleanup() {
1417 MOZ_ASSERT(NS_IsMainThread());
1419 if (mState != Finished) {
1420 mState = Finished;
1422 MOZ_ASSERT(mCallback);
1423 mCallback = nullptr;
1425 // Abort and release CompareNetworks.
1426 for (uint32_t i = 0; i < mCNList.Length(); ++i) {
1427 mCNList[i]->Abort();
1429 mCNList.Clear();
1433 } // namespace
1435 nsresult PurgeCache(nsIPrincipal* aPrincipal, const nsAString& aCacheName) {
1436 MOZ_ASSERT(NS_IsMainThread());
1437 MOZ_ASSERT(aPrincipal);
1439 if (aCacheName.IsEmpty()) {
1440 return NS_OK;
1443 AutoJSAPI jsapi;
1444 jsapi.Init();
1445 ErrorResult rv;
1446 RefPtr<CacheStorage> cacheStorage =
1447 CreateCacheStorage(jsapi.cx(), aPrincipal, rv);
1448 if (NS_WARN_IF(rv.Failed())) {
1449 return rv.StealNSResult();
1452 // We use the ServiceWorker scope as key for the cacheStorage.
1453 RefPtr<Promise> promise = cacheStorage->Delete(aCacheName, rv);
1454 if (NS_WARN_IF(rv.Failed())) {
1455 return rv.StealNSResult();
1458 // Set [[PromiseIsHandled]] to ensure that if this promise gets rejected,
1459 // we don't end up reporting a rejected promise to the console.
1460 MOZ_ALWAYS_TRUE(promise->SetAnyPromiseIsHandled());
1462 // We don't actually care about the result of the delete operation.
1463 return NS_OK;
1466 nsresult GenerateCacheName(nsAString& aName) {
1467 nsresult rv;
1468 nsCOMPtr<nsIUUIDGenerator> uuidGenerator =
1469 do_GetService("@mozilla.org/uuid-generator;1", &rv);
1470 if (NS_WARN_IF(NS_FAILED(rv))) {
1471 return rv;
1474 nsID id;
1475 rv = uuidGenerator->GenerateUUIDInPlace(&id);
1476 if (NS_WARN_IF(NS_FAILED(rv))) {
1477 return rv;
1480 char chars[NSID_LENGTH];
1481 id.ToProvidedString(chars);
1483 // NSID_LENGTH counts the null terminator.
1484 aName.AssignASCII(chars, NSID_LENGTH - 1);
1486 return NS_OK;
1489 nsresult Compare(ServiceWorkerRegistrationInfo* aRegistration,
1490 nsIPrincipal* aPrincipal, const nsAString& aCacheName,
1491 const nsAString& aURL, CompareCallback* aCallback) {
1492 MOZ_ASSERT(NS_IsMainThread());
1493 MOZ_ASSERT(aRegistration);
1494 MOZ_ASSERT(aPrincipal);
1495 MOZ_ASSERT(!aURL.IsEmpty());
1496 MOZ_ASSERT(aCallback);
1498 RefPtr<CompareManager> cm = new CompareManager(aRegistration, aCallback);
1500 nsresult rv = cm->Initialize(aPrincipal, aURL, aCacheName);
1501 if (NS_WARN_IF(NS_FAILED(rv))) {
1502 return rv;
1505 return NS_OK;
1508 } // namespace mozilla::dom::serviceWorkerScriptCache