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