Bug 1207727 - Rework updating service workers according to the latest spec. r=bkelly
authordimi <dlee@mozilla.com>
Mon, 26 Oct 2015 10:59:48 +0800
changeset 304651 9214ef441dabc11baef57ca294de2797c36158ed
parent 304650 cae63161b689624cf55c4391c42b8e7683a72654
child 304652 fcde5d56c92665f3e8b68a14543f21ceeb8de22d
push id1001
push userraliiev@mozilla.com
push dateMon, 18 Jan 2016 19:06:03 +0000
treeherdermozilla-release@8b89261f3ac4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbkelly
bugs1207727
milestone44.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 1207727 - Rework updating service workers according to the latest spec. r=bkelly
dom/workers/ServiceWorkerEvents.cpp
dom/workers/ServiceWorkerManager.cpp
dom/workers/ServiceWorkerManager.h
dom/workers/ServiceWorkerPrivate.cpp
dom/workers/ServiceWorkerPrivate.h
dom/workers/ServiceWorkerScriptCache.cpp
dom/workers/ServiceWorkerScriptCache.h
--- a/dom/workers/ServiceWorkerEvents.cpp
+++ b/dom/workers/ServiceWorkerEvents.cpp
@@ -437,19 +437,25 @@ RespondWithHandler::CancelRequest(nsresu
 void
 FetchEvent::RespondWith(Promise& aArg, ErrorResult& aRv)
 {
   if (EventPhase() == nsIDOMEvent::NONE || mWaitToRespond) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
-  if (!mPromise) {
-    mPromise = &aArg;
+  // 4.5.3.2 If the respond-with entered flag is set, then:
+  // Throw an "InvalidStateError" exception.
+  // Here we use |mPromise != nullptr| as respond-with enter flag
+  if (mPromise) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return;
   }
+  mPromise = &aArg;
+
   RefPtr<InternalRequest> ir = mRequest->GetInternalRequest();
   StopImmediatePropagation();
   mWaitToRespond = true;
   RefPtr<RespondWithHandler> handler =
     new RespondWithHandler(mChannel, mRequest->Mode(), ir->IsClientRequest(),
                            ir->IsNavigationRequest(), mScriptSpec);
   aArg.AppendNativeHandler(handler);
 }
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -367,16 +367,17 @@ ServiceWorkerRegistrationInfo::Clear()
                                                  WhichServiceWorker::ACTIVE_WORKER);
 }
 
 ServiceWorkerRegistrationInfo::ServiceWorkerRegistrationInfo(const nsACString& aScope,
                                                              nsIPrincipal* aPrincipal)
   : mControlledDocumentsCounter(0)
   , mScope(aScope)
   , mPrincipal(aPrincipal)
+  , mLastUpdateCheckTime(0)
   , mPendingUninstall(false)
 { }
 
 ServiceWorkerRegistrationInfo::~ServiceWorkerRegistrationInfo()
 {
   if (IsControllingDocuments()) {
     NS_WARNING("ServiceWorkerRegistrationInfo is still controlling documents. This can be a bug or a leak in ServiceWorker API or in any other API that takes the document alive.");
   }
@@ -1177,17 +1178,17 @@ private:
     // 9.2.20 If newestWorker is not null, and newestWorker's script url is
     // equal to registration's registering script url and response is a
     // byte-for-byte match with the script resource of newestWorker...
     if (workerInfo && workerInfo->ScriptSpec().Equals(mRegistration->mScriptSpec)) {
       cacheName = workerInfo->CacheName();
     }
 
     nsresult rv =
-      serviceWorkerScriptCache::Compare(mRegistration->mPrincipal, cacheName,
+      serviceWorkerScriptCache::Compare(mRegistration, mRegistration->mPrincipal, cacheName,
                                         NS_ConvertUTF8toUTF16(mRegistration->mScriptSpec),
                                         this, mLoadGroup);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return Fail(rv);
     }
   }
 
   void
@@ -1900,25 +1901,28 @@ ServiceWorkerManager::SendPushEvent(cons
     return NS_ERROR_INVALID_ARG;
   }
 
   ServiceWorkerInfo* serviceWorker = GetActiveWorkerInfoForScope(attrs, aScope);
   if (NS_WARN_IF(!serviceWorker)) {
     return NS_ERROR_FAILURE;
   }
 
+  RefPtr<ServiceWorkerRegistrationInfo> registration =
+    GetRegistration(serviceWorker->GetPrincipal(), aScope);
+
   if (optional_argc == 2) {
     nsTArray<uint8_t> data;
     if (!data.InsertElementsAt(0, aDataBytes, aDataLength, fallible)) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
-    return serviceWorker->WorkerPrivate()->SendPushEvent(Some(data));
+    return serviceWorker->WorkerPrivate()->SendPushEvent(Some(data), registration);
   } else {
     MOZ_ASSERT(optional_argc == 0);
-    return serviceWorker->WorkerPrivate()->SendPushEvent(Nothing());
+    return serviceWorker->WorkerPrivate()->SendPushEvent(Nothing(), registration);
   }
 #endif // MOZ_SIMPLEPUSH
 }
 
 NS_IMETHODIMP
 ServiceWorkerManager::SendPushSubscriptionChangeEvent(const nsACString& aOriginAttributes,
                                                       const nsACString& aScope)
 {
@@ -2378,16 +2382,43 @@ ServiceWorkerRegistrationInfo::FinishAct
 
   // Activation never fails, so aSuccess is ignored.
   mActiveWorker->UpdateState(ServiceWorkerState::Activated);
   RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
   swm->StoreRegistration(mPrincipal, this);
 }
 
 void
+ServiceWorkerRegistrationInfo::RefreshLastUpdateCheckTime()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mLastUpdateCheckTime = PR_IntervalNow() / PR_MSEC_PER_SEC;
+}
+
+bool
+ServiceWorkerRegistrationInfo::IsLastUpdateCheckTimeOverOneDay() const
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // For testing.
+  if (Preferences::GetBool("dom.serviceWorkers.testUpdateOverOneDay")) {
+    return true;
+  }
+
+  const uint64_t kSecondsPerDay = 86400;
+  const uint64_t now = PR_IntervalNow() / PR_MSEC_PER_SEC;
+
+  if ((mLastUpdateCheckTime != 0) &&
+      (now - mLastUpdateCheckTime > kSecondsPerDay)) {
+    return true;
+  }
+  return false;
+}
+
+void
 ServiceWorkerManager::LoadRegistration(
                              const ServiceWorkerRegistrationData& aRegistration)
 {
   AssertIsOnMainThread();
 
   nsCOMPtr<nsIPrincipal> principal =
     PrincipalInfoToPrincipal(aRegistration.principal());
   if (!principal) {
--- a/dom/workers/ServiceWorkerManager.h
+++ b/dom/workers/ServiceWorkerManager.h
@@ -68,16 +68,18 @@ public:
   nsCString mScriptSpec;
 
   nsCOMPtr<nsIPrincipal> mPrincipal;
 
   RefPtr<ServiceWorkerInfo> mActiveWorker;
   RefPtr<ServiceWorkerInfo> mWaitingWorker;
   RefPtr<ServiceWorkerInfo> mInstallingWorker;
 
+  uint64_t mLastUpdateCheckTime;
+
   // When unregister() is called on a registration, it is not immediately
   // removed since documents may be controlled. It is marked as
   // pendingUninstall and when all controlling documents go away, removed.
   bool mPendingUninstall;
 
   ServiceWorkerRegistrationInfo(const nsACString& aScope,
                                 nsIPrincipal* aPrincipal);
 
@@ -127,16 +129,21 @@ public:
   TryToActivate();
 
   void
   Activate();
 
   void
   FinishActivate(bool aSuccess);
 
+  void
+  RefreshLastUpdateCheckTime();
+
+  bool
+  IsLastUpdateCheckTimeOverOneDay() const;
 };
 
 class ServiceWorkerUpdateFinishCallback
 {
 protected:
   virtual ~ServiceWorkerUpdateFinishCallback()
   { }
 
--- a/dom/workers/ServiceWorkerPrivate.cpp
+++ b/dom/workers/ServiceWorkerPrivate.cpp
@@ -186,16 +186,61 @@ public:
     MOZ_ASSERT(workerPrivate);
     workerPrivate->AssertIsOnWorkerThread();
 #endif
   }
 };
 
 NS_IMPL_ISUPPORTS0(KeepAliveHandler)
 
+class SoftUpdateRequest : public nsRunnable
+{
+protected:
+  nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> mRegistration;
+public:
+  explicit SoftUpdateRequest(nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration)
+    : mRegistration(aRegistration)
+  {
+    MOZ_ASSERT(aRegistration);
+  }
+
+  NS_IMETHOD Run()
+  {
+    AssertIsOnMainThread();
+
+    RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+    MOZ_ASSERT(swm);
+
+    OriginAttributes attrs =
+      mozilla::BasePrincipal::Cast(mRegistration->mPrincipal)->OriginAttributesRef();
+
+    swm->PropagateSoftUpdate(attrs,
+                             NS_ConvertUTF8toUTF16(mRegistration->mScope));
+    return NS_OK;
+  }
+};
+
+class CheckLastUpdateTimeRequest final : public SoftUpdateRequest
+{
+public:
+  explicit CheckLastUpdateTimeRequest(
+    nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration)
+    : SoftUpdateRequest(aRegistration)
+  {}
+
+  NS_IMETHOD Run()
+  {
+    AssertIsOnMainThread();
+    if (mRegistration->IsLastUpdateCheckTimeOverOneDay()) {
+      SoftUpdateRequest::Run();
+    }
+    return NS_OK;
+  }
+};
+
 class ExtendableEventWorkerRunnable : public WorkerRunnable
 {
 protected:
   nsMainThreadPtrHandle<KeepAliveToken> mKeepAliveToken;
 
 public:
   ExtendableEventWorkerRunnable(WorkerPrivate* aWorkerPrivate,
                                 KeepAliveToken* aKeepAliveToken)
@@ -243,16 +288,43 @@ public:
     waitUntilPromise->AppendNativeHandler(keepAliveHandler);
 
     if (aWaitUntilPromise) {
       waitUntilPromise.forget(aWaitUntilPromise);
     }
   }
 };
 
+// Handle functional event
+// 9.9.7 If the time difference in seconds calculated by the current time minus
+// registration's last update check time is greater than 86400, invoke Soft Update
+// algorithm.
+class ExtendableFunctionalEventWorkerRunnable : public ExtendableEventWorkerRunnable
+{
+protected:
+  nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> mRegistration;
+public:
+  ExtendableFunctionalEventWorkerRunnable(WorkerPrivate* aWorkerPrivate,
+                                          KeepAliveToken* aKeepAliveToken,
+                                          nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration)
+    : ExtendableEventWorkerRunnable(aWorkerPrivate, aKeepAliveToken)
+    , mRegistration(aRegistration)
+  {
+    MOZ_ASSERT(aRegistration);
+  }
+
+  void
+  PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult)
+  {
+    nsCOMPtr<nsIRunnable> runnable = new CheckLastUpdateTimeRequest(mRegistration);
+
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable.forget())));
+  }
+};
+
 /*
  * Fires 'install' event on the ServiceWorkerGlobalScope. Modifies busy count
  * since it fires the event. This is ok since there can't be nested
  * ServiceWorkers, so the parent thread -> worker thread requirement for
  * runnables is satisfied.
  */
 class LifecycleEventWorkerRunnable : public ExtendableEventWorkerRunnable
 {
@@ -410,25 +482,27 @@ ServiceWorkerPrivate::SendLifeCycleEvent
   }
 
   return NS_OK;
 }
 
 #ifndef MOZ_SIMPLEPUSH
 namespace {
 
-class SendPushEventRunnable final : public ExtendableEventWorkerRunnable
+class SendPushEventRunnable final : public ExtendableFunctionalEventWorkerRunnable
 {
   Maybe<nsTArray<uint8_t>> mData;
 
 public:
   SendPushEventRunnable(WorkerPrivate* aWorkerPrivate,
                         KeepAliveToken* aKeepAliveToken,
-                        const Maybe<nsTArray<uint8_t>>& aData)
-      : ExtendableEventWorkerRunnable(aWorkerPrivate, aKeepAliveToken)
+                        const Maybe<nsTArray<uint8_t>>& aData,
+                        nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> aRegistration)
+      : ExtendableFunctionalEventWorkerRunnable(
+          aWorkerPrivate, aKeepAliveToken, aRegistration)
       , mData(aData)
   {
     AssertIsOnMainThread();
     MOZ_ASSERT(aWorkerPrivate);
     MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
   }
 
   bool
@@ -499,28 +573,34 @@ public:
     return true;
   }
 };
 
 } // anonymous namespace
 #endif // !MOZ_SIMPLEPUSH
 
 nsresult
-ServiceWorkerPrivate::SendPushEvent(const Maybe<nsTArray<uint8_t>>& aData)
+ServiceWorkerPrivate::SendPushEvent(const Maybe<nsTArray<uint8_t>>& aData,
+                                    ServiceWorkerRegistrationInfo* aRegistration)
 {
 #ifdef MOZ_SIMPLEPUSH
   return NS_ERROR_NOT_AVAILABLE;
 #else
   nsresult rv = SpawnWorkerIfNeeded(PushEvent, nullptr);
   NS_ENSURE_SUCCESS(rv, rv);
 
   MOZ_ASSERT(mKeepAliveToken);
+
+  nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> regInfo(
+    new nsMainThreadPtrHolder<ServiceWorkerRegistrationInfo>(aRegistration, false));
+
   RefPtr<WorkerRunnable> r = new SendPushEventRunnable(mWorkerPrivate,
                                                          mKeepAliveToken,
-                                                         aData);
+                                                         aData,
+                                                         regInfo);
   AutoJSAPI jsapi;
   jsapi.Init();
   if (NS_WARN_IF(!r->Dispatch(jsapi.cx()))) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 #endif // MOZ_SIMPLEPUSH
@@ -822,17 +902,17 @@ ServiceWorkerPrivate::SendNotificationCl
 
   return NS_OK;
 }
 
 namespace {
 
 // Inheriting ExtendableEventWorkerRunnable so that the worker is not terminated
 // while handling the fetch event, though that's very unlikely.
-class FetchEventRunnable : public ExtendableEventWorkerRunnable
+class FetchEventRunnable : public ExtendableFunctionalEventWorkerRunnable
                          , public nsIHttpHeaderVisitor {
   nsMainThreadPtrHandle<nsIInterceptedChannel> mInterceptedChannel;
   const nsCString mScriptSpec;
   nsTArray<nsCString> mHeaderNames;
   nsTArray<nsCString> mHeaderValues;
   UniquePtr<ServiceWorkerClientInfo> mClientInfo;
   nsCString mSpec;
   nsCString mMethod;
@@ -846,19 +926,21 @@ class FetchEventRunnable : public Extend
   nsCString mReferrer;
 public:
   FetchEventRunnable(WorkerPrivate* aWorkerPrivate,
                      KeepAliveToken* aKeepAliveToken,
                      nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
                      // CSP checks might require the worker script spec
                      // later on.
                      const nsACString& aScriptSpec,
+                     nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration,
                      UniquePtr<ServiceWorkerClientInfo>&& aClientInfo,
                      bool aIsReload)
-    : ExtendableEventWorkerRunnable(aWorkerPrivate, aKeepAliveToken)
+    : ExtendableFunctionalEventWorkerRunnable(
+        aWorkerPrivate, aKeepAliveToken, aRegistration)
     , mInterceptedChannel(aChannel)
     , mScriptSpec(aScriptSpec)
     , mClientInfo(Move(aClientInfo))
     , mIsReload(aIsReload)
     , mIsHttpChannel(false)
     , mRequestMode(RequestMode::No_cors)
     , mRequestRedirect(RequestRedirect::Follow)
     // By default we set it to same-origin since normal HTTP fetches always
@@ -1085,16 +1167,23 @@ private:
     }
 
     RefPtr<Promise> respondWithPromise = event->GetPromise();
     if (respondWithPromise) {
       RefPtr<KeepAliveHandler> keepAliveHandler =
         new KeepAliveHandler(mKeepAliveToken);
       respondWithPromise->AppendNativeHandler(keepAliveHandler);
     }
+
+    // 9.8.22 If request is a non-subresource request, then: Invoke Soft Update algorithm
+    if (internalReq->IsNavigationRequest()) {
+      nsCOMPtr<nsIRunnable> runnable= new SoftUpdateRequest(mRegistration);
+
+      MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable.forget())));
+    }
     return true;
   }
 };
 
 NS_IMPL_ISUPPORTS_INHERITED(FetchEventRunnable, WorkerRunnable, nsIHttpHeaderVisitor)
 
 } // anonymous namespace
 
@@ -1114,19 +1203,29 @@ ServiceWorkerPrivate::SendFetchEvent(nsI
 
   nsMainThreadPtrHandle<nsIInterceptedChannel> handle(
     new nsMainThreadPtrHolder<nsIInterceptedChannel>(aChannel, false));
 
   if (NS_WARN_IF(!mInfo)) {
     return NS_ERROR_FAILURE;
   }
 
+  RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+  MOZ_ASSERT(swm);
+
+  RefPtr<ServiceWorkerRegistrationInfo> registration =
+    swm->GetRegistration(mInfo->GetPrincipal(), mInfo->Scope());
+
+  nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> regInfo(
+    new nsMainThreadPtrHolder<ServiceWorkerRegistrationInfo>(registration, false));
+
   RefPtr<FetchEventRunnable> r =
     new FetchEventRunnable(mWorkerPrivate, mKeepAliveToken, handle,
-                           mInfo->ScriptSpec(), Move(aClientInfo), aIsReload);
+                           mInfo->ScriptSpec(), regInfo,
+                           Move(aClientInfo), aIsReload);
   rv = r->Init();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   AutoJSAPI jsapi;
   jsapi.Init();
   if (NS_WARN_IF(!r->Dispatch(jsapi.cx()))) {
--- a/dom/workers/ServiceWorkerPrivate.h
+++ b/dom/workers/ServiceWorkerPrivate.h
@@ -79,17 +79,18 @@ public:
   ContinueOnSuccessfulScriptEvaluation(nsRunnable* aCallback);
 
   nsresult
   SendLifeCycleEvent(const nsAString& aEventType,
                      LifeCycleEventCallback* aCallback,
                      nsIRunnable* aLoadFailure);
 
   nsresult
-  SendPushEvent(const Maybe<nsTArray<uint8_t>>& aData);
+  SendPushEvent(const Maybe<nsTArray<uint8_t>>& aData,
+                ServiceWorkerRegistrationInfo* aRegistration);
 
   nsresult
   SendPushSubscriptionChangeEvent();
 
   nsresult
   SendNotificationClickEvent(const nsAString& aID,
                              const nsAString& aTitle,
                              const nsAString& aDir,
--- a/dom/workers/ServiceWorkerScriptCache.cpp
+++ b/dom/workers/ServiceWorkerScriptCache.cpp
@@ -8,16 +8,17 @@
 #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/ipc/BackgroundUtils.h"
 #include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "nsICacheInfoChannel.h"
 #include "nsIHttpChannelInternal.h"
 #include "nsIStreamLoader.h"
 #include "nsIThreadRetargetableRequest.h"
 
 #include "nsIPrincipal.h"
 #include "nsNetUtil.h"
 #include "nsScriptLoader.h"
 #include "Workers.h"
@@ -88,82 +89,17 @@ public:
   explicit CompareNetwork(CompareManager* aManager)
     : mManager(aManager)
   {
     MOZ_ASSERT(aManager);
     AssertIsOnMainThread();
   }
 
   nsresult
-  Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL, nsILoadGroup* aLoadGroup)
-  {
-    MOZ_ASSERT(aPrincipal);
-    AssertIsOnMainThread();
-
-    nsCOMPtr<nsIURI> uri;
-    nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr, nullptr);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    nsCOMPtr<nsILoadGroup> loadGroup;
-    rv = NS_NewLoadGroup(getter_AddRefs(loadGroup), aPrincipal);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    // 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,
-                       nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED,
-                       nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER,
-                       loadGroup,
-                       nullptr, // aCallbacks
-                       nsIChannel::LOAD_BYPASS_SERVICE_WORKER);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    nsLoadFlags flags;
-    rv = mChannel->GetLoadFlags(&flags);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    flags |= nsIRequest::LOAD_BYPASS_CACHE;
-    rv = mChannel->SetLoadFlags(flags);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
-    if (httpChannel) {
-      // Spec says no redirects allowed for SW scripts.
-      httpChannel->SetRedirectionLimit(0);
-
-      httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Service-Worker"),
-                                    NS_LITERAL_CSTRING("script"),
-                                    /* merge */ false);
-    }
-
-    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;
-    }
-
-    return NS_OK;
-  }
+  Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL, nsILoadGroup* aLoadGroup);
 
   void
   Abort()
   {
     AssertIsOnMainThread();
 
     MOZ_ASSERT(mChannel);
     mChannel->Cancel(NS_BINDING_ABORTED);
@@ -291,24 +227,27 @@ private:
 
 NS_IMPL_ISUPPORTS(CompareCache, nsIStreamLoaderObserver)
 
 class CompareManager final : public PromiseNativeHandler
 {
 public:
   NS_DECL_ISUPPORTS
 
-  explicit CompareManager(CompareCallback* aCallback)
-    : mCallback(aCallback)
+  explicit CompareManager(ServiceWorkerRegistrationInfo* aRegistration,
+                          CompareCallback* aCallback)
+    : mRegistration(aRegistration)
+    , mCallback(aCallback)
     , mState(WaitingForOpen)
     , mNetworkFinished(false)
     , mCacheFinished(false)
     , mInCache(false)
   {
     AssertIsOnMainThread();
+    MOZ_ASSERT(aRegistration);
   }
 
   nsresult
   Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL,
              const nsAString& aCacheName, nsILoadGroup* aLoadGroup)
   {
     AssertIsOnMainThread();
     MOZ_ASSERT(aPrincipal);
@@ -354,16 +293,23 @@ public:
 
   void
   SetMaxScope(const nsACString& aMaxScope)
   {
     MOZ_ASSERT(!mNetworkFinished);
     mMaxScope = aMaxScope;
   }
 
+  already_AddRefed<ServiceWorkerRegistrationInfo>
+  GetRegistration()
+  {
+    RefPtr<ServiceWorkerRegistrationInfo> copy = mRegistration.get();
+    return copy.forget();
+  }
+
   void
   NetworkFinished(nsresult aStatus)
   {
     AssertIsOnMainThread();
 
     mNetworkFinished = true;
 
     if (NS_WARN_IF(NS_FAILED(aStatus))) {
@@ -616,16 +562,17 @@ private:
       Fail(result.StealNSResult());
       return;
     }
 
     mState = WaitingForPut;
     cachePromise->AppendNativeHandler(this);
   }
 
+  RefPtr<ServiceWorkerRegistrationInfo> mRegistration;
   RefPtr<CompareCallback> mCallback;
   JS::PersistentRooted<JSObject*> mSandbox;
   RefPtr<CacheStorage> mCacheStorage;
 
   RefPtr<CompareNetwork> mCN;
   RefPtr<CompareCache> mCC;
 
   nsString mURL;
@@ -645,16 +592,79 @@ private:
 
   bool mNetworkFinished;
   bool mCacheFinished;
   bool mInCache;
 };
 
 NS_IMPL_ISUPPORTS0(CompareManager)
 
+nsresult
+CompareNetwork::Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL, nsILoadGroup* aLoadGroup)
+{
+  MOZ_ASSERT(aPrincipal);
+  AssertIsOnMainThread();
+
+  nsCOMPtr<nsIURI> uri;
+  nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr, nullptr);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsCOMPtr<nsILoadGroup> loadGroup;
+  rv = NS_NewLoadGroup(getter_AddRefs(loadGroup), aPrincipal);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsLoadFlags flags = nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
+  RefPtr<ServiceWorkerRegistrationInfo> registration =
+    mManager->GetRegistration();
+  if (registration->IsLastUpdateCheckTimeOverOneDay()) {
+    flags |= nsIRequest::LOAD_BYPASS_CACHE;
+  }
+
+  // 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,
+                     nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED,
+                     nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER,
+                     loadGroup,
+                     nullptr, // aCallbacks
+                     flags);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
+  if (httpChannel) {
+    // Spec says no redirects allowed for SW scripts.
+    httpChannel->SetRedirectionLimit(0);
+
+    httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Service-Worker"),
+                                  NS_LITERAL_CSTRING("script"),
+                                  /* merge */ false);
+  }
+
+  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;
+  }
+
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 CompareNetwork::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
 {
   AssertIsOnMainThread();
 
   // If no channel, Abort() has been called.
   if (!mChannel) {
     return NS_OK;
@@ -727,16 +737,30 @@ CompareNetwork::OnStreamComplete(nsIStre
     nsAutoCString maxScope;
     // 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"),
                                              maxScope);
 
     mManager->SetMaxScope(maxScope);
 
+    bool isFromCache = false;
+    nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(httpChannel));
+    if (cacheChannel) {
+      cacheChannel->IsFromCache(&isFromCache);
+    }
+
+    // [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 (!isFromCache) {
+      RefPtr<ServiceWorkerRegistrationInfo> registration =
+        mManager->GetRegistration();
+      registration->RefreshLastUpdateCheckTime();
+    }
+
     nsAutoCString mimeType;
     rv = httpChannel->GetContentType(mimeType);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       mManager->NetworkFinished(NS_ERROR_DOM_SECURITY_ERR);
       return rv;
     }
 
     if (!mimeType.LowerCaseEqualsLiteral("text/javascript") &&
@@ -767,17 +791,17 @@ CompareNetwork::OnStreamComplete(nsIStre
     rv = uri->GetScheme(scheme);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       mManager->NetworkFinished(rv);
       return NS_OK;
     }
 
     if (NS_WARN_IF(!scheme.LowerCaseEqualsLiteral("app"))) {
       mManager->NetworkFinished(NS_ERROR_FAILURE);
-      return NS_OK;      
+      return NS_OK;
     }
   }
 
   char16_t* buffer = nullptr;
   size_t len = 0;
 
   rv = nsScriptLoader::ConvertToUTF16(httpChannel, aString, aLen,
                                       NS_LITERAL_STRING("UTF-8"), nullptr,
@@ -1013,26 +1037,28 @@ GenerateCacheName(nsAString& aName)
   char chars[NSID_LENGTH];
   id.ToProvidedString(chars);
   aName.AssignASCII(chars, NSID_LENGTH);
 
   return NS_OK;
 }
 
 nsresult
-Compare(nsIPrincipal* aPrincipal, const nsAString& aCacheName,
+Compare(ServiceWorkerRegistrationInfo* aRegistration,
+        nsIPrincipal* aPrincipal, const nsAString& aCacheName,
         const nsAString& aURL, CompareCallback* aCallback,
         nsILoadGroup* aLoadGroup)
 {
   AssertIsOnMainThread();
+  MOZ_ASSERT(aRegistration);
   MOZ_ASSERT(aPrincipal);
   MOZ_ASSERT(!aURL.IsEmpty());
   MOZ_ASSERT(aCallback);
 
-  RefPtr<CompareManager> cm = new CompareManager(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;
 }
--- a/dom/workers/ServiceWorkerScriptCache.h
+++ b/dom/workers/ServiceWorkerScriptCache.h
@@ -39,17 +39,18 @@ public:
                    const nsAString& aNewCacheName,
                    const nsACString& aMaxScope) = 0;
 
   NS_IMETHOD_(MozExternalRefCountType) AddRef() = 0;
   NS_IMETHOD_(MozExternalRefCountType) Release() = 0;
 };
 
 nsresult
-Compare(nsIPrincipal* aPrincipal, const nsAString& aCacheName,
+Compare(ServiceWorkerRegistrationInfo* aRegistration,
+        nsIPrincipal* aPrincipal, const nsAString& aCacheName,
         const nsAString& aURL, CompareCallback* aCallback, nsILoadGroup* aLoadGroup);
 
 } // namespace serviceWorkerScriptCache
 
 } // namespace workers
 } // namespace dom
 } // namespace mozilla