forked from mirrors/gecko-dev
		
	 265e672179
			
		
	
	
		265e672179
		
	
	
	
	
		
			
			# ignore-this-changeset --HG-- extra : amend_source : 4d301d3b0b8711c4692392aa76088ba7fd7d1022
		
			
				
	
	
		
			1358 lines
		
	
	
	
		
			38 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1358 lines
		
	
	
	
		
			38 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "ServiceWorkerScriptCache.h"
 | |
| #include "mozilla/SystemGroup.h"
 | |
| #include "mozilla/Unused.h"
 | |
| #include "mozilla/dom/CacheBinding.h"
 | |
| #include "mozilla/dom/cache/CacheStorage.h"
 | |
| #include "mozilla/dom/cache/Cache.h"
 | |
| #include "mozilla/dom/Promise.h"
 | |
| #include "mozilla/dom/PromiseWorkerProxy.h"
 | |
| #include "mozilla/dom/ScriptLoader.h"
 | |
| #include "mozilla/dom/WorkerCommon.h"
 | |
| #include "mozilla/ipc/BackgroundUtils.h"
 | |
| #include "mozilla/ipc/PBackgroundSharedTypes.h"
 | |
| #include "nsICacheInfoChannel.h"
 | |
| #include "nsIHttpChannelInternal.h"
 | |
| #include "nsIStreamLoader.h"
 | |
| #include "nsIThreadRetargetableRequest.h"
 | |
| 
 | |
| #include "nsIInputStreamPump.h"
 | |
| #include "nsIPrincipal.h"
 | |
| #include "nsIScriptError.h"
 | |
| #include "nsIScriptSecurityManager.h"
 | |
| #include "nsContentUtils.h"
 | |
| #include "nsNetUtil.h"
 | |
| #include "ServiceWorkerManager.h"
 | |
| #include "nsStringStream.h"
 | |
| 
 | |
| using mozilla::dom::cache::Cache;
 | |
| using mozilla::dom::cache::CacheStorage;
 | |
| using mozilla::ipc::PrincipalInfo;
 | |
| 
 | |
| namespace mozilla {
 | |
| namespace dom {
 | |
| 
 | |
| namespace serviceWorkerScriptCache {
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| already_AddRefed<CacheStorage> CreateCacheStorage(JSContext* aCx,
 | |
|                                                   nsIPrincipal* aPrincipal,
 | |
|                                                   ErrorResult& aRv) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   MOZ_ASSERT(aPrincipal);
 | |
| 
 | |
|   nsIXPConnect* xpc = nsContentUtils::XPConnect();
 | |
|   MOZ_ASSERT(xpc, "This should never be null!");
 | |
|   JS::Rooted<JSObject*> sandbox(aCx);
 | |
|   aRv = xpc->CreateSandbox(aCx, aPrincipal, sandbox.address());
 | |
|   if (NS_WARN_IF(aRv.Failed())) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   // This is called when the JSContext is not in a realm, so CreateSandbox
 | |
|   // returned an unwrapped global.
 | |
|   MOZ_ASSERT(JS_IsGlobalObject(sandbox));
 | |
| 
 | |
|   nsCOMPtr<nsIGlobalObject> sandboxGlobalObject = xpc::NativeGlobal(sandbox);
 | |
|   if (!sandboxGlobalObject) {
 | |
|     aRv.Throw(NS_ERROR_FAILURE);
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   // We assume private browsing is not enabled here.  The ScriptLoader
 | |
|   // explicitly fails for private browsing so there should never be
 | |
|   // a service worker running in private browsing mode.  Therefore if
 | |
|   // we are purging scripts or running a comparison algorithm we cannot
 | |
|   // be in private browing.
 | |
|   //
 | |
|   // Also, bypass the CacheStorage trusted origin checks.  The ServiceWorker
 | |
|   // has validated the origin prior to this point.  All the information
 | |
|   // to revalidate is not available now.
 | |
|   return CacheStorage::CreateOnMainThread(cache::CHROME_ONLY_NAMESPACE,
 | |
|                                           sandboxGlobalObject, aPrincipal,
 | |
|                                           true /* force trusted origin */, aRv);
 | |
| }
 | |
| 
 | |
| class CompareManager;
 | |
| class CompareCache;
 | |
| 
 | |
| // This class downloads a URL from the network, compare the downloaded script
 | |
| // with an existing cache if provided, and report to CompareManager via calling
 | |
| // ComparisonFinished().
 | |
| class CompareNetwork final : public nsIStreamLoaderObserver,
 | |
|                              public nsIRequestObserver {
 | |
|  public:
 | |
|   NS_DECL_ISUPPORTS
 | |
|   NS_DECL_NSISTREAMLOADEROBSERVER
 | |
|   NS_DECL_NSIREQUESTOBSERVER
 | |
| 
 | |
|   CompareNetwork(CompareManager* aManager,
 | |
|                  ServiceWorkerRegistrationInfo* aRegistration,
 | |
|                  bool aIsMainScript)
 | |
|       : mManager(aManager),
 | |
|         mRegistration(aRegistration),
 | |
|         mInternalHeaders(new InternalHeaders()),
 | |
|         mLoadFlags(nsIChannel::LOAD_BYPASS_SERVICE_WORKER),
 | |
|         mState(WaitingForInitialization),
 | |
|         mNetworkResult(NS_OK),
 | |
|         mCacheResult(NS_OK),
 | |
|         mIsMainScript(aIsMainScript),
 | |
|         mIsFromCache(false) {
 | |
|     MOZ_ASSERT(aManager);
 | |
|     MOZ_ASSERT(NS_IsMainThread());
 | |
|   }
 | |
| 
 | |
|   nsresult Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL,
 | |
|                       nsILoadGroup* aLoadGroup, Cache* const aCache);
 | |
| 
 | |
|   void Abort();
 | |
| 
 | |
|   void NetworkFinish(nsresult aRv);
 | |
| 
 | |
|   void CacheFinish(nsresult aRv);
 | |
| 
 | |
|   const nsString& URL() const {
 | |
|     MOZ_ASSERT(NS_IsMainThread());
 | |
|     return mURL;
 | |
|   }
 | |
| 
 | |
|   const nsString& Buffer() const {
 | |
|     MOZ_ASSERT(NS_IsMainThread());
 | |
|     return mBuffer;
 | |
|   }
 | |
| 
 | |
|   const ChannelInfo& GetChannelInfo() const { return mChannelInfo; }
 | |
| 
 | |
|   already_AddRefed<InternalHeaders> GetInternalHeaders() const {
 | |
|     RefPtr<InternalHeaders> internalHeaders = mInternalHeaders;
 | |
|     return internalHeaders.forget();
 | |
|   }
 | |
| 
 | |
|   UniquePtr<PrincipalInfo> TakePrincipalInfo() {
 | |
|     return std::move(mPrincipalInfo);
 | |
|   }
 | |
| 
 | |
|   bool Succeeded() const { return NS_SUCCEEDED(mNetworkResult); }
 | |
| 
 | |
|   const nsTArray<nsCString>& URLList() const { return mURLList; }
 | |
| 
 | |
|  private:
 | |
|   ~CompareNetwork() {
 | |
|     MOZ_ASSERT(NS_IsMainThread());
 | |
|     MOZ_ASSERT(!mCC);
 | |
|   }
 | |
| 
 | |
|   void Finish();
 | |
| 
 | |
|   nsresult SetPrincipalInfo(nsIChannel* aChannel);
 | |
| 
 | |
|   RefPtr<CompareManager> mManager;
 | |
|   RefPtr<CompareCache> mCC;
 | |
|   RefPtr<ServiceWorkerRegistrationInfo> mRegistration;
 | |
| 
 | |
|   nsCOMPtr<nsIChannel> mChannel;
 | |
|   nsString mBuffer;
 | |
|   nsString mURL;
 | |
|   ChannelInfo mChannelInfo;
 | |
|   RefPtr<InternalHeaders> mInternalHeaders;
 | |
|   UniquePtr<PrincipalInfo> mPrincipalInfo;
 | |
|   nsTArray<nsCString> mURLList;
 | |
| 
 | |
|   nsCString mMaxScope;
 | |
|   nsLoadFlags mLoadFlags;
 | |
| 
 | |
|   enum {
 | |
|     WaitingForInitialization,
 | |
|     WaitingForBothFinished,
 | |
|     WaitingForNetworkFinished,
 | |
|     WaitingForCacheFinished,
 | |
|     Finished
 | |
|   } mState;
 | |
| 
 | |
|   nsresult mNetworkResult;
 | |
|   nsresult mCacheResult;
 | |
| 
 | |
|   const bool mIsMainScript;
 | |
|   bool mIsFromCache;
 | |
| };
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(CompareNetwork, nsIStreamLoaderObserver, nsIRequestObserver)
 | |
| 
 | |
| // This class gets a cached Response from the CacheStorage and then it calls
 | |
| // CacheFinish() in the CompareNetwork.
 | |
| class CompareCache final : public PromiseNativeHandler,
 | |
|                            public nsIStreamLoaderObserver {
 | |
|  public:
 | |
|   NS_DECL_ISUPPORTS
 | |
|   NS_DECL_NSISTREAMLOADEROBSERVER
 | |
| 
 | |
|   explicit CompareCache(CompareNetwork* aCN)
 | |
|       : mCN(aCN), mState(WaitingForInitialization), mInCache(false) {
 | |
|     MOZ_ASSERT(aCN);
 | |
|     MOZ_ASSERT(NS_IsMainThread());
 | |
|   }
 | |
| 
 | |
|   nsresult Initialize(Cache* const aCache, const nsAString& aURL);
 | |
| 
 | |
|   void Finish(nsresult aStatus, bool aInCache);
 | |
| 
 | |
|   void Abort();
 | |
| 
 | |
|   virtual void ResolvedCallback(JSContext* aCx,
 | |
|                                 JS::Handle<JS::Value> aValue) override;
 | |
| 
 | |
|   virtual void RejectedCallback(JSContext* aCx,
 | |
|                                 JS::Handle<JS::Value> aValue) override;
 | |
| 
 | |
|   const nsString& Buffer() const {
 | |
|     MOZ_ASSERT(NS_IsMainThread());
 | |
|     return mBuffer;
 | |
|   }
 | |
| 
 | |
|   bool InCache() { return mInCache; }
 | |
| 
 | |
|  private:
 | |
|   ~CompareCache() { MOZ_ASSERT(NS_IsMainThread()); }
 | |
| 
 | |
|   void ManageValueResult(JSContext* aCx, JS::Handle<JS::Value> aValue);
 | |
| 
 | |
|   RefPtr<CompareNetwork> mCN;
 | |
|   nsCOMPtr<nsIInputStreamPump> mPump;
 | |
| 
 | |
|   nsString mURL;
 | |
|   nsString mBuffer;
 | |
| 
 | |
|   enum {
 | |
|     WaitingForInitialization,
 | |
|     WaitingForScript,
 | |
|     Finished,
 | |
|   } mState;
 | |
| 
 | |
|   bool mInCache;
 | |
| };
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(CompareCache, nsIStreamLoaderObserver)
 | |
| 
 | |
| class CompareManager final : public PromiseNativeHandler {
 | |
|  public:
 | |
|   NS_DECL_ISUPPORTS
 | |
| 
 | |
|   explicit CompareManager(ServiceWorkerRegistrationInfo* aRegistration,
 | |
|                           CompareCallback* aCallback)
 | |
|       : mRegistration(aRegistration),
 | |
|         mCallback(aCallback),
 | |
|         mLoadFlags(nsIChannel::LOAD_BYPASS_SERVICE_WORKER),
 | |
|         mState(WaitingForInitialization),
 | |
|         mPendingCount(0),
 | |
|         mOnFailure(OnFailure::DoNothing),
 | |
|         mAreScriptsEqual(true) {
 | |
|     MOZ_ASSERT(NS_IsMainThread());
 | |
|     MOZ_ASSERT(aRegistration);
 | |
|   }
 | |
| 
 | |
|   nsresult Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL,
 | |
|                       const nsAString& aCacheName, nsILoadGroup* aLoadGroup);
 | |
| 
 | |
|   void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
 | |
| 
 | |
|   void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
 | |
| 
 | |
|   CacheStorage* CacheStorage_() {
 | |
|     MOZ_ASSERT(NS_IsMainThread());
 | |
|     MOZ_ASSERT(mCacheStorage);
 | |
|     return mCacheStorage;
 | |
|   }
 | |
| 
 | |
|   void ComparisonFinished(nsresult aStatus, bool aIsMainScript, bool aIsEqual,
 | |
|                           const nsACString& aMaxScope, nsLoadFlags aLoadFlags) {
 | |
|     MOZ_ASSERT(NS_IsMainThread());
 | |
|     if (mState == Finished) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForScriptOrComparisonResult);
 | |
| 
 | |
|     if (NS_WARN_IF(NS_FAILED(aStatus))) {
 | |
|       Fail(aStatus);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     mAreScriptsEqual = mAreScriptsEqual && aIsEqual;
 | |
| 
 | |
|     if (aIsMainScript) {
 | |
|       mMaxScope = aMaxScope;
 | |
|       mLoadFlags = aLoadFlags;
 | |
|     }
 | |
| 
 | |
|     // Check whether all CompareNetworks finished their jobs.
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mPendingCount > 0);
 | |
|     if (--mPendingCount) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (mAreScriptsEqual) {
 | |
|       MOZ_ASSERT(mCallback);
 | |
|       mCallback->ComparisonResult(aStatus, true /* aSameScripts */, mOnFailure,
 | |
|                                   EmptyString(), mMaxScope, mLoadFlags);
 | |
|       Cleanup();
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // Write to Cache so ScriptLoader reads succeed.
 | |
|     WriteNetworkBufferToNewCache();
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   ~CompareManager() {
 | |
|     MOZ_ASSERT(NS_IsMainThread());
 | |
|     MOZ_ASSERT(mCNList.Length() == 0);
 | |
|   }
 | |
| 
 | |
|   void Fail(nsresult aStatus);
 | |
| 
 | |
|   void Cleanup();
 | |
| 
 | |
|   nsresult FetchScript(const nsAString& aURL, bool aIsMainScript,
 | |
|                        Cache* const aCache = nullptr) {
 | |
|     MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForInitialization ||
 | |
|                           mState == WaitingForScriptOrComparisonResult);
 | |
| 
 | |
|     RefPtr<CompareNetwork> cn =
 | |
|         new CompareNetwork(this, mRegistration, aIsMainScript);
 | |
|     mCNList.AppendElement(cn);
 | |
|     mPendingCount += 1;
 | |
| 
 | |
|     nsresult rv = cn->Initialize(mPrincipal, aURL, mLoadGroup, aCache);
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       return rv;
 | |
|     }
 | |
| 
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   void ManageOldCache(JSContext* aCx, JS::Handle<JS::Value> aValue) {
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForExistingOpen);
 | |
| 
 | |
|     // RAII Cleanup when fails.
 | |
|     nsresult rv = NS_ERROR_FAILURE;
 | |
|     auto guard = MakeScopeExit([&] { Fail(rv); });
 | |
| 
 | |
|     if (NS_WARN_IF(!aValue.isObject())) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     MOZ_ASSERT(!mOldCache);
 | |
|     JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
 | |
|     if (NS_WARN_IF(!obj) ||
 | |
|         NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Cache, obj, mOldCache)))) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     Optional<RequestOrUSVString> request;
 | |
|     CacheQueryOptions options;
 | |
|     ErrorResult error;
 | |
|     RefPtr<Promise> promise = mOldCache->Keys(aCx, request, options, error);
 | |
|     if (NS_WARN_IF(error.Failed())) {
 | |
|       // No exception here because there are no ReadableStreams involved here.
 | |
|       MOZ_ASSERT(!error.IsJSException());
 | |
|       rv = error.StealNSResult();
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     mState = WaitingForExistingKeys;
 | |
|     promise->AppendNativeHandler(this);
 | |
|     guard.release();
 | |
|   }
 | |
| 
 | |
|   void ManageOldKeys(JSContext* aCx, JS::Handle<JS::Value> aValue) {
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForExistingKeys);
 | |
| 
 | |
|     // RAII Cleanup when fails.
 | |
|     nsresult rv = NS_ERROR_FAILURE;
 | |
|     auto guard = MakeScopeExit([&] { Fail(rv); });
 | |
| 
 | |
|     if (NS_WARN_IF(!aValue.isObject())) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
 | |
|     if (NS_WARN_IF(!obj)) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     uint32_t len = 0;
 | |
|     if (!JS_GetArrayLength(aCx, obj, &len)) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // Fetch and compare the source scripts.
 | |
|     MOZ_ASSERT(mPendingCount == 0);
 | |
| 
 | |
|     mState = WaitingForScriptOrComparisonResult;
 | |
| 
 | |
|     bool hasMainScript = false;
 | |
|     AutoTArray<nsString, 8> urlList;
 | |
| 
 | |
|     // Extract the list of URLs in the old cache.
 | |
|     for (uint32_t i = 0; i < len; ++i) {
 | |
|       JS::Rooted<JS::Value> val(aCx);
 | |
|       if (NS_WARN_IF(!JS_GetElement(aCx, obj, i, &val)) ||
 | |
|           NS_WARN_IF(!val.isObject())) {
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       Request* request;
 | |
|       JS::Rooted<JSObject*> requestObj(aCx, &val.toObject());
 | |
|       if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Request, &requestObj, request)))) {
 | |
|         return;
 | |
|       };
 | |
| 
 | |
|       nsString url;
 | |
|       request->GetUrl(url);
 | |
| 
 | |
|       if (!hasMainScript && url == mURL) {
 | |
|         hasMainScript = true;
 | |
|       }
 | |
| 
 | |
|       urlList.AppendElement(url);
 | |
|     }
 | |
| 
 | |
|     // If the main script is missing, then something has gone wrong.  We
 | |
|     // will try to continue with the update process to trigger a new
 | |
|     // installation.  If that fails, however, then uninstall the registration
 | |
|     // because it is broken in a way that cannot be fixed.
 | |
|     if (!hasMainScript) {
 | |
|       mOnFailure = OnFailure::Uninstall;
 | |
|     }
 | |
| 
 | |
|     // Always make sure to fetch the main script.  If the old cache has
 | |
|     // no entries or the main script entry is missing, then the loop below
 | |
|     // may not trigger it.  This should not really happen, but we handle it
 | |
|     // gracefully if it does occur.  Its possible the bad cache state is due
 | |
|     // to a crash or shutdown during an update, etc.
 | |
|     rv = FetchScript(mURL, true /* aIsMainScript */, mOldCache);
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     for (const auto& url : urlList) {
 | |
|       // We explicitly start the fetch for the main script above.
 | |
|       if (mURL == url) {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       rv = FetchScript(url, false /* aIsMainScript */, mOldCache);
 | |
|       if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|         return;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     guard.release();
 | |
|   }
 | |
| 
 | |
|   void ManageNewCache(JSContext* aCx, JS::Handle<JS::Value> aValue) {
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForOpen);
 | |
| 
 | |
|     // RAII Cleanup when fails.
 | |
|     nsresult rv = NS_ERROR_FAILURE;
 | |
|     auto guard = MakeScopeExit([&] { Fail(rv); });
 | |
| 
 | |
|     if (NS_WARN_IF(!aValue.isObject())) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
 | |
|     if (NS_WARN_IF(!obj)) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     Cache* cache = nullptr;
 | |
|     rv = UNWRAP_OBJECT(Cache, &obj, cache);
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // Just to be safe.
 | |
|     RefPtr<Cache> kungfuDeathGrip = cache;
 | |
| 
 | |
|     MOZ_ASSERT(mPendingCount == 0);
 | |
|     for (uint32_t i = 0; i < mCNList.Length(); ++i) {
 | |
|       // We bail out immediately when something goes wrong.
 | |
|       rv = WriteToCache(aCx, cache, mCNList[i]);
 | |
|       if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|         return;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     mState = WaitingForPut;
 | |
|     guard.release();
 | |
|   }
 | |
| 
 | |
|   void WriteNetworkBufferToNewCache() {
 | |
|     MOZ_ASSERT(NS_IsMainThread());
 | |
|     MOZ_ASSERT(mCNList.Length() != 0);
 | |
|     MOZ_ASSERT(mCacheStorage);
 | |
|     MOZ_ASSERT(mNewCacheName.IsEmpty());
 | |
| 
 | |
|     ErrorResult result;
 | |
|     result = serviceWorkerScriptCache::GenerateCacheName(mNewCacheName);
 | |
|     if (NS_WARN_IF(result.Failed())) {
 | |
|       MOZ_ASSERT(!result.IsErrorWithMessage());
 | |
|       Fail(result.StealNSResult());
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     RefPtr<Promise> cacheOpenPromise =
 | |
|         mCacheStorage->Open(mNewCacheName, result);
 | |
|     if (NS_WARN_IF(result.Failed())) {
 | |
|       MOZ_ASSERT(!result.IsErrorWithMessage());
 | |
|       Fail(result.StealNSResult());
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     mState = WaitingForOpen;
 | |
|     cacheOpenPromise->AppendNativeHandler(this);
 | |
|   }
 | |
| 
 | |
|   nsresult WriteToCache(JSContext* aCx, Cache* aCache, CompareNetwork* aCN) {
 | |
|     MOZ_ASSERT(NS_IsMainThread());
 | |
|     MOZ_ASSERT(aCache);
 | |
|     MOZ_ASSERT(aCN);
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForOpen);
 | |
| 
 | |
|     // We don't have to save any information from a failed CompareNetwork.
 | |
|     if (!aCN->Succeeded()) {
 | |
|       return NS_OK;
 | |
|     }
 | |
| 
 | |
|     nsCOMPtr<nsIInputStream> body;
 | |
|     nsresult rv = NS_NewCStringInputStream(
 | |
|         getter_AddRefs(body), NS_ConvertUTF16toUTF8(aCN->Buffer()));
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       return rv;
 | |
|     }
 | |
| 
 | |
|     RefPtr<InternalResponse> ir =
 | |
|         new InternalResponse(200, NS_LITERAL_CSTRING("OK"));
 | |
|     ir->SetBody(body, aCN->Buffer().Length());
 | |
|     ir->SetURLList(aCN->URLList());
 | |
| 
 | |
|     ir->InitChannelInfo(aCN->GetChannelInfo());
 | |
|     UniquePtr<PrincipalInfo> principalInfo = aCN->TakePrincipalInfo();
 | |
|     if (principalInfo) {
 | |
|       ir->SetPrincipalInfo(std::move(principalInfo));
 | |
|     }
 | |
| 
 | |
|     RefPtr<InternalHeaders> internalHeaders = aCN->GetInternalHeaders();
 | |
|     ir->Headers()->Fill(*(internalHeaders.get()), IgnoreErrors());
 | |
| 
 | |
|     RefPtr<Response> response =
 | |
|         new Response(aCache->GetGlobalObject(), ir, nullptr);
 | |
| 
 | |
|     RequestOrUSVString request;
 | |
|     request.SetAsUSVString().Rebind(aCN->URL().Data(), aCN->URL().Length());
 | |
| 
 | |
|     // For now we have to wait until the Put Promise is fulfilled before we can
 | |
|     // continue since Cache does not yet support starting a read that is being
 | |
|     // written to.
 | |
|     ErrorResult result;
 | |
|     RefPtr<Promise> cachePromise = aCache->Put(aCx, request, *response, result);
 | |
|     result.WouldReportJSException();
 | |
|     if (NS_WARN_IF(result.Failed())) {
 | |
|       // No exception here because there are no ReadableStreams involved here.
 | |
|       MOZ_ASSERT(!result.IsJSException());
 | |
|       MOZ_ASSERT(!result.IsErrorWithMessage());
 | |
|       return result.StealNSResult();
 | |
|     }
 | |
| 
 | |
|     mPendingCount += 1;
 | |
|     cachePromise->AppendNativeHandler(this);
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   RefPtr<ServiceWorkerRegistrationInfo> mRegistration;
 | |
|   RefPtr<CompareCallback> mCallback;
 | |
|   RefPtr<CacheStorage> mCacheStorage;
 | |
| 
 | |
|   nsTArray<RefPtr<CompareNetwork>> mCNList;
 | |
| 
 | |
|   nsString mURL;
 | |
|   RefPtr<nsIPrincipal> mPrincipal;
 | |
|   RefPtr<nsILoadGroup> mLoadGroup;
 | |
| 
 | |
|   // Used for the old cache where saves the old source scripts.
 | |
|   RefPtr<Cache> mOldCache;
 | |
| 
 | |
|   // Only used if the network script has changed and needs to be cached.
 | |
|   nsString mNewCacheName;
 | |
| 
 | |
|   nsCString mMaxScope;
 | |
|   nsLoadFlags mLoadFlags;
 | |
| 
 | |
|   enum {
 | |
|     WaitingForInitialization,
 | |
|     WaitingForExistingOpen,
 | |
|     WaitingForExistingKeys,
 | |
|     WaitingForScriptOrComparisonResult,
 | |
|     WaitingForOpen,
 | |
|     WaitingForPut,
 | |
|     Finished
 | |
|   } mState;
 | |
| 
 | |
|   uint32_t mPendingCount;
 | |
|   OnFailure mOnFailure;
 | |
|   bool mAreScriptsEqual;
 | |
| };
 | |
| 
 | |
| NS_IMPL_ISUPPORTS0(CompareManager)
 | |
| 
 | |
| nsresult CompareNetwork::Initialize(nsIPrincipal* aPrincipal,
 | |
|                                     const nsAString& aURL,
 | |
|                                     nsILoadGroup* aLoadGroup,
 | |
|                                     Cache* const aCache) {
 | |
|   MOZ_ASSERT(aPrincipal);
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   nsCOMPtr<nsIURI> uri;
 | |
|   nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr, nullptr);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   mURL = aURL;
 | |
|   mURLList.AppendElement(NS_ConvertUTF16toUTF8(mURL));
 | |
| 
 | |
|   nsCOMPtr<nsILoadGroup> loadGroup;
 | |
|   rv = NS_NewLoadGroup(getter_AddRefs(loadGroup), aPrincipal);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   // Update LoadFlags for propagating to ServiceWorkerInfo.
 | |
|   mLoadFlags = nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
 | |
| 
 | |
|   ServiceWorkerUpdateViaCache uvc = mRegistration->GetUpdateViaCache();
 | |
|   if (uvc == ServiceWorkerUpdateViaCache::None ||
 | |
|       (uvc == ServiceWorkerUpdateViaCache::Imports && mIsMainScript)) {
 | |
|     mLoadFlags |= nsIRequest::VALIDATE_ALWAYS;
 | |
|   }
 | |
| 
 | |
|   if (mRegistration->IsLastUpdateCheckTimeOverOneDay()) {
 | |
|     mLoadFlags |= nsIRequest::LOAD_BYPASS_CACHE;
 | |
|   }
 | |
| 
 | |
|   // Different settings are needed for fetching imported scripts, since they
 | |
|   // might be cross-origin scripts.
 | |
|   uint32_t secFlags = mIsMainScript
 | |
|                           ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
 | |
|                           : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
 | |
| 
 | |
|   nsContentPolicyType contentPolicyType =
 | |
|       mIsMainScript ? nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER
 | |
|                     : nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS;
 | |
| 
 | |
|   // Note that because there is no "serviceworker" RequestContext type, we can
 | |
|   // use the TYPE_INTERNAL_SCRIPT content policy types when loading a service
 | |
|   // worker.
 | |
|   rv = NS_NewChannel(getter_AddRefs(mChannel), uri, aPrincipal, secFlags,
 | |
|                      contentPolicyType, nullptr, /* aPerformanceStorage */
 | |
|                      loadGroup, nullptr /* aCallbacks */, mLoadFlags);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
 | |
|   if (httpChannel) {
 | |
|     // Spec says no redirects allowed for top-level SW scripts.
 | |
|     if (mIsMainScript) {
 | |
|       rv = httpChannel->SetRedirectionLimit(0);
 | |
|       MOZ_ASSERT(NS_SUCCEEDED(rv));
 | |
|     }
 | |
| 
 | |
|     rv = httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Service-Worker"),
 | |
|                                        NS_LITERAL_CSTRING("script"),
 | |
|                                        /* merge */ false);
 | |
|     MOZ_ASSERT(NS_SUCCEEDED(rv));
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIStreamLoader> loader;
 | |
|   rv = NS_NewStreamLoader(getter_AddRefs(loader), this, this);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   rv = mChannel->AsyncOpen2(loader);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   // If we do have an existing cache to compare with.
 | |
|   if (aCache) {
 | |
|     mCC = new CompareCache(this);
 | |
|     rv = mCC->Initialize(aCache, aURL);
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       Abort();
 | |
|       return rv;
 | |
|     }
 | |
| 
 | |
|     mState = WaitingForBothFinished;
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   mState = WaitingForNetworkFinished;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void CompareNetwork::Finish() {
 | |
|   if (mState == Finished) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   bool same = true;
 | |
|   nsresult rv = NS_OK;
 | |
| 
 | |
|   // mNetworkResult is prior to mCacheResult, since it's needed for reporting
 | |
|   // various errors to web content.
 | |
|   if (NS_FAILED(mNetworkResult)) {
 | |
|     // An imported script could become offline, since it might no longer be
 | |
|     // needed by the new importing script. In that case, the importing script
 | |
|     // must be different, and thus, it's okay to report same script found here.
 | |
|     rv = mIsMainScript ? mNetworkResult : NS_OK;
 | |
|     same = true;
 | |
|   } else if (mCC && NS_FAILED(mCacheResult)) {
 | |
|     rv = mCacheResult;
 | |
|   } else {  // Both passed.
 | |
|     same = mCC && mCC->InCache() && mCC->Buffer().Equals(mBuffer);
 | |
|   }
 | |
| 
 | |
|   mManager->ComparisonFinished(rv, mIsMainScript, same, mMaxScope, mLoadFlags);
 | |
| 
 | |
|   // We have done with the CompareCache.
 | |
|   mCC = nullptr;
 | |
| }
 | |
| 
 | |
| void CompareNetwork::NetworkFinish(nsresult aRv) {
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForBothFinished ||
 | |
|                         mState == WaitingForNetworkFinished);
 | |
| 
 | |
|   mNetworkResult = aRv;
 | |
| 
 | |
|   if (mState == WaitingForBothFinished) {
 | |
|     mState = WaitingForCacheFinished;
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (mState == WaitingForNetworkFinished) {
 | |
|     Finish();
 | |
|     return;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void CompareNetwork::CacheFinish(nsresult aRv) {
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForBothFinished ||
 | |
|                         mState == WaitingForCacheFinished);
 | |
| 
 | |
|   mCacheResult = aRv;
 | |
| 
 | |
|   if (mState == WaitingForBothFinished) {
 | |
|     mState = WaitingForNetworkFinished;
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (mState == WaitingForCacheFinished) {
 | |
|     Finish();
 | |
|     return;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void CompareNetwork::Abort() {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   if (mState != Finished) {
 | |
|     mState = Finished;
 | |
| 
 | |
|     MOZ_ASSERT(mChannel);
 | |
|     mChannel->Cancel(NS_BINDING_ABORTED);
 | |
|     mChannel = nullptr;
 | |
| 
 | |
|     if (mCC) {
 | |
|       mCC->Abort();
 | |
|       mCC = nullptr;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| CompareNetwork::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   if (mState == Finished) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
 | |
|   MOZ_ASSERT_IF(mIsMainScript, channel == mChannel);
 | |
|   mChannel = channel;
 | |
| 
 | |
|   MOZ_ASSERT(!mChannelInfo.IsInitialized());
 | |
|   mChannelInfo.InitFromChannel(mChannel);
 | |
| 
 | |
|   nsresult rv = SetPrincipalInfo(mChannel);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   mInternalHeaders->FillResponseHeaders(mChannel);
 | |
| 
 | |
|   nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(channel));
 | |
|   if (cacheChannel) {
 | |
|     cacheChannel->IsFromCache(&mIsFromCache);
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult CompareNetwork::SetPrincipalInfo(nsIChannel* aChannel) {
 | |
|   nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
 | |
|   if (!ssm) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIPrincipal> channelPrincipal;
 | |
|   nsresult rv = ssm->GetChannelResultPrincipal(
 | |
|       aChannel, getter_AddRefs(channelPrincipal));
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   UniquePtr<PrincipalInfo> principalInfo = MakeUnique<PrincipalInfo>();
 | |
|   rv = PrincipalToPrincipalInfo(channelPrincipal, principalInfo.get());
 | |
| 
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   mPrincipalInfo = std::move(principalInfo);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| CompareNetwork::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext,
 | |
|                               nsresult aStatusCode) {
 | |
|   // Nothing to do here!
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| CompareNetwork::OnStreamComplete(nsIStreamLoader* aLoader,
 | |
|                                  nsISupports* aContext, nsresult aStatus,
 | |
|                                  uint32_t aLen, const uint8_t* aString) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   if (mState == Finished) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   nsresult rv = NS_ERROR_FAILURE;
 | |
|   auto guard = MakeScopeExit([&] { NetworkFinish(rv); });
 | |
| 
 | |
|   if (NS_WARN_IF(NS_FAILED(aStatus))) {
 | |
|     rv = (aStatus == NS_ERROR_REDIRECT_LOOP) ? NS_ERROR_DOM_SECURITY_ERR
 | |
|                                              : aStatus;
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIRequest> request;
 | |
|   rv = aLoader->GetRequest(getter_AddRefs(request));
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request);
 | |
|   MOZ_ASSERT(httpChannel, "How come we don't have an HTTP channel?");
 | |
| 
 | |
|   bool requestSucceeded;
 | |
|   rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   if (NS_WARN_IF(!requestSucceeded)) {
 | |
|     // Get the stringified numeric status code, not statusText which could be
 | |
|     // something misleading like OK for a 404.
 | |
|     uint32_t status = 0;
 | |
|     Unused << httpChannel->GetResponseStatus(
 | |
|         &status);  // don't care if this fails, use 0.
 | |
|     nsAutoString statusAsText;
 | |
|     statusAsText.AppendInt(status);
 | |
| 
 | |
|     ServiceWorkerManager::LocalizeAndReportToAllClients(
 | |
|         mRegistration->Scope(), "ServiceWorkerRegisterNetworkError",
 | |
|         nsTArray<nsString>{NS_ConvertUTF8toUTF16(mRegistration->Scope()),
 | |
|                            statusAsText, mURL});
 | |
| 
 | |
|     rv = NS_ERROR_FAILURE;
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // Note: we explicitly don't check for the return value here, because the
 | |
|   // absence of the header is not an error condition.
 | |
|   Unused << httpChannel->GetResponseHeader(
 | |
|       NS_LITERAL_CSTRING("Service-Worker-Allowed"), mMaxScope);
 | |
| 
 | |
|   // [9.2 Update]4.13, If response's cache state is not "local",
 | |
|   // set registration's last update check time to the current time
 | |
|   if (!mIsFromCache) {
 | |
|     mRegistration->RefreshLastUpdateCheckTime();
 | |
|   }
 | |
| 
 | |
|   nsAutoCString mimeType;
 | |
|   rv = httpChannel->GetContentType(mimeType);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     // We should only end up here if !mResponseHead in the channel.  If headers
 | |
|     // were received but no content type was specified, we'll be given
 | |
|     // UNKNOWN_CONTENT_TYPE "application/x-unknown-content-type" and so fall
 | |
|     // into the next case with its better error message.
 | |
|     rv = NS_ERROR_DOM_SECURITY_ERR;
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   if (mimeType.IsEmpty() ||
 | |
|       !nsContentUtils::IsJavascriptMIMEType(NS_ConvertUTF8toUTF16(mimeType))) {
 | |
|     ServiceWorkerManager::LocalizeAndReportToAllClients(
 | |
|         mRegistration->Scope(), "ServiceWorkerRegisterMimeTypeError2",
 | |
|         nsTArray<nsString>{NS_ConvertUTF8toUTF16(mRegistration->Scope()),
 | |
|                            NS_ConvertUTF8toUTF16(mimeType), mURL});
 | |
|     rv = NS_ERROR_DOM_SECURITY_ERR;
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIURI> channelURL;
 | |
|   rv = httpChannel->GetURI(getter_AddRefs(channelURL));
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   nsCString channelURLSpec;
 | |
|   MOZ_ALWAYS_SUCCEEDS(channelURL->GetSpec(channelURLSpec));
 | |
| 
 | |
|   // Append the final URL if its different from the original
 | |
|   // request URL.  This lets us note that a redirect occurred
 | |
|   // even though we don't track every redirect URL here.
 | |
|   MOZ_DIAGNOSTIC_ASSERT(!mURLList.IsEmpty());
 | |
|   if (channelURLSpec != mURLList[0]) {
 | |
|     mURLList.AppendElement(channelURLSpec);
 | |
|   }
 | |
| 
 | |
|   char16_t* buffer = nullptr;
 | |
|   size_t len = 0;
 | |
| 
 | |
|   rv = ScriptLoader::ConvertToUTF16(httpChannel, aString, aLen,
 | |
|                                     NS_LITERAL_STRING("UTF-8"), nullptr, buffer,
 | |
|                                     len);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   mBuffer.Adopt(buffer, len);
 | |
| 
 | |
|   rv = NS_OK;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult CompareCache::Initialize(Cache* const aCache, const nsAString& aURL) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   MOZ_ASSERT(aCache);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForInitialization);
 | |
| 
 | |
|   // This JSContext will not end up executing JS code because here there are
 | |
|   // no ReadableStreams involved.
 | |
|   AutoJSAPI jsapi;
 | |
|   jsapi.Init();
 | |
| 
 | |
|   RequestOrUSVString request;
 | |
|   request.SetAsUSVString().Rebind(aURL.Data(), aURL.Length());
 | |
|   ErrorResult error;
 | |
|   CacheQueryOptions params;
 | |
|   RefPtr<Promise> promise = aCache->Match(jsapi.cx(), request, params, error);
 | |
|   if (NS_WARN_IF(error.Failed())) {
 | |
|     // No exception here because there are no ReadableStreams involved here.
 | |
|     MOZ_ASSERT(!error.IsJSException());
 | |
|     mState = Finished;
 | |
|     return error.StealNSResult();
 | |
|   }
 | |
| 
 | |
|   // Retrieve the script from aCache.
 | |
|   mState = WaitingForScript;
 | |
|   promise->AppendNativeHandler(this);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void CompareCache::Finish(nsresult aStatus, bool aInCache) {
 | |
|   if (mState != Finished) {
 | |
|     mState = Finished;
 | |
|     mInCache = aInCache;
 | |
|     mCN->CacheFinish(aStatus);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void CompareCache::Abort() {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   if (mState != Finished) {
 | |
|     mState = Finished;
 | |
| 
 | |
|     if (mPump) {
 | |
|       mPump->Cancel(NS_BINDING_ABORTED);
 | |
|       mPump = nullptr;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| CompareCache::OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext,
 | |
|                                nsresult aStatus, uint32_t aLen,
 | |
|                                const uint8_t* aString) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   if (mState == Finished) {
 | |
|     return aStatus;
 | |
|   }
 | |
| 
 | |
|   if (NS_WARN_IF(NS_FAILED(aStatus))) {
 | |
|     Finish(aStatus, false);
 | |
|     return aStatus;
 | |
|   }
 | |
| 
 | |
|   char16_t* buffer = nullptr;
 | |
|   size_t len = 0;
 | |
| 
 | |
|   nsresult rv = ScriptLoader::ConvertToUTF16(
 | |
|       nullptr, aString, aLen, NS_LITERAL_STRING("UTF-8"), nullptr, buffer, len);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     Finish(rv, false);
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   mBuffer.Adopt(buffer, len);
 | |
| 
 | |
|   Finish(NS_OK, true);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void CompareCache::ResolvedCallback(JSContext* aCx,
 | |
|                                     JS::Handle<JS::Value> aValue) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   switch (mState) {
 | |
|     case Finished:
 | |
|       return;
 | |
|     case WaitingForScript:
 | |
|       ManageValueResult(aCx, aValue);
 | |
|       return;
 | |
|     default:
 | |
|       MOZ_CRASH("Unacceptable state.");
 | |
|   }
 | |
| }
 | |
| 
 | |
| void CompareCache::RejectedCallback(JSContext* aCx,
 | |
|                                     JS::Handle<JS::Value> aValue) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   if (mState != Finished) {
 | |
|     Finish(NS_ERROR_FAILURE, false);
 | |
|     return;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void CompareCache::ManageValueResult(JSContext* aCx,
 | |
|                                      JS::Handle<JS::Value> aValue) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   // The cache returns undefined if the object is not stored.
 | |
|   if (aValue.isUndefined()) {
 | |
|     Finish(NS_OK, false);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(aValue.isObject());
 | |
| 
 | |
|   JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
 | |
|   if (NS_WARN_IF(!obj)) {
 | |
|     Finish(NS_ERROR_FAILURE, false);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   Response* response = nullptr;
 | |
|   nsresult rv = UNWRAP_OBJECT(Response, &obj, response);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     Finish(rv, false);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(response->Ok());
 | |
| 
 | |
|   nsCOMPtr<nsIInputStream> inputStream;
 | |
|   response->GetBody(getter_AddRefs(inputStream));
 | |
|   MOZ_ASSERT(inputStream);
 | |
| 
 | |
|   MOZ_ASSERT(!mPump);
 | |
|   rv = NS_NewInputStreamPump(getter_AddRefs(mPump), inputStream.forget(),
 | |
|                              0,     /* default segsize */
 | |
|                              0,     /* default segcount */
 | |
|                              false, /* default closeWhenDone */
 | |
|                              SystemGroup::EventTargetFor(TaskCategory::Other));
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     Finish(rv, false);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIStreamLoader> loader;
 | |
|   rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     Finish(rv, false);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   rv = mPump->AsyncRead(loader, nullptr);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     mPump = nullptr;
 | |
|     Finish(rv, false);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(mPump);
 | |
|   if (rr) {
 | |
|     nsCOMPtr<nsIEventTarget> sts =
 | |
|         do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
 | |
|     rv = rr->RetargetDeliveryTo(sts);
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       mPump = nullptr;
 | |
|       Finish(rv, false);
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsresult CompareManager::Initialize(nsIPrincipal* aPrincipal,
 | |
|                                     const nsAString& aURL,
 | |
|                                     const nsAString& aCacheName,
 | |
|                                     nsILoadGroup* aLoadGroup) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   MOZ_ASSERT(aPrincipal);
 | |
|   MOZ_ASSERT(mPendingCount == 0);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForInitialization);
 | |
| 
 | |
|   // RAII Cleanup when fails.
 | |
|   auto guard = MakeScopeExit([&] { Cleanup(); });
 | |
| 
 | |
|   mURL = aURL;
 | |
|   mPrincipal = aPrincipal;
 | |
|   mLoadGroup = aLoadGroup;
 | |
| 
 | |
|   // Always create a CacheStorage since we want to write the network entry to
 | |
|   // the cache even if there isn't an existing one.
 | |
|   AutoJSAPI jsapi;
 | |
|   jsapi.Init();
 | |
|   ErrorResult result;
 | |
|   mCacheStorage = CreateCacheStorage(jsapi.cx(), aPrincipal, result);
 | |
|   if (NS_WARN_IF(result.Failed())) {
 | |
|     MOZ_ASSERT(!result.IsErrorWithMessage());
 | |
|     return result.StealNSResult();
 | |
|   }
 | |
| 
 | |
|   // If there is no existing cache, proceed to fetch the script directly.
 | |
|   if (aCacheName.IsEmpty()) {
 | |
|     mState = WaitingForScriptOrComparisonResult;
 | |
|     nsresult rv = FetchScript(aURL, true /* aIsMainScript */);
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       return rv;
 | |
|     }
 | |
| 
 | |
|     guard.release();
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // Open the cache saving the old source scripts.
 | |
|   RefPtr<Promise> promise = mCacheStorage->Open(aCacheName, result);
 | |
|   if (NS_WARN_IF(result.Failed())) {
 | |
|     MOZ_ASSERT(!result.IsErrorWithMessage());
 | |
|     return result.StealNSResult();
 | |
|   }
 | |
| 
 | |
|   mState = WaitingForExistingOpen;
 | |
|   promise->AppendNativeHandler(this);
 | |
| 
 | |
|   guard.release();
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| // This class manages 4 promises if needed:
 | |
| // 1. Retrieve the Cache object by a given CacheName of OldCache.
 | |
| // 2. Retrieve the URLs saved in OldCache.
 | |
| // 3. Retrieve the Cache object of the NewCache for the newly created SW.
 | |
| // 4. Put the value in the cache.
 | |
| // For this reason we have mState to know what callback we are handling.
 | |
| void CompareManager::ResolvedCallback(JSContext* aCx,
 | |
|                                       JS::Handle<JS::Value> aValue) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   MOZ_ASSERT(mCallback);
 | |
| 
 | |
|   switch (mState) {
 | |
|     case Finished:
 | |
|       return;
 | |
|     case WaitingForExistingOpen:
 | |
|       ManageOldCache(aCx, aValue);
 | |
|       return;
 | |
|     case WaitingForExistingKeys:
 | |
|       ManageOldKeys(aCx, aValue);
 | |
|       return;
 | |
|     case WaitingForOpen:
 | |
|       ManageNewCache(aCx, aValue);
 | |
|       return;
 | |
|     case WaitingForPut:
 | |
|       MOZ_DIAGNOSTIC_ASSERT(mPendingCount > 0);
 | |
|       if (--mPendingCount == 0) {
 | |
|         mCallback->ComparisonResult(NS_OK, false /* aIsEqual */, mOnFailure,
 | |
|                                     mNewCacheName, mMaxScope, mLoadFlags);
 | |
|         Cleanup();
 | |
|       }
 | |
|       return;
 | |
|     default:
 | |
|       MOZ_DIAGNOSTIC_ASSERT(false);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void CompareManager::RejectedCallback(JSContext* aCx,
 | |
|                                       JS::Handle<JS::Value> aValue) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   switch (mState) {
 | |
|     case Finished:
 | |
|       return;
 | |
|     case WaitingForExistingOpen:
 | |
|       NS_WARNING("Could not open the existing cache.");
 | |
|       break;
 | |
|     case WaitingForExistingKeys:
 | |
|       NS_WARNING("Could not get the existing URLs.");
 | |
|       break;
 | |
|     case WaitingForOpen:
 | |
|       NS_WARNING("Could not open cache.");
 | |
|       break;
 | |
|     case WaitingForPut:
 | |
|       NS_WARNING("Could not write to cache.");
 | |
|       break;
 | |
|     default:
 | |
|       MOZ_DIAGNOSTIC_ASSERT(false);
 | |
|   }
 | |
| 
 | |
|   Fail(NS_ERROR_FAILURE);
 | |
| }
 | |
| 
 | |
| void CompareManager::Fail(nsresult aStatus) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   mCallback->ComparisonResult(aStatus, false /* aIsEqual */, mOnFailure,
 | |
|                               EmptyString(), EmptyCString(), mLoadFlags);
 | |
|   Cleanup();
 | |
| }
 | |
| 
 | |
| void CompareManager::Cleanup() {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   if (mState != Finished) {
 | |
|     mState = Finished;
 | |
| 
 | |
|     MOZ_ASSERT(mCallback);
 | |
|     mCallback = nullptr;
 | |
| 
 | |
|     // Abort and release CompareNetworks.
 | |
|     for (uint32_t i = 0; i < mCNList.Length(); ++i) {
 | |
|       mCNList[i]->Abort();
 | |
|     }
 | |
|     mCNList.Clear();
 | |
|   }
 | |
| }
 | |
| 
 | |
| }  // namespace
 | |
| 
 | |
| nsresult PurgeCache(nsIPrincipal* aPrincipal, const nsAString& aCacheName) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   MOZ_ASSERT(aPrincipal);
 | |
| 
 | |
|   if (aCacheName.IsEmpty()) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   AutoJSAPI jsapi;
 | |
|   jsapi.Init();
 | |
|   ErrorResult rv;
 | |
|   RefPtr<CacheStorage> cacheStorage =
 | |
|       CreateCacheStorage(jsapi.cx(), aPrincipal, rv);
 | |
|   if (NS_WARN_IF(rv.Failed())) {
 | |
|     return rv.StealNSResult();
 | |
|   }
 | |
| 
 | |
|   // We use the ServiceWorker scope as key for the cacheStorage.
 | |
|   RefPtr<Promise> promise = cacheStorage->Delete(aCacheName, rv);
 | |
|   if (NS_WARN_IF(rv.Failed())) {
 | |
|     return rv.StealNSResult();
 | |
|   }
 | |
| 
 | |
|   // We don't actually care about the result of the delete operation.
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult GenerateCacheName(nsAString& aName) {
 | |
|   nsresult rv;
 | |
|   nsCOMPtr<nsIUUIDGenerator> uuidGenerator =
 | |
|       do_GetService("@mozilla.org/uuid-generator;1", &rv);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   nsID id;
 | |
|   rv = uuidGenerator->GenerateUUIDInPlace(&id);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   char chars[NSID_LENGTH];
 | |
|   id.ToProvidedString(chars);
 | |
| 
 | |
|   // NSID_LENGTH counts the null terminator.
 | |
|   aName.AssignASCII(chars, NSID_LENGTH - 1);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult Compare(ServiceWorkerRegistrationInfo* aRegistration,
 | |
|                  nsIPrincipal* aPrincipal, const nsAString& aCacheName,
 | |
|                  const nsAString& aURL, CompareCallback* aCallback,
 | |
|                  nsILoadGroup* aLoadGroup) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   MOZ_ASSERT(aRegistration);
 | |
|   MOZ_ASSERT(aPrincipal);
 | |
|   MOZ_ASSERT(!aURL.IsEmpty());
 | |
|   MOZ_ASSERT(aCallback);
 | |
| 
 | |
|   RefPtr<CompareManager> cm = new CompareManager(aRegistration, aCallback);
 | |
| 
 | |
|   nsresult rv = cm->Initialize(aPrincipal, aURL, aCacheName, aLoadGroup);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| }  // namespace serviceWorkerScriptCache
 | |
| 
 | |
| }  // namespace dom
 | |
| }  // namespace mozilla
 |