Bug 1472307 - Implement delay update mechanism in ServiceWorkerRegistrationProxy. r=asuth
authorEden Chuang <echuang@mozilla.com>
Thu, 14 Mar 2019 10:19:13 +0000
changeset 521868 e9e31edcc25a93b88e667ffa8d182d8c44a00310
parent 521867 2c24099776a7c46fa4e764984901e386f5d83fd5
child 521869 5da37483c3e2278f7adca24137f33c8c49fc6e63
push id10870
push usernbeleuzu@mozilla.com
push dateFri, 15 Mar 2019 20:00:07 +0000
treeherdermozilla-beta@c594aee5b7a4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersasuth
bugs1472307
milestone67.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 1472307 - Implement delay update mechanism in ServiceWorkerRegistrationProxy. r=asuth 1. Add a new private member class DelayedUpdateCallback in ServiceWorkerRegistrationProxy for handling the deley time up. 2. Modify the lambda function in ServiceWorkerRegistrationProxy::Update(). Get the update delay time at first. If the delay time does not equal zero. create a timer and a timer callback(DelayedUpdateCallback) to perform the delay update. Otherwise execute update directly. Differential Revision: https://phabricator.services.mozilla.com/D17358
dom/serviceworkers/ServiceWorkerRegistrationProxy.cpp
dom/serviceworkers/ServiceWorkerRegistrationProxy.h
dom/serviceworkers/test/test_self_update_worker.html
--- a/dom/serviceworkers/ServiceWorkerRegistrationProxy.cpp
+++ b/dom/serviceworkers/ServiceWorkerRegistrationProxy.cpp
@@ -11,16 +11,34 @@
 #include "ServiceWorkerRegistrationParent.h"
 #include "ServiceWorkerUnregisterCallback.h"
 
 namespace mozilla {
 namespace dom {
 
 using mozilla::ipc::AssertIsOnBackgroundThread;
 
+class ServiceWorkerRegistrationProxy::DelayedUpdate final
+    : public nsITimerCallback {
+    RefPtr<ServiceWorkerRegistrationProxy> mProxy;
+    RefPtr<ServiceWorkerRegistrationPromise::Private> mPromise;
+    nsCOMPtr<nsITimer> mTimer;
+
+    ~DelayedUpdate() = default;
+  public:
+    NS_DECL_THREADSAFE_ISUPPORTS
+    NS_DECL_NSITIMERCALLBACK
+
+    DelayedUpdate(RefPtr<ServiceWorkerRegistrationProxy>&& aProxy,
+        RefPtr<ServiceWorkerRegistrationPromise::Private>&& aPromise,
+        uint32_t delay);
+    void Reject();
+};
+
+
 ServiceWorkerRegistrationProxy::~ServiceWorkerRegistrationProxy() {
   // Any thread
   MOZ_DIAGNOSTIC_ASSERT(!mActor);
   MOZ_DIAGNOSTIC_ASSERT(!mReg);
 }
 
 void ServiceWorkerRegistrationProxy::MaybeShutdownOnBGThread() {
   AssertIsOnBackgroundThread();
@@ -65,16 +83,20 @@ void ServiceWorkerRegistrationProxy::Ini
       "ServiceWorkerRegistrationProxy::mInfo", reg);
 
   mReg->AddInstance(this, mDescriptor);
 }
 
 void ServiceWorkerRegistrationProxy::MaybeShutdownOnMainThread() {
   AssertIsOnMainThread();
 
+  if (mDelayedUpdate) {
+    mDelayedUpdate->Reject();
+    mDelayedUpdate = nullptr;
+  }
   nsCOMPtr<nsIRunnable> r = NewRunnableMethod(
       __func__, this, &ServiceWorkerRegistrationProxy::MaybeShutdownOnBGThread);
 
   MOZ_ALWAYS_SUCCEEDS(mEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL));
 }
 
 void ServiceWorkerRegistrationProxy::StopListeningOnMainThread() {
   AssertIsOnMainThread();
@@ -219,37 +241,99 @@ class UpdateCallback final : public Serv
 
   void UpdateFailed(ErrorResult& aResult) override {
     mPromise->Reject(CopyableErrorResult(aResult), __func__);
   }
 };
 
 }  // anonymous namespace
 
+NS_IMPL_ISUPPORTS(ServiceWorkerRegistrationProxy::DelayedUpdate,
+                  nsITimerCallback)
+
+ServiceWorkerRegistrationProxy::DelayedUpdate::DelayedUpdate(
+    RefPtr<ServiceWorkerRegistrationProxy >&& aProxy,
+    RefPtr<ServiceWorkerRegistrationPromise::Private >&& aPromise,
+    uint32_t delay)
+  : mProxy(std::move(aProxy))
+  , mPromise(std::move(aPromise))
+{
+  MOZ_DIAGNOSTIC_ASSERT(mProxy);
+  MOZ_DIAGNOSTIC_ASSERT(mPromise);
+  mProxy->mDelayedUpdate = this;
+  Result<nsCOMPtr<nsITimer>, nsresult> result = NS_NewTimerWithCallback(
+      this, delay, nsITimer::TYPE_ONE_SHOT,
+      SystemGroup::EventTargetFor(TaskCategory::Other));
+  mTimer = result.unwrapOr(nullptr);
+  MOZ_DIAGNOSTIC_ASSERT(mTimer);
+}
+
+void
+ServiceWorkerRegistrationProxy::DelayedUpdate::Reject()
+{
+  MOZ_DIAGNOSTIC_ASSERT(mPromise);
+  if (mTimer) {
+    mTimer->Cancel();
+    mTimer = nullptr;
+  }
+  mPromise->Reject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__);
+}
+
+NS_IMETHODIMP
+ServiceWorkerRegistrationProxy::DelayedUpdate::Notify(nsITimer* aTimer)
+{
+  auto scopeExit = MakeScopeExit(
+      [&] { mPromise->Reject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__); });
+  MOZ_DIAGNOSTIC_ASSERT((mProxy->mDelayedUpdate == this));
+
+  NS_ENSURE_TRUE(mProxy->mReg, NS_ERROR_FAILURE);
+
+  RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+  NS_ENSURE_TRUE(swm, NS_ERROR_FAILURE);
+
+  RefPtr<UpdateCallback> cb = new UpdateCallback(std::move(mPromise));
+  swm->Update(mProxy->mReg->Principal(), mProxy->mReg->Scope(), cb);
+
+  mTimer = nullptr;
+  mProxy->mDelayedUpdate = nullptr;
+
+  scopeExit.release();
+  return NS_OK;
+}
+
 RefPtr<ServiceWorkerRegistrationPromise>
 ServiceWorkerRegistrationProxy::Update() {
   AssertIsOnBackgroundThread();
 
   RefPtr<ServiceWorkerRegistrationProxy> self = this;
   RefPtr<ServiceWorkerRegistrationPromise::Private> promise =
       new ServiceWorkerRegistrationPromise::Private(__func__);
 
   nsCOMPtr<nsIRunnable> r =
       NS_NewRunnableFunction(__func__, [self, promise]() mutable {
         auto scopeExit = MakeScopeExit(
             [&] { promise->Reject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__); });
 
+        // Get the delay value for the update
         NS_ENSURE_TRUE_VOID(self->mReg);
+        uint32_t delay = self->mReg->GetUpdateDelay();
 
-        RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
-        NS_ENSURE_TRUE_VOID(swm);
+        // If the delay value does not equal to 0, create a timer and a timer
+        // callback to perform the delayed update. Otherwise, update directly.
+        if (delay) {
+          RefPtr<ServiceWorkerRegistrationProxy::DelayedUpdate> du =
+              new ServiceWorkerRegistrationProxy::DelayedUpdate(
+                  std::move(self), std::move(promise), delay);
+        } else {
+          RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+          NS_ENSURE_TRUE_VOID(swm);
 
-        RefPtr<UpdateCallback> cb = new UpdateCallback(std::move(promise));
-        swm->Update(self->mReg->Principal(), self->mReg->Scope(), cb);
-
+          RefPtr<UpdateCallback> cb = new UpdateCallback(std::move(promise));
+          swm->Update(self->mReg->Principal(), self->mReg->Scope(), cb);
+        }
         scopeExit.release();
       });
 
   MOZ_ALWAYS_SUCCEEDS(SystemGroup::Dispatch(TaskCategory::Other, r.forget()));
 
   return promise;
 }
 
--- a/dom/serviceworkers/ServiceWorkerRegistrationProxy.h
+++ b/dom/serviceworkers/ServiceWorkerRegistrationProxy.h
@@ -43,16 +43,20 @@ class ServiceWorkerRegistrationProxy fin
 
   // Main thread methods
   void InitOnMainThread();
 
   void MaybeShutdownOnMainThread();
 
   void StopListeningOnMainThread();
 
+  // The timer callback to perform the delayed update
+  class DelayedUpdate;
+  RefPtr<DelayedUpdate> mDelayedUpdate;
+
   // ServiceWorkerRegistrationListener interface
   void UpdateState(
       const ServiceWorkerRegistrationDescriptor& aDescriptor) override;
 
   void FireUpdateFound() override;
 
   void RegistrationRemoved() override;
 
--- a/dom/serviceworkers/test/test_self_update_worker.html
+++ b/dom/serviceworkers/test/test_self_update_worker.html
@@ -84,11 +84,54 @@ add_task(async function test_update() {
     await activateDummyWorker();
   }
 
 
   await registration.unregister();
   await SpecialPowers.popPrefEnv();
   await SpecialPowers.popPrefEnv();
 });
+
+// Test variant to ensure that we properly keep the timer alive by having a
+// non-zero but small timer duration. In this case, the delay is simply our
+// exponential growth rate of 30, so if we end up getting to version 4, that's
+// okay and the test may need to be updated.
+add_task(async function test_delay_update() {
+  let version;
+  navigator.serviceWorker.onmessage = function(event) {
+    ok (event.data.version <= 3, "Service worker updated too many times." + event.data.version);
+    version = event.data.version;
+  }
+
+  await SpecialPowers.pushPrefEnv({"set": [
+    ["dom.serviceWorkers.update_delay", 1],
+    ["dom.serviceWorkers.idle_extended_timeout", 299999]]});
+
+  // clear version counter
+  await fetch("self_update_worker.sjs?clearcounter");
+
+  var worker;
+  let registration = await navigator.serviceWorker.register(
+    "self_update_worker.sjs",
+    { scope: "./test_self_update_worker.html?random=" + Date.now()})
+    .then(function(registration) {
+      worker = registration.installing;
+      // We can't wait for 'activated' here, since it's possible for
+      // the update process to kill the worker before it activates.
+      // See: https://github.com/w3c/ServiceWorker/issues/1285
+      return waitForState(worker, 'activating', registration);
+    });
+
+  // We need to wait a reasonable time to give the self updating worker a chance
+  // to change to a newer version. Register and activate an empty worker 5 times.
+  for (i = 0; i < 5; i++) {
+    await activateDummyWorker();
+  }
+
+  is(version, 3, "Service worker version should be 3.");
+
+  await registration.unregister();
+  await SpecialPowers.popPrefEnv();
+  await SpecialPowers.popPrefEnv();
+});
 </script>
 </body>
 </html>