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