tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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