Bug 931249 - patch 1 - Scriptloader use cache if needed. r=khuey
authorNikhil Marathe <nsm.nikhil@gmail.com>
Thu, 19 Mar 2015 11:41:42 -0700
changeset 269143 cd1337898e7074d36707f39604b963278725140f
parent 269142 ce5528a5b564e7b9d6c8d6d7bb3067d13a364660
child 269144 b65a9ff678282889cd57fd39831ed9b00f8d6afa
push id863
push userraliiev@mozilla.com
push dateMon, 03 Aug 2015 13:22:43 +0000
treeherdermozilla-release@f6321b14228d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskhuey
bugs931249
milestone40.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 931249 - patch 1 - Scriptloader use cache if needed. r=khuey
dom/fetch/Headers.cpp
dom/fetch/Headers.h
dom/fetch/Request.cpp
dom/fetch/Response.cpp
dom/workers/RuntimeService.h
dom/workers/ScriptLoader.cpp
dom/workers/ServiceWorkerManager.cpp
dom/workers/ServiceWorkerManager.h
dom/workers/WorkerPrivate.cpp
dom/workers/WorkerPrivate.h
dom/workers/Workers.h
--- a/dom/fetch/Headers.cpp
+++ b/dom/fetch/Headers.cpp
@@ -51,18 +51,27 @@ Headers::Constructor(const GlobalObject&
 }
 
 // static
 already_AddRefed<Headers>
 Headers::Constructor(const GlobalObject& aGlobal,
                      const OwningHeadersOrByteStringSequenceSequenceOrByteStringMozMap& aInit,
                      ErrorResult& aRv)
 {
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+  return Create(global, aInit, aRv);
+}
+
+/* static */ already_AddRefed<Headers>
+Headers::Create(nsIGlobalObject* aGlobal,
+                const OwningHeadersOrByteStringSequenceSequenceOrByteStringMozMap& aInit,
+                ErrorResult& aRv)
+{
   nsRefPtr<InternalHeaders> ih = new InternalHeaders();
-  nsRefPtr<Headers> headers = new Headers(aGlobal.GetAsSupports(), ih);
+  nsRefPtr<Headers> headers = new Headers(aGlobal, ih);
 
   if (aInit.IsHeaders()) {
     ih->Fill(*(aInit.GetAsHeaders().get()->mInternalHeaders), aRv);
   } else if (aInit.IsByteStringSequenceSequence()) {
     ih->Fill(aInit.GetAsByteStringSequenceSequence(), aRv);
   } else if (aInit.IsByteStringMozMap()) {
     ih->Fill(aInit.GetAsByteStringMozMap(), aRv);
   }
--- a/dom/fetch/Headers.h
+++ b/dom/fetch/Headers.h
@@ -62,16 +62,21 @@ public:
               const Optional<HeadersOrByteStringSequenceSequenceOrByteStringMozMap>& aInit,
               ErrorResult& aRv);
 
   static already_AddRefed<Headers>
   Constructor(const GlobalObject& aGlobal,
               const OwningHeadersOrByteStringSequenceSequenceOrByteStringMozMap& aInit,
               ErrorResult& aRv);
 
+  static already_AddRefed<Headers>
+  Create(nsIGlobalObject* aGlobalObject,
+         const OwningHeadersOrByteStringSequenceSequenceOrByteStringMozMap& aInit,
+         ErrorResult& aRv);
+
   void Append(const nsACString& aName, const nsACString& aValue,
               ErrorResult& aRv)
   {
     mInternalHeaders->Append(aName, aValue, aRv);
   }
 
   void Delete(const nsACString& aName, ErrorResult& aRv)
   {
--- a/dom/fetch/Request.cpp
+++ b/dom/fetch/Request.cpp
@@ -43,16 +43,92 @@ Request::~Request()
 
 already_AddRefed<InternalRequest>
 Request::GetInternalRequest()
 {
   nsRefPtr<InternalRequest> r = mRequest;
   return r.forget();
 }
 
+namespace {
+void
+GetRequestURLFromWindow(const GlobalObject& aGlobal, nsPIDOMWindow* aWindow,
+                        const nsAString& aInput, nsAString& aRequestURL,
+                        ErrorResult& aRv)
+{
+  MOZ_ASSERT(aWindow);
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsCOMPtr<nsIURI> docURI = aWindow->GetDocumentURI();
+  nsAutoCString spec;
+  aRv = docURI->GetSpec(spec);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
+
+  nsRefPtr<dom::URL> url =
+    dom::URL::Constructor(aGlobal, aInput, NS_ConvertUTF8toUTF16(spec), aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
+
+  url->Stringify(aRequestURL, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
+}
+
+void
+GetRequestURLFromChrome(const nsAString& aInput, nsAString& aRequestURL,
+                        ErrorResult& aRv)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+#ifdef DEBUG
+  nsCOMPtr<nsIURI> uri;
+  aRv = NS_NewURI(getter_AddRefs(uri), aInput, nullptr, nullptr);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
+
+  nsAutoCString spec;
+  aRv = uri->GetSpec(spec);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
+
+  CopyUTF8toUTF16(spec, aRequestURL);
+#else
+  aRequestURL = aInput;
+#endif
+}
+
+void
+GetRequestURLFromWorker(const GlobalObject& aGlobal, const nsAString& aInput,
+                        nsAString& aRequestURL, ErrorResult& aRv)
+{
+  workers::WorkerPrivate* worker = workers::GetCurrentThreadWorkerPrivate();
+  MOZ_ASSERT(worker);
+  worker->AssertIsOnWorkerThread();
+
+  NS_ConvertUTF8toUTF16 baseURL(worker->GetLocationInfo().mHref);
+  nsRefPtr<workers::URL> url =
+    workers::URL::Constructor(aGlobal, aInput, baseURL, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
+
+  url->Stringify(aRequestURL, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
+}
+
+} // anonymous namespace
+
 /*static*/ already_AddRefed<Request>
 Request::Constructor(const GlobalObject& aGlobal,
                      const RequestOrUSVString& aInput,
                      const RequestInit& aInit, ErrorResult& aRv)
 {
   nsRefPtr<InternalRequest> request;
 
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
@@ -79,57 +155,36 @@ Request::Constructor(const GlobalObject&
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   RequestMode fallbackMode = RequestMode::EndGuard_;
   RequestCredentials fallbackCredentials = RequestCredentials::EndGuard_;
   RequestCache fallbackCache = RequestCache::EndGuard_;
   if (aInput.IsUSVString()) {
-    nsString input;
+    nsAutoString input;
     input.Assign(aInput.GetAsUSVString());
 
-    nsString requestURL;
+    nsAutoString requestURL;
     if (NS_IsMainThread()) {
       nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(global);
-      MOZ_ASSERT(window);
-      nsCOMPtr<nsIURI> docURI = window->GetDocumentURI();
-      nsCString spec;
-      aRv = docURI->GetSpec(spec);
-      if (NS_WARN_IF(aRv.Failed())) {
-        return nullptr;
-      }
-
-      nsRefPtr<mozilla::dom::URL> url =
-        dom::URL::Constructor(aGlobal, input, NS_ConvertUTF8toUTF16(spec), aRv);
-      if (aRv.Failed()) {
-        return nullptr;
-      }
-
-      url->Stringify(requestURL, aRv);
-      if (aRv.Failed()) {
-        return nullptr;
+      if (window) {
+        GetRequestURLFromWindow(aGlobal, window, input, requestURL, aRv);
+      } else {
+        // If we don't have a window, we must assume that this is a full URL.
+        GetRequestURLFromChrome(input, requestURL, aRv);
       }
     } else {
-      workers::WorkerPrivate* worker = workers::GetCurrentThreadWorkerPrivate();
-      MOZ_ASSERT(worker);
-      worker->AssertIsOnWorkerThread();
+      GetRequestURLFromWorker(aGlobal, input, requestURL, aRv);
+    }
 
-      nsString baseURL = NS_ConvertUTF8toUTF16(worker->GetLocationInfo().mHref);
-      nsRefPtr<workers::URL> url =
-        workers::URL::Constructor(aGlobal, input, baseURL, aRv);
-      if (aRv.Failed()) {
-        return nullptr;
-      }
+    if (NS_WARN_IF(aRv.Failed())) {
+      return nullptr;
+    }
 
-      url->Stringify(requestURL, aRv);
-      if (aRv.Failed()) {
-        return nullptr;
-      }
-    }
     request->SetURL(NS_ConvertUTF16toUTF8(requestURL));
     fallbackMode = RequestMode::Cors;
     fallbackCredentials = RequestCredentials::Omit;
     fallbackCache = RequestCache::Default;
   }
 
   // CORS-with-forced-preflight is not publicly exposed and should not be
   // considered a valid value.
@@ -239,17 +294,17 @@ Request::Constructor(const GlobalObject&
     // method is guaranteed to be uppercase due to step 14.2 above.
     if (method.EqualsLiteral("HEAD") || method.EqualsLiteral("GET")) {
       aRv.ThrowTypeError(MSG_NO_BODY_ALLOWED_FOR_GET_AND_HEAD);
       return nullptr;
     }
 
     const OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& bodyInit = aInit.mBody.Value();
     nsCOMPtr<nsIInputStream> stream;
-    nsCString contentType;
+    nsAutoCString contentType;
     aRv = ExtractByteStreamFromBody(bodyInit,
                                     getter_AddRefs(stream), contentType);
     if (NS_WARN_IF(aRv.Failed())) {
       return nullptr;
     }
     request->ClearCreatedByFetchEvent();
     request->SetBody(stream);
 
--- a/dom/fetch/Response.cpp
+++ b/dom/fetch/Response.cpp
@@ -119,16 +119,18 @@ Response::Redirect(const GlobalObject& a
   return r.forget();
 }
 
 /*static*/ already_AddRefed<Response>
 Response::Constructor(const GlobalObject& aGlobal,
                       const Optional<ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams>& aBody,
                       const ResponseInit& aInit, ErrorResult& aRv)
 {
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+
   if (aInit.mStatus < 200 || aInit.mStatus > 599) {
     aRv.ThrowRangeError(MSG_INVALID_RESPONSE_STATUSCODE_ERROR);
     return nullptr;
   }
 
   nsCString statusText;
   if (aInit.mStatusText.WasPassed()) {
     statusText = aInit.mStatusText.Value();
@@ -148,26 +150,25 @@ Response::Constructor(const GlobalObject
   } else {
     // Since we don't support default values for ByteString.
     statusText = NS_LITERAL_CSTRING("OK");
   }
 
   nsRefPtr<InternalResponse> internalResponse =
     new InternalResponse(aInit.mStatus, statusText);
 
-  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
   nsRefPtr<Response> r = new Response(global, internalResponse);
 
   if (aInit.mHeaders.WasPassed()) {
     internalResponse->Headers()->Clear();
 
     // Instead of using Fill, create an object to allow the constructor to
     // unwrap the HeadersInit.
     nsRefPtr<Headers> headers =
-      Headers::Constructor(aGlobal, aInit.mHeaders.Value(), aRv);
+      Headers::Create(global, aInit.mHeaders.Value(), aRv);
     if (aRv.Failed()) {
       return nullptr;
     }
 
     internalResponse->Headers()->Fill(*headers->GetInternalHeaders(), aRv);
     if (NS_WARN_IF(aRv.Failed())) {
       return nullptr;
     }
--- a/dom/workers/RuntimeService.h
+++ b/dom/workers/RuntimeService.h
@@ -140,26 +140,16 @@ public:
                      const nsACString& aName,
                      SharedWorker** aSharedWorker)
   {
     return CreateSharedWorkerInternal(aGlobal, aScriptURL, aName,
                                       WorkerTypeShared, aSharedWorker);
   }
 
   nsresult
-  CreateSharedWorkerForServiceWorker(const GlobalObject& aGlobal,
-                                     const nsAString& aScriptURL,
-                                     const nsACString& aScope,
-                                     SharedWorker** aSharedWorker)
-  {
-    return CreateSharedWorkerInternal(aGlobal, aScriptURL, aScope,
-                                      WorkerTypeService, aSharedWorker);
-  }
-
-  nsresult
   CreateSharedWorkerForServiceWorkerFromLoadInfo(JSContext* aCx,
                                                  WorkerLoadInfo* aLoadInfo,
                                                  const nsAString& aScriptURL,
                                                  const nsACString& aScope,
                                                  SharedWorker** aSharedWorker)
   {
     return CreateSharedWorkerFromLoadInfo(aCx, aLoadInfo, aScriptURL, aScope,
                                           WorkerTypeService, aSharedWorker);
--- a/dom/workers/ScriptLoader.cpp
+++ b/dom/workers/ScriptLoader.cpp
@@ -4,52 +4,91 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ScriptLoader.h"
 
 #include "nsIChannel.h"
 #include "nsIContentPolicy.h"
 #include "nsIContentSecurityPolicy.h"
 #include "nsIHttpChannel.h"
+#include "nsIInputStreamPump.h"
 #include "nsIIOService.h"
 #include "nsIProtocolHandler.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIStreamLoader.h"
+#include "nsIStreamListenerTee.h"
+#include "nsIThreadRetargetableRequest.h"
 #include "nsIURI.h"
 
 #include "jsapi.h"
 #include "nsError.h"
 #include "nsContentPolicyUtils.h"
 #include "nsContentUtils.h"
 #include "nsDocShellCID.h"
 #include "nsISupportsPrimitives.h"
 #include "nsNetUtil.h"
 #include "nsScriptLoader.h"
 #include "nsString.h"
+#include "nsStreamUtils.h"
 #include "nsTArray.h"
 #include "nsThreadUtils.h"
 #include "nsXPCOM.h"
 #include "xpcpublic.h"
 
 #include "mozilla/Assertions.h"
 #include "mozilla/LoadContext.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/dom/CacheBinding.h"
+#include "mozilla/dom/cache/Cache.h"
+#include "mozilla/dom/cache/CacheStorage.h"
 #include "mozilla/dom/Exceptions.h"
+#include "mozilla/dom/InternalResponse.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/Response.h"
 #include "Principal.h"
 #include "WorkerFeature.h"
 #include "WorkerPrivate.h"
 #include "WorkerRunnable.h"
 
 #define MAX_CONCURRENT_SCRIPTS 1000
 
 USING_WORKERS_NAMESPACE
 
+using mozilla::dom::cache::Cache;
+using mozilla::dom::cache::CacheStorage;
+using mozilla::dom::Promise;
+using mozilla::dom::PromiseNativeHandler;
 using mozilla::dom::workers::exceptions::ThrowDOMExceptionForNSResult;
 
 namespace {
 
+nsIURI*
+GetBaseURI(bool aIsMainScript, WorkerPrivate* aWorkerPrivate)
+{
+  MOZ_ASSERT(aWorkerPrivate);
+  nsIURI* baseURI;
+  WorkerPrivate* parentWorker = aWorkerPrivate->GetParent();
+  if (aIsMainScript) {
+    if (parentWorker) {
+      baseURI = parentWorker->GetBaseURI();
+      NS_ASSERTION(baseURI, "Should have been set already!");
+    }
+    else {
+      // May be null.
+      baseURI = aWorkerPrivate->GetBaseURI();
+    }
+  }
+  else {
+    baseURI = aWorkerPrivate->GetBaseURI();
+    NS_ASSERTION(baseURI, "Should have been set already!");
+  }
+
+  return baseURI;
+}
+
 nsresult
 ChannelFromScriptURL(nsIPrincipal* principal,
                      nsIURI* baseURI,
                      nsIDocument* parentDoc,
                      nsILoadGroup* loadGroup,
                      nsIIOService* ios,
                      nsIScriptSecurityManager* secMan,
                      const nsAString& aScriptURL,
@@ -154,54 +193,99 @@ ChannelFromScriptURL(nsIPrincipal* princ
   return rv;
 }
 
 struct ScriptLoadInfo
 {
   ScriptLoadInfo()
   : mScriptTextBuf(nullptr)
   , mScriptTextLength(0)
-  , mLoadResult(NS_ERROR_NOT_INITIALIZED), mExecutionScheduled(false)
+  , mLoadResult(NS_ERROR_NOT_INITIALIZED)
+  , mLoadingFinished(false)
+  , mExecutionScheduled(false)
   , mExecutionResult(false)
+  , mCacheStatus(Uncached)
   { }
 
   ~ScriptLoadInfo()
   {
     if (mScriptTextBuf) {
       js_free(mScriptTextBuf);
     }
   }
 
   bool
   ReadyToExecute()
   {
     return !mChannel && NS_SUCCEEDED(mLoadResult) && !mExecutionScheduled;
   }
 
   nsString mURL;
+
+  // This full URL string is populated only if this object is used in a
+  // ServiceWorker.
+  nsString mFullURL;
+
+  // This promise is set only when the script is for a ServiceWorker but
+  // it's not in the cache yet. The promise is resolved when the full body is
+  // stored into the cache.  mCachePromise will be set to nullptr after
+  // resolution.
+  nsRefPtr<Promise> mCachePromise;
+
   nsCOMPtr<nsIChannel> mChannel;
   char16_t* mScriptTextBuf;
   size_t mScriptTextLength;
 
   nsresult mLoadResult;
+  bool mLoadingFinished;
   bool mExecutionScheduled;
   bool mExecutionResult;
+
+  enum CacheStatus {
+    // By default a normal script is just loaded from the network. But for
+    // ServiceWorkers, we have to check if the cache contains the script and
+    // load it from the cache.
+    Uncached,
+
+    WritingToCache,
+
+    ReadingFromCache,
+
+    // This script has been loaded from the ServiceWorker cache.
+    Cached,
+
+    // This script must be stored in the ServiceWorker cache.
+    ToBeCached,
+
+    // Something went wrong or the worker went away.
+    Cancel
+  };
+
+  CacheStatus mCacheStatus;
+
+  bool Finished() const
+  {
+    return mLoadingFinished && !mCachePromise && !mChannel;
+  }
 };
 
 class ScriptLoaderRunnable;
 
 class ScriptExecutorRunnable final : public MainThreadWorkerSyncRunnable
 {
   ScriptLoaderRunnable& mScriptLoader;
+  bool mIsWorkerScript;
   uint32_t mFirstIndex;
   uint32_t mLastIndex;
 
 public:
   ScriptExecutorRunnable(ScriptLoaderRunnable& aScriptLoader,
-                         nsIEventTarget* aSyncLoopTarget, uint32_t aFirstIndex,
+                         nsIEventTarget* aSyncLoopTarget,
+                         bool aIsWorkerScript,
+                         uint32_t aFirstIndex,
                          uint32_t aLastIndex);
 
 private:
   ~ScriptExecutorRunnable()
   { }
 
   virtual bool
   IsDebuggerRunnable() const override;
@@ -216,25 +300,177 @@ private:
   NS_DECL_NSICANCELABLERUNNABLE
 
   void
   ShutdownScriptLoader(JSContext* aCx,
                        WorkerPrivate* aWorkerPrivate,
                        bool aResult);
 };
 
+class CacheScriptLoader;
+
+class CacheCreator final : public PromiseNativeHandler
+{
+public:
+  explicit CacheCreator(WorkerPrivate* aWorkerPrivate)
+    : mCacheName(aWorkerPrivate->ServiceWorkerCacheName())
+  {
+    MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
+    AssertIsOnMainThread();
+  }
+
+  void
+  AddLoader(CacheScriptLoader* aLoader)
+  {
+    AssertIsOnMainThread();
+    MOZ_ASSERT(!mCacheStorage);
+    mLoaders.AppendElement(aLoader);
+  }
+
+  virtual void
+  ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+  virtual void
+  RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+  // Try to load from cache with aPrincipal used for cache access.
+  nsresult
+  Load(nsIPrincipal* aPrincipal);
+
+  Cache*
+  Cache_() const
+  {
+    AssertIsOnMainThread();
+    MOZ_ASSERT(mCache);
+    return mCache;
+  }
+
+  nsIGlobalObject*
+  Global() const
+  {
+    AssertIsOnMainThread();
+    MOZ_ASSERT(mSandboxGlobalObject);
+    return mSandboxGlobalObject;
+  }
+
+  void
+  DeleteCache();
+
+private:
+  ~CacheCreator()
+  {
+  }
+
+  nsresult
+  CreateCacheStorage(nsIPrincipal* aPrincipal);
+
+  void
+  FailLoaders(nsresult aRv);
+
+  nsRefPtr<Cache> mCache;
+  nsRefPtr<CacheStorage> mCacheStorage;
+  nsCOMPtr<nsIGlobalObject> mSandboxGlobalObject;
+  nsTArray<nsRefPtr<CacheScriptLoader>> mLoaders;
+
+  nsString mCacheName;
+};
+
+class CacheScriptLoader final : public PromiseNativeHandler
+                                  , public nsIStreamLoaderObserver
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_NSISTREAMLOADEROBSERVER
+
+  CacheScriptLoader(WorkerPrivate* aWorkerPrivate, ScriptLoadInfo& aLoadInfo,
+                    uint32_t aIndex, bool aIsWorkerScript,
+                    ScriptLoaderRunnable* aRunnable)
+    : mLoadInfo(aLoadInfo)
+    , mIndex(aIndex)
+    , mRunnable(aRunnable)
+    , mIsWorkerScript(aIsWorkerScript)
+    , mFailed(false)
+  {
+    MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
+    mBaseURI = GetBaseURI(mIsWorkerScript, aWorkerPrivate);
+    AssertIsOnMainThread();
+  }
+
+  void
+  Fail(nsresult aRv);
+
+  void
+  Load(Cache* aCache);
+
+  virtual void
+  ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+  virtual void
+  RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+private:
+  ~CacheScriptLoader()
+  {
+    AssertIsOnMainThread();
+  }
+
+  ScriptLoadInfo& mLoadInfo;
+  uint32_t mIndex;
+  nsRefPtr<ScriptLoaderRunnable> mRunnable;
+  bool mIsWorkerScript;
+  bool mFailed;
+  nsCOMPtr<nsIInputStreamPump> mPump;
+  nsCOMPtr<nsIURI> mBaseURI;
+};
+
+NS_IMPL_ISUPPORTS(CacheScriptLoader, nsIStreamLoaderObserver)
+
+class CachePromiseHandler final : public PromiseNativeHandler
+{
+public:
+  CachePromiseHandler(ScriptLoaderRunnable* aRunnable,
+                      ScriptLoadInfo& aLoadInfo,
+                      uint32_t aIndex)
+    : mRunnable(aRunnable)
+    , mLoadInfo(aLoadInfo)
+    , mIndex(aIndex)
+  {
+    AssertIsOnMainThread();
+    MOZ_ASSERT(mRunnable);
+  }
+
+  virtual void
+  ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+  virtual void
+  RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+private:
+  ~CachePromiseHandler()
+  {
+    AssertIsOnMainThread();
+  }
+
+  nsRefPtr<ScriptLoaderRunnable> mRunnable;
+  ScriptLoadInfo& mLoadInfo;
+  uint32_t mIndex;
+};
+
 class ScriptLoaderRunnable final : public WorkerFeature,
                                    public nsIRunnable,
                                    public nsIStreamLoaderObserver
 {
   friend class ScriptExecutorRunnable;
+  friend class CachePromiseHandler;
+  friend class CacheScriptLoader;
 
   WorkerPrivate* mWorkerPrivate;
   nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
   nsTArray<ScriptLoadInfo> mLoadInfos;
+  nsRefPtr<CacheCreator> mCacheCreator;
   bool mIsMainScript;
   WorkerScriptType mWorkerScriptType;
   bool mCanceled;
   bool mCanceledMainThread;
 
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
 
@@ -265,16 +501,45 @@ private:
 
     if (NS_FAILED(RunInternal())) {
       CancelMainThread();
     }
 
     return NS_OK;
   }
 
+  void
+  LoadingFinished(uint32_t aIndex, nsresult aRv)
+  {
+    AssertIsOnMainThread();
+    MOZ_ASSERT(aIndex < mLoadInfos.Length());
+    ScriptLoadInfo& loadInfo = mLoadInfos[aIndex];
+
+    loadInfo.mLoadResult = aRv;
+
+    MOZ_ASSERT(!loadInfo.mLoadingFinished);
+    loadInfo.mLoadingFinished = true;
+
+    MaybeExecuteFinishedScripts(aIndex);
+  }
+
+  void
+  MaybeExecuteFinishedScripts(uint32_t aIndex)
+  {
+    AssertIsOnMainThread();
+    MOZ_ASSERT(aIndex < mLoadInfos.Length());
+    ScriptLoadInfo& loadInfo = mLoadInfos[aIndex];
+
+    // We execute the last step if we don't have a pending operation with the
+    // cache and the loading is completed.
+    if (!mCanceledMainThread && loadInfo.Finished()) {
+      ExecuteFinishedScripts();
+    }
+  }
+
   NS_IMETHOD
   OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext,
                    nsresult aStatus, uint32_t aStringLen,
                    const uint8_t* aString) override
   {
     AssertIsOnMainThread();
 
     nsCOMPtr<nsISupportsPRUint32> indexSupports(do_QueryInterface(aContext));
@@ -283,22 +548,19 @@ private:
     uint32_t index = UINT32_MAX;
     if (NS_FAILED(indexSupports->GetData(&index)) ||
         index >= mLoadInfos.Length()) {
       NS_ERROR("Bad index!");
     }
 
     ScriptLoadInfo& loadInfo = mLoadInfos[index];
 
-    loadInfo.mLoadResult = OnStreamCompleteInternal(aLoader, aContext, aStatus,
-                                                    aStringLen, aString,
-                                                    loadInfo);
-
-    ExecuteFinishedScripts();
-
+    nsresult rv = OnStreamCompleteInternal(aLoader, aContext, aStatus,
+                                           aStringLen, aString, loadInfo);
+    LoadingFinished(index, rv);
     return NS_OK;
   }
 
   virtual bool
   Notify(JSContext* aCx, Status aStatus) override
   {
     mWorkerPrivate->AssertIsOnWorkerThread();
 
@@ -330,116 +592,235 @@ private:
     AssertIsOnMainThread();
 
     if (mCanceledMainThread) {
       return;
     }
 
     mCanceledMainThread = true;
 
+    if (mCacheCreator) {
+      MOZ_ASSERT(mWorkerPrivate->IsServiceWorker());
+      DeleteCache();
+    }
+
     // Cancel all the channels that were already opened.
     for (uint32_t index = 0; index < mLoadInfos.Length(); index++) {
       ScriptLoadInfo& loadInfo = mLoadInfos[index];
 
+      if (loadInfo.mCachePromise) {
+        MOZ_ASSERT(mWorkerPrivate->IsServiceWorker());
+        loadInfo.mCachePromise->MaybeReject(NS_BINDING_ABORTED);
+        loadInfo.mCachePromise = nullptr;
+      }
+
       if (loadInfo.mChannel &&
           NS_FAILED(loadInfo.mChannel->Cancel(NS_BINDING_ABORTED))) {
         NS_WARNING("Failed to cancel channel!");
-        loadInfo.mChannel = nullptr;
-        loadInfo.mLoadResult = NS_BINDING_ABORTED;
+        LoadingFinished(index, NS_BINDING_ABORTED);
       }
     }
 
     ExecuteFinishedScripts();
   }
 
+  void
+  DeleteCache()
+  {
+    AssertIsOnMainThread();
+
+    if (!mCacheCreator) {
+      return;
+    }
+
+    mCacheCreator->DeleteCache();
+    mCacheCreator = nullptr;
+  }
+
   nsresult
   RunInternal()
   {
     AssertIsOnMainThread();
 
+    if (IsMainWorkerScript() && mWorkerPrivate->IsServiceWorker()) {
+      mWorkerPrivate->SetLoadingWorkerScript(true);
+    }
+
+    if (!mWorkerPrivate->IsServiceWorker() ||
+        mWorkerPrivate->ServiceWorkerCacheName().IsEmpty()) {
+      for (uint32_t index = 0, len = mLoadInfos.Length(); index < len;
+           ++index) {
+        nsresult rv = LoadScript(index);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return rv;
+        }
+      }
+
+      return NS_OK;
+    }
+
+    MOZ_ASSERT(!mCacheCreator);
+    mCacheCreator = new CacheCreator(mWorkerPrivate);
+
+    for (uint32_t index = 0, len = mLoadInfos.Length(); index < len; ++index) {
+      nsRefPtr<CacheScriptLoader> loader =
+        new CacheScriptLoader(mWorkerPrivate, mLoadInfos[index], index,
+                              IsMainWorkerScript(), this);
+      mCacheCreator->AddLoader(loader);
+    }
+
+    // The worker may have a null principal on first load, but in that case its
+    // parent definitely will have one.
+    nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
+    if (!principal) {
+      WorkerPrivate* parentWorker = mWorkerPrivate->GetParent();
+      MOZ_ASSERT(parentWorker, "Must have a parent!");
+      principal = parentWorker->GetPrincipal();
+    }
+
+    nsresult rv = mCacheCreator->Load(principal);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    return NS_OK;
+  }
+
+  nsresult
+  LoadScript(uint32_t aIndex)
+  {
+    AssertIsOnMainThread();
+    MOZ_ASSERT(aIndex < mLoadInfos.Length());
+
     WorkerPrivate* parentWorker = mWorkerPrivate->GetParent();
 
     // Figure out which principal to use.
     nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
     nsCOMPtr<nsILoadGroup> loadGroup = mWorkerPrivate->GetLoadGroup();
     if (!principal) {
       NS_ASSERTION(parentWorker, "Must have a principal!");
       NS_ASSERTION(mIsMainScript, "Must have a principal for importScripts!");
 
       principal = parentWorker->GetPrincipal();
       loadGroup = parentWorker->GetLoadGroup();
     }
     NS_ASSERTION(principal, "This should never be null here!");
     MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(loadGroup, principal));
 
     // Figure out our base URI.
-    nsCOMPtr<nsIURI> baseURI;
-    if (mIsMainScript) {
-      if (parentWorker) {
-        baseURI = parentWorker->GetBaseURI();
-        NS_ASSERTION(baseURI, "Should have been set already!");
-      }
-      else {
-        // May be null.
-        baseURI = mWorkerPrivate->GetBaseURI();
-      }
-    }
-    else {
-      baseURI = mWorkerPrivate->GetBaseURI();
-      NS_ASSERTION(baseURI, "Should have been set already!");
-    }
+    nsCOMPtr<nsIURI> baseURI = GetBaseURI(mIsMainScript, mWorkerPrivate);
 
     // May be null.
     nsCOMPtr<nsIDocument> parentDoc = mWorkerPrivate->GetDocument();
 
     nsCOMPtr<nsIChannel> channel;
     if (IsMainWorkerScript()) {
       // May be null.
       channel = mWorkerPrivate->ForgetWorkerChannel();
     }
 
     nsCOMPtr<nsIIOService> ios(do_GetIOService());
 
     nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
     NS_ASSERTION(secMan, "This should never be null!");
 
-    for (uint32_t index = 0; index < mLoadInfos.Length(); index++) {
-      ScriptLoadInfo& loadInfo = mLoadInfos[index];
-      nsresult& rv = loadInfo.mLoadResult;
+    ScriptLoadInfo& loadInfo = mLoadInfos[aIndex];
+    nsresult& rv = loadInfo.mLoadResult;
+
+    if (!channel) {
+      rv = ChannelFromScriptURL(principal, baseURI, parentDoc, loadGroup, ios,
+                                secMan, loadInfo.mURL, IsMainWorkerScript(),
+                                mWorkerScriptType, getter_AddRefs(channel));
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+    }
+
+    // We need to know which index we're on in OnStreamComplete so we know
+    // where to put the result.
+    nsCOMPtr<nsISupportsPRUint32> indexSupports =
+      do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID, &rv);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    rv = indexSupports->SetData(aIndex);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
 
-      if (!channel) {
-        rv = ChannelFromScriptURL(principal, baseURI, parentDoc, loadGroup, ios,
-                                  secMan, loadInfo.mURL, mIsMainScript,
-                                  mWorkerScriptType, getter_AddRefs(channel));
-        if (NS_FAILED(rv)) {
-          return rv;
-        }
+    // We don't care about progress so just use the simple stream loader for
+    // OnStreamComplete notification only.
+    nsCOMPtr<nsIStreamLoader> loader;
+    rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    if (loadInfo.mCacheStatus != ScriptLoadInfo::ToBeCached) {
+      rv = channel->AsyncOpen(loader, indexSupports);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+    } else {
+      nsCOMPtr<nsIInputStream> reader;
+      nsCOMPtr<nsIOutputStream> writer;
+
+      // In case we return early.
+      loadInfo.mCacheStatus = ScriptLoadInfo::Cancel;
+
+      rv = NS_NewPipe(getter_AddRefs(reader), getter_AddRefs(writer), 0,
+                      UINT32_MAX, // unlimited size to avoid writer WOULD_BLOCK case
+                      true, false); // non-blocking reader, blocking writer
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
       }
 
-      // We need to know which index we're on in OnStreamComplete so we know
-      // where to put the result.
-      nsCOMPtr<nsISupportsPRUint32> indexSupports =
-        do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID, &rv);
-      NS_ENSURE_SUCCESS(rv, rv);
+      // We synthesize the result code, but its never exposed to content.
+      nsRefPtr<InternalResponse> ir =
+        new InternalResponse(200, NS_LITERAL_CSTRING("OK"));
+      ir->SetBody(reader);
 
-      rv = indexSupports->SetData(index);
-      NS_ENSURE_SUCCESS(rv, rv);
+      nsCOMPtr<nsIStreamListenerTee> tee =
+        do_CreateInstance(NS_STREAMLISTENERTEE_CONTRACTID);
+      rv = tee->Init(loader, writer, nullptr);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      rv = channel->AsyncOpen(tee, indexSupports);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      nsRefPtr<Response> response = new Response(mCacheCreator->Global(), ir);
+
+      RequestOrUSVString request;
 
-      // We don't care about progress so just use the simple stream loader for
-      // OnStreamComplete notification only.
-      nsCOMPtr<nsIStreamLoader> loader;
-      rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
-      NS_ENSURE_SUCCESS(rv, rv);
+      MOZ_ASSERT(!loadInfo.mFullURL.IsEmpty());
+      request.SetAsUSVString().Rebind(loadInfo.mFullURL.Data(),
+                                      loadInfo.mFullURL.Length());
 
-      rv = channel->AsyncOpen(loader, indexSupports);
-      NS_ENSURE_SUCCESS(rv, rv);
+      ErrorResult error;
+      loadInfo.mCachePromise =
+        mCacheCreator->Cache_()->Put(request, *response, error);
+      if (NS_WARN_IF(error.Failed())) {
+        channel->Cancel(error.ErrorCode());
+        return error.ErrorCode();
+      }
 
-      loadInfo.mChannel.swap(channel);
+      nsRefPtr<CachePromiseHandler> promiseHandler =
+        new CachePromiseHandler(this, loadInfo, aIndex);
+      loadInfo.mCachePromise->AppendNativeHandler(promiseHandler);
+
+      loadInfo.mCacheStatus = ScriptLoadInfo::WritingToCache;
     }
 
+    loadInfo.mChannel.swap(channel);
+
     return NS_OK;
   }
 
   nsresult
   OnStreamCompleteInternal(nsIStreamLoader* aLoader, nsISupports* aContext,
                            nsresult aStatus, uint32_t aStringLen,
                            const uint8_t* aString, ScriptLoadInfo& aLoadInfo)
   {
@@ -577,28 +958,63 @@ private:
         }
       }
 
       // The principal can change, but it should still match the original
       // load group's appId and browser element flag.
       MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(channelLoadGroup, channelPrincipal));
 
       mWorkerPrivate->SetPrincipal(channelPrincipal, channelLoadGroup);
+    }
+
+    DataReceived();
+    return NS_OK;
+  }
+
+  void
+  DataReceivedFromCache(uint32_t aIndex, const uint8_t* aString,
+                        uint32_t aStringLen)
+  {
+    MOZ_ASSERT(aIndex < mLoadInfos.Length());
+    ScriptLoadInfo& loadInfo = mLoadInfos[aIndex];
+    MOZ_ASSERT(loadInfo.mCacheStatus == ScriptLoadInfo::Cached);
+
+    // May be null.
+    nsIDocument* parentDoc = mWorkerPrivate->GetDocument();
+
+    MOZ_ASSERT(!loadInfo.mScriptTextBuf);
+
+    DebugOnly<nsresult> rv =
+      nsScriptLoader::ConvertToUTF16(nullptr, aString, aStringLen,
+                                     NS_LITERAL_STRING("UTF-8"), parentDoc,
+                                     loadInfo.mScriptTextBuf,
+                                     loadInfo.mScriptTextLength);
+
+    if (NS_SUCCEEDED(rv)) {
+      DataReceived();
+    }
+
+    LoadingFinished(aIndex, rv);
+  }
+
+  void
+  DataReceived()
+  {
+    if (IsMainWorkerScript()) {
+      WorkerPrivate* parent = mWorkerPrivate->GetParent();
 
       if (parent) {
         // XHR Params Allowed
         mWorkerPrivate->SetXHRParamsAllowed(parent->XHRParamsAllowed());
 
         // Set Eval and ContentSecurityPolicy
         mWorkerPrivate->SetCSP(parent->GetCSP());
         mWorkerPrivate->SetEvalAllowed(parent->IsEvalAllowed());
       }
     }
-
-    return NS_OK;
   }
 
   void
   ExecuteFinishedScripts()
   {
     AssertIsOnMainThread();
 
     if (IsMainWorkerScript()) {
@@ -617,42 +1033,353 @@ private:
     }
 
     // Find lastIndex based on whether mChannel is set, and update
     // mExecutionScheduled on the ones we're about to schedule.
     if (firstIndex != UINT32_MAX) {
       for (uint32_t index = firstIndex; index < mLoadInfos.Length(); index++) {
         ScriptLoadInfo& loadInfo = mLoadInfos[index];
 
-        // If we still have a channel then the load is not complete.
-        if (loadInfo.mChannel) {
+        if (!loadInfo.Finished()) {
           break;
         }
 
         // We can execute this one.
         loadInfo.mExecutionScheduled = true;
 
         lastIndex = index;
       }
     }
 
+    // This is the last index, we can unused things before the exection of the
+    // script and the stopping of the sync loop.
+    if (lastIndex == mLoadInfos.Length() - 1) {
+      mCacheCreator = nullptr;
+    }
+
     if (firstIndex != UINT32_MAX && lastIndex != UINT32_MAX) {
       nsRefPtr<ScriptExecutorRunnable> runnable =
-        new ScriptExecutorRunnable(*this, mSyncLoopTarget, firstIndex,
-                                   lastIndex);
+        new ScriptExecutorRunnable(*this, mSyncLoopTarget, IsMainWorkerScript(),
+                                   firstIndex, lastIndex);
       if (!runnable->Dispatch(nullptr)) {
         MOZ_ASSERT(false, "This should never fail!");
       }
     }
   }
 };
 
 NS_IMPL_ISUPPORTS(ScriptLoaderRunnable, nsIRunnable, nsIStreamLoaderObserver)
 
-class ChannelGetterRunnable final : public nsRunnable
+void
+CachePromiseHandler::ResolvedCallback(JSContext* aCx,
+                                      JS::Handle<JS::Value> aValue)
+{
+  AssertIsOnMainThread();
+  MOZ_ASSERT(mLoadInfo.mCacheStatus == ScriptLoadInfo::WritingToCache);
+  mLoadInfo.mCacheStatus = ScriptLoadInfo::Cached;
+
+  mLoadInfo.mCachePromise = nullptr;
+  mRunnable->MaybeExecuteFinishedScripts(mIndex);
+}
+
+void
+CachePromiseHandler::RejectedCallback(JSContext* aCx,
+                                      JS::Handle<JS::Value> aValue)
+{
+  AssertIsOnMainThread();
+  MOZ_ASSERT(mLoadInfo.mCacheStatus == ScriptLoadInfo::WritingToCache);
+  mLoadInfo.mCacheStatus = ScriptLoadInfo::Cancel;
+
+  mLoadInfo.mCachePromise = nullptr;
+
+  // This will delete the cache object and will call LoadingFinished() with an
+  // error for each ongoing operation.
+  mRunnable->DeleteCache();
+}
+
+nsresult
+CacheCreator::CreateCacheStorage(nsIPrincipal* aPrincipal)
+{
+  AssertIsOnMainThread();
+  MOZ_ASSERT(!mCacheStorage);
+  MOZ_ASSERT(aPrincipal);
+
+  nsIXPConnect* xpc = nsContentUtils::XPConnect();
+  MOZ_ASSERT(xpc, "This should never be null!");
+
+  AutoSafeJSContext cx;
+  nsCOMPtr<nsIXPConnectJSObjectHolder> sandbox;
+  nsresult rv = xpc->CreateSandbox(cx, aPrincipal, getter_AddRefs(sandbox));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  mSandboxGlobalObject = xpc::NativeGlobal(sandbox->GetJSObject());
+  if (NS_WARN_IF(!mSandboxGlobalObject)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  ErrorResult error;
+  mCacheStorage =
+    CacheStorage::CreateOnMainThread(cache::CHROME_ONLY_NAMESPACE,
+                                     mSandboxGlobalObject,
+                                     aPrincipal, error);
+  if (NS_WARN_IF(error.Failed())) {
+    return error.ErrorCode();
+  }
+
+  return NS_OK;
+}
+
+nsresult
+CacheCreator::Load(nsIPrincipal* aPrincipal)
+{
+  AssertIsOnMainThread();
+  MOZ_ASSERT(!mLoaders.IsEmpty());
+
+  nsresult rv = CreateCacheStorage(aPrincipal);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // We use the ServiceWorker scope as key for the cacheStorage.
+  ErrorResult error;
+  MOZ_ASSERT(!mCacheName.IsEmpty());
+  nsRefPtr<Promise> promise = mCacheStorage->Open(mCacheName, error);
+  if (NS_WARN_IF(error.Failed())) {
+    return error.ErrorCode();
+  }
+
+  promise->AppendNativeHandler(this);
+  return NS_OK;
+}
+
+void
+CacheCreator::FailLoaders(nsresult aRv)
+{
+  AssertIsOnMainThread();
+
+  for (uint32_t i = 0, len = mLoaders.Length(); i < len; ++i) {
+    mLoaders[i]->Fail(aRv);
+  }
+
+  mLoaders.Clear();
+}
+
+void
+CacheCreator::RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
+{
+  AssertIsOnMainThread();
+  FailLoaders(NS_ERROR_FAILURE);
+}
+
+void
+CacheCreator::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
+{
+  AssertIsOnMainThread();
+  MOZ_ASSERT(aValue.isObject());
+
+  JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
+  Cache* cache = nullptr;
+  nsresult rv = UNWRAP_OBJECT(Cache, obj, cache);
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(rv));
+
+  mCache = cache;
+  MOZ_ASSERT(mCache);
+
+  // If the worker is canceled, CancelMainThread() will have cleared the
+  // loaders.
+  for (uint32_t i = 0, len = mLoaders.Length(); i < len; ++i) {
+    mLoaders[i]->Load(cache);
+  }
+}
+
+void
+CacheCreator::DeleteCache()
+{
+  AssertIsOnMainThread();
+
+  ErrorResult rv;
+
+  // It's safe to do this while Cache::Match() and Cache::Put() calls are
+  // running.
+  nsRefPtr<Promise> promise = mCacheStorage->Delete(mCacheName, rv);
+  if (NS_WARN_IF(rv.Failed())) {
+    return;
+  }
+
+  // We don't care to know the result of the promise object.
+  FailLoaders(NS_ERROR_FAILURE);
+}
+
+void
+CacheScriptLoader::Fail(nsresult aRv)
+{
+  AssertIsOnMainThread();
+  MOZ_ASSERT(NS_FAILED(aRv));
+
+  if (mFailed) {
+    return;
+  }
+
+  mFailed = true;
+
+  if (mPump) {
+    MOZ_ASSERT(mLoadInfo.mCacheStatus == ScriptLoadInfo::ReadingFromCache);
+    mPump->Cancel(aRv);
+    mPump = nullptr;
+  }
+
+  mLoadInfo.mCacheStatus = ScriptLoadInfo::Cancel;
+  mRunnable->LoadingFinished(mIndex, aRv);
+}
+
+void
+CacheScriptLoader::Load(Cache* aCache)
+{
+  AssertIsOnMainThread();
+  MOZ_ASSERT(aCache);
+
+  nsCOMPtr<nsIURI> uri;
+  nsresult rv = NS_NewURI(getter_AddRefs(uri), mLoadInfo.mURL, nullptr,
+                          mBaseURI);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    Fail(rv);
+    return;
+  }
+
+  nsAutoCString spec;
+  rv = uri->GetSpec(spec);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    Fail(rv);
+    return;
+  }
+
+  MOZ_ASSERT(mLoadInfo.mFullURL.IsEmpty());
+  CopyUTF8toUTF16(spec, mLoadInfo.mFullURL);
+
+  RequestOrUSVString request;
+  request.SetAsUSVString().Rebind(mLoadInfo.mFullURL.Data(),
+                                  mLoadInfo.mFullURL.Length());
+
+  CacheQueryOptions params;
+
+  ErrorResult error;
+  nsRefPtr<Promise> promise = aCache->Match(request, params, error);
+  if (NS_WARN_IF(error.Failed())) {
+    Fail(error.ErrorCode());
+    return;
+  }
+
+  promise->AppendNativeHandler(this);
+}
+
+void
+CacheScriptLoader::RejectedCallback(JSContext* aCx,
+                                    JS::Handle<JS::Value> aValue)
+{
+  AssertIsOnMainThread();
+  MOZ_ASSERT(mLoadInfo.mCacheStatus == ScriptLoadInfo::Uncached);
+  Fail(NS_ERROR_FAILURE);
+}
+
+void
+CacheScriptLoader::ResolvedCallback(JSContext* aCx,
+                                    JS::Handle<JS::Value> aValue)
+{
+  AssertIsOnMainThread();
+  MOZ_ASSERT(mLoadInfo.mCacheStatus == ScriptLoadInfo::Uncached);
+
+  // If we have already called 'Fail', we should not proceed.
+  if (mFailed) {
+    return;
+  }
+
+  if (aValue.isUndefined()) {
+    mLoadInfo.mCacheStatus = ScriptLoadInfo::ToBeCached;
+    mRunnable->LoadScript(mIndex);
+    return;
+  }
+
+  MOZ_ASSERT(aValue.isObject());
+
+  JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
+  Response* response = nullptr;
+  nsresult rv = UNWRAP_OBJECT(Response, obj, response);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    Fail(rv);
+    return;
+  }
+
+  nsCOMPtr<nsIInputStream> inputStream;
+  response->GetBody(getter_AddRefs(inputStream));
+
+  if (!inputStream) {
+    mLoadInfo.mCacheStatus = ScriptLoadInfo::Cached;
+    mRunnable->DataReceivedFromCache(mIndex, (uint8_t*)"", 0);
+    return;
+  }
+
+  MOZ_ASSERT(!mPump);
+  rv = NS_NewInputStreamPump(getter_AddRefs(mPump), inputStream);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    Fail(rv);
+    return;
+  }
+
+  nsCOMPtr<nsIStreamLoader> loader;
+  rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    Fail(rv);
+    return;
+  }
+
+  rv = mPump->AsyncRead(loader, nullptr);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    mPump = nullptr;
+    Fail(rv);
+    return;
+  }
+
+
+  nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(mPump);
+  if (rr) {
+    nsCOMPtr<nsIEventTarget> sts =
+      do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+    rv = rr->RetargetDeliveryTo(sts);
+    if (NS_FAILED(rv)) {
+      NS_WARNING("Failed to dispatch the nsIInputStreamPump to a IO thread.");
+    }
+  }
+
+  mLoadInfo.mCacheStatus = ScriptLoadInfo::ReadingFromCache;
+}
+
+NS_IMETHODIMP
+CacheScriptLoader::OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext,
+                                    nsresult aStatus, uint32_t aStringLen,
+                                    const uint8_t* aString)
+{
+  AssertIsOnMainThread();
+  MOZ_ASSERT(mLoadInfo.mCacheStatus == ScriptLoadInfo::ReadingFromCache);
+
+  mPump = nullptr;
+
+  if (NS_FAILED(aStatus)) {
+    Fail(aStatus);
+    return NS_OK;
+  }
+
+  mLoadInfo.mCacheStatus = ScriptLoadInfo::Cached;
+
+  mRunnable->DataReceivedFromCache(mIndex, aString, aStringLen);
+  return NS_OK;
+}
+
+ class ChannelGetterRunnable final : public nsRunnable
 {
   WorkerPrivate* mParentWorker;
   nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
   const nsAString& mScriptURL;
   nsIChannel** mChannel;
   nsresult mResult;
 
 public:
@@ -669,21 +1396,21 @@ public:
   }
 
   NS_IMETHOD
   Run() override
   {
     AssertIsOnMainThread();
 
     nsIPrincipal* principal = mParentWorker->GetPrincipal();
-    NS_ASSERTION(principal, "This should never be null here!");
+    MOZ_ASSERT(principal);
 
     // Figure out our base URI.
     nsCOMPtr<nsIURI> baseURI = mParentWorker->GetBaseURI();
-    NS_ASSERTION(baseURI, "Should have been set already!");
+    MOZ_ASSERT(baseURI);
 
     // May be null.
     nsCOMPtr<nsIDocument> parentDoc = mParentWorker->GetDocument();
 
     nsCOMPtr<nsILoadGroup> loadGroup = mParentWorker->GetLoadGroup();
 
     nsCOMPtr<nsIChannel> channel;
     mResult =
@@ -714,20 +1441,22 @@ public:
 private:
   virtual ~ChannelGetterRunnable()
   { }
 };
 
 ScriptExecutorRunnable::ScriptExecutorRunnable(
                                             ScriptLoaderRunnable& aScriptLoader,
                                             nsIEventTarget* aSyncLoopTarget,
+                                            bool aIsWorkerScript,
                                             uint32_t aFirstIndex,
                                             uint32_t aLastIndex)
 : MainThreadWorkerSyncRunnable(aScriptLoader.mWorkerPrivate, aSyncLoopTarget),
-  mScriptLoader(aScriptLoader), mFirstIndex(aFirstIndex), mLastIndex(aLastIndex)
+  mScriptLoader(aScriptLoader), mIsWorkerScript(aIsWorkerScript),
+  mFirstIndex(aFirstIndex), mLastIndex(aLastIndex)
 {
   MOZ_ASSERT(aFirstIndex <= aLastIndex);
   MOZ_ASSERT(aLastIndex < aScriptLoader.mLoadInfos.Length());
 }
 
 bool
 ScriptExecutorRunnable::IsDebuggerRunnable() const
 {
@@ -845,16 +1574,22 @@ ScriptExecutorRunnable::Cancel()
   return MainThreadWorkerSyncRunnable::Cancel();
 }
 
 void
 ScriptExecutorRunnable::ShutdownScriptLoader(JSContext* aCx,
                                              WorkerPrivate* aWorkerPrivate,
                                              bool aResult)
 {
+  MOZ_ASSERT(mLastIndex == mScriptLoader.mLoadInfos.Length() - 1);
+
+  if (mIsWorkerScript && aWorkerPrivate->IsServiceWorker()) {
+    aWorkerPrivate->SetLoadingWorkerScript(false);
+  }
+
   aWorkerPrivate->RemoveFeature(aCx, &mScriptLoader);
   aWorkerPrivate->StopSyncLoop(mSyncLoopTarget, aResult);
 }
 
 bool
 LoadAllScripts(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
                nsTArray<ScriptLoadInfo>& aLoadInfos, bool aIsMainScript,
                WorkerScriptType aWorkerScriptType)
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -616,17 +616,18 @@ public:
       return NS_ERROR_DOM_SECURITY_ERR;
     }
 
     // We have to create a ServiceWorker here simply to ensure there are no
     // errors. Ideally we should just pass this worker on to ContinueInstall.
     MOZ_ASSERT(!swm->mSetOfScopesBeingUpdated.Contains(mRegistration->mScope));
     swm->mSetOfScopesBeingUpdated.Put(mRegistration->mScope, true);
     nsRefPtr<ServiceWorkerInfo> dummyInfo =
-      new ServiceWorkerInfo(mRegistration, mRegistration->mScriptSpec);
+      new ServiceWorkerInfo(mRegistration, mRegistration->mScriptSpec,
+                            EmptyString()); // TODO(baku) this has to be generated.
     nsRefPtr<ServiceWorker> serviceWorker;
     rv = swm->CreateServiceWorker(mRegistration->mPrincipal,
                                   dummyInfo,
                                   getter_AddRefs(serviceWorker));
 
     if (NS_WARN_IF(NS_FAILED(rv))) {
       swm->mSetOfScopesBeingUpdated.Remove(mRegistration->mScope);
       Fail(NS_ERROR_DOM_ABORT_ERR);
@@ -673,17 +674,18 @@ public:
     // Begin [[Install]] atomic step 4.
     if (mRegistration->mInstallingWorker) {
       // FIXME(nsm): Terminate and stuff
       mRegistration->mInstallingWorker->UpdateState(ServiceWorkerState::Redundant);
     }
 
     swm->InvalidateServiceWorkerRegistrationWorker(mRegistration,
                                                    WhichServiceWorker::INSTALLING_WORKER);
-    mRegistration->mInstallingWorker = new ServiceWorkerInfo(mRegistration, mRegistration->mScriptSpec);
+    mRegistration->mInstallingWorker =
+      new ServiceWorkerInfo(mRegistration, mRegistration->mScriptSpec, EmptyString()); // TODO(baku)
     mRegistration->mInstallingWorker->UpdateState(ServiceWorkerState::Installing);
 
     Succeed();
 
     // Step 4.6 "Queue a task..." for updatefound.
     nsCOMPtr<nsIRunnable> upr =
       NS_NewRunnableMethodWithArg<ServiceWorkerRegistrationInfo*>(swm,
                                                                   &ServiceWorkerManager::FireUpdateFound,
@@ -1745,32 +1747,44 @@ ServiceWorkerRegistrationInfo::FinishAct
 
 NS_IMETHODIMP
 ServiceWorkerManager::CreateServiceWorkerForWindow(nsPIDOMWindow* aWindow,
                                                    ServiceWorkerInfo* aInfo,
                                                    ServiceWorker** aServiceWorker)
 {
   AssertIsOnMainThread();
   MOZ_ASSERT(aWindow);
-
-  RuntimeService* rs = RuntimeService::GetOrCreateService();
-  nsRefPtr<SharedWorker> sharedWorker;
+  MOZ_ASSERT(aInfo);
 
   AutoJSAPI jsapi;
   jsapi.Init(aWindow);
   JSContext* cx = jsapi.cx();
 
-  nsCOMPtr<nsIGlobalObject> sgo = do_QueryInterface(aWindow);
-  JS::Rooted<JSObject*> jsGlobal(cx, sgo->GetGlobalJSObject());
-  GlobalObject global(cx, jsGlobal);
-  nsresult rv = rs->CreateSharedWorkerForServiceWorker(global,
-                                                       NS_ConvertUTF8toUTF16(aInfo->ScriptSpec()),
-                                                       aInfo->Scope(),
-                                                       getter_AddRefs(sharedWorker));
-
+  WorkerLoadInfo loadInfo;
+  nsresult rv = WorkerPrivate::GetLoadInfo(cx, aWindow, nullptr,
+                                           NS_ConvertUTF8toUTF16(aInfo->ScriptSpec()),
+                                           false,
+                                           WorkerPrivate::OverrideLoadGroup,
+                                           &loadInfo);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  loadInfo.mServiceWorkerCacheName = aInfo->CacheName();
+
+  RuntimeService* rs = RuntimeService::GetOrCreateService();
+  if (!rs) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsRefPtr<SharedWorker> sharedWorker;
+  rv = rs->CreateSharedWorkerForServiceWorkerFromLoadInfo(cx, &loadInfo,
+                                                          NS_ConvertUTF8toUTF16(aInfo->ScriptSpec()),
+                                                          aInfo->Scope(),
+                                                          getter_AddRefs(sharedWorker));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsRefPtr<ServiceWorker> serviceWorker =
     new ServiceWorker(aWindow, aInfo, sharedWorker);
 
   serviceWorker.forget(aServiceWorker);
@@ -1793,17 +1807,17 @@ ServiceWorkerManager::LoadRegistrations(
     ServiceWorkerRegistrationInfo* registration =
       CreateNewRegistration(aRegistrations[i].scope(), principal);
 
     registration->mScriptSpec = aRegistrations[i].scriptSpec();
 
     const nsCString& currentWorkerURL = aRegistrations[i].currentWorkerURL();
     if (!currentWorkerURL.IsEmpty()) {
       registration->mActiveWorker =
-        new ServiceWorkerInfo(registration, currentWorkerURL);
+        new ServiceWorkerInfo(registration, currentWorkerURL, EmptyString()); // TODO(baku)
       registration->mActiveWorker->SetActivateStateUncheckedWithoutEvent(ServiceWorkerState::Activated);
     }
   }
 }
 
 void
 ServiceWorkerManager::ActorFailed()
 {
@@ -2528,16 +2542,17 @@ ServiceWorkerManager::CreateServiceWorke
   WorkerLoadInfo info;
   nsresult rv = NS_NewURI(getter_AddRefs(info.mBaseURI), aInfo->ScriptSpec(),
                           nullptr, nullptr);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   info.mResolvedScriptURI = info.mBaseURI;
+  info.mServiceWorkerCacheName = aInfo->CacheName();
 
   rv = info.mBaseURI->GetHost(info.mDomain);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   info.mPrincipal = aPrincipal;
 
--- a/dom/workers/ServiceWorkerManager.h
+++ b/dom/workers/ServiceWorkerManager.h
@@ -210,16 +210,17 @@ public:
  * _GetNewestWorker(serviceWorkerRegistration)", we represent the description
  * by this class and spawn a ServiceWorker in the right global when required.
  */
 class ServiceWorkerInfo final
 {
 private:
   const ServiceWorkerRegistrationInfo* mRegistration;
   nsCString mScriptSpec;
+  nsString mCacheName;
   ServiceWorkerState mState;
   // We hold rawptrs since the ServiceWorker constructor and destructor ensure
   // addition and removal.
   // There is a high chance of there being at least one ServiceWorker
   // associated with this all the time.
   nsAutoTArray<ServiceWorker*, 1> mInstances;
 
   ~ServiceWorkerInfo()
@@ -242,30 +243,38 @@ public:
 
   void SetScriptSpec(const nsCString& aSpec)
   {
     MOZ_ASSERT(!aSpec.IsEmpty());
     mScriptSpec = aSpec;
   }
 
   explicit ServiceWorkerInfo(ServiceWorkerRegistrationInfo* aReg,
-                             const nsACString& aScriptSpec)
+                             const nsACString& aScriptSpec,
+                             const nsAString& aCacheName)
     : mRegistration(aReg)
     , mScriptSpec(aScriptSpec)
+    , mCacheName(aCacheName)
     , mState(ServiceWorkerState::EndGuard_)
   {
     MOZ_ASSERT(mRegistration);
   }
 
   ServiceWorkerState
   State() const
   {
     return mState;
   }
 
+  const nsString&
+  CacheName() const
+  {
+    return mCacheName;
+  }
+
   void
   UpdateState(ServiceWorkerState aState);
 
   // Only used to set initial state when loading from disk!
   void
   SetActivateStateUncheckedWithoutEvent(ServiceWorkerState aState)
   {
     mState = aState;
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -2289,16 +2289,17 @@ WorkerLoadInfo::StealFrom(WorkerLoadInfo
 
   MOZ_ASSERT(!mInterfaceRequestor);
   aOther.mInterfaceRequestor.swap(mInterfaceRequestor);
 
   MOZ_ASSERT(!mPrincipalInfo);
   mPrincipalInfo = aOther.mPrincipalInfo.forget();
 
   mDomain = aOther.mDomain;
+  mServiceWorkerCacheName = aOther.mServiceWorkerCacheName;
   mWindowID = aOther.mWindowID;
   mFromWindow = aOther.mFromWindow;
   mEvalAllowed = aOther.mEvalAllowed;
   mReportCSPViolations = aOther.mReportCSPViolations;
   mXHRParamsAllowed = aOther.mXHRParamsAllowed;
   mPrincipalIsSystem = aOther.mPrincipalIsSystem;
   mIsInPrivilegedApp = aOther.mIsInPrivilegedApp;
   mIsInCertifiedApp = aOther.mIsInCertifiedApp;
@@ -2676,17 +2677,18 @@ WorkerPrivateParent<Derived>::WorkerPriv
                                            bool aIsChromeWorker,
                                            WorkerType aWorkerType,
                                            const nsACString& aSharedWorkerName,
                                            WorkerLoadInfo& aLoadInfo)
 : mMutex("WorkerPrivateParent Mutex"),
   mCondVar(mMutex, "WorkerPrivateParent CondVar"),
   mMemoryReportCondVar(mMutex, "WorkerPrivateParent Memory Report CondVar"),
   mParent(aParent), mScriptURL(aScriptURL),
-  mSharedWorkerName(aSharedWorkerName), mBusyCount(0), mMessagePortSerial(0),
+  mSharedWorkerName(aSharedWorkerName), mLoadingWorkerScript(false),
+  mBusyCount(0), mMessagePortSerial(0),
   mParentStatus(Pending), mParentFrozen(false),
   mIsChromeWorker(aIsChromeWorker), mMainThreadObjectsForgotten(false),
   mWorkerType(aWorkerType),
   mCreationTimeStamp(TimeStamp::Now())
 {
   MOZ_ASSERT_IF(!IsDedicatedWorker(),
                 !aSharedWorkerName.IsVoid() && NS_IsMainThread());
   MOZ_ASSERT_IF(IsDedicatedWorker(), aSharedWorkerName.IsEmpty());
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -159,16 +159,18 @@ private:
   WorkerPrivate* mParent;
   nsString mScriptURL;
   nsCString mSharedWorkerName;
   LocationInfo mLocationInfo;
   // The lifetime of these objects within LoadInfo is managed explicitly;
   // they do not need to be cycle collected.
   WorkerLoadInfo mLoadInfo;
 
+  Atomic<bool> mLoadingWorkerScript;
+
   // Only used for top level workers.
   nsTArray<nsCOMPtr<nsIRunnable>> mQueuedRunnables;
 
   // Only for ChromeWorkers without window and only touched on the main thread.
   nsTArray<nsCString> mHostObjectURIs;
 
   // Protected by mMutex.
   JSSettings mJSSettings;
@@ -475,16 +477,33 @@ public:
 
   nsIURI*
   GetResolvedScriptURI() const
   {
     AssertIsOnMainThread();
     return mLoadInfo.mResolvedScriptURI;
   }
 
+  const nsString&
+  ServiceWorkerCacheName() const
+  {
+    MOZ_ASSERT(IsServiceWorker());
+    AssertIsOnMainThread();
+    return mLoadingWorkerScript ?
+             mLoadInfo.mServiceWorkerCacheName : EmptyString();
+  }
+  
+  void
+  SetLoadingWorkerScript(bool aLoadingWorkerScript)
+  {
+    // any thread
+    MOZ_ASSERT(IsServiceWorker());
+    mLoadingWorkerScript = aLoadingWorkerScript;
+  }
+
   TimeStamp CreationTimeStamp() const
   {
     return mCreationTimeStamp;
   }
 
   TimeStamp NowBaseTimeStamp() const
   {
     return mNowBaseTimeStamp;
--- a/dom/workers/Workers.h
+++ b/dom/workers/Workers.h
@@ -235,16 +235,18 @@ struct WorkerLoadInfo
   };
 
   // Only set if we have a custom overriden load group
   nsRefPtr<InterfaceRequestor> mInterfaceRequestor;
 
   nsAutoPtr<mozilla::ipc::PrincipalInfo> mPrincipalInfo;
   nsCString mDomain;
 
+  nsString mServiceWorkerCacheName;
+
   uint64_t mWindowID;
 
   bool mFromWindow;
   bool mEvalAllowed;
   bool mReportCSPViolations;
   bool mXHRParamsAllowed;
   bool mPrincipalIsSystem;
   bool mIsInPrivilegedApp;