Bug 984048 - Patch 5 - ServiceWorker [[Install]] algorithm. r=ehsan, khuey
authorNikhil Marathe <nsm.nikhil@gmail.com>
Wed, 02 Jul 2014 17:48:50 -0700
changeset 212854 9d000928d525054a7f778a29213eb9482768056e
parent 212853 083d3ccb9b434b5804eb456467fd0a3e01bff895
child 212855 d402b14b4ad8f28274c906e2471cfe637d0c28c4
push id3857
push userraliiev@mozilla.com
push dateTue, 02 Sep 2014 16:39:23 +0000
treeherdermozilla-beta@5638b907b505 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan, khuey
bugs984048
milestone33.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 984048 - Patch 5 - ServiceWorker [[Install]] algorithm. r=ehsan, khuey
dom/workers/RuntimeService.cpp
dom/workers/RuntimeService.h
dom/workers/ServiceWorkerManager.cpp
dom/workers/ServiceWorkerManager.h
dom/workers/test/serviceworkers/install_event_worker.js
dom/workers/test/serviceworkers/mochitest.ini
dom/workers/test/serviceworkers/test_install_event.html
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -2157,16 +2157,43 @@ RuntimeService::CreateServiceWorker(cons
   serviceWorker->mURL = aScriptURL;
   serviceWorker->mScope = NS_ConvertUTF8toUTF16(aScope);
 
   serviceWorker.forget(aServiceWorker);
   return rv;
 }
 
 nsresult
+RuntimeService::CreateServiceWorkerFromLoadInfo(JSContext* aCx,
+                                               WorkerPrivate::LoadInfo aLoadInfo,
+                                               const nsAString& aScriptURL,
+                                               const nsACString& aScope,
+                                               ServiceWorker** aServiceWorker)
+{
+
+  nsRefPtr<SharedWorker> sharedWorker;
+  nsresult rv = CreateSharedWorkerFromLoadInfo(aCx, aLoadInfo, aScriptURL, aScope,
+                                               WorkerTypeService,
+                                               getter_AddRefs(sharedWorker));
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsRefPtr<ServiceWorker> serviceWorker =
+    new ServiceWorker(nullptr, sharedWorker);
+
+  serviceWorker->mURL = aScriptURL;
+  serviceWorker->mScope = NS_ConvertUTF8toUTF16(aScope);
+
+  serviceWorker.forget(aServiceWorker);
+  return rv;
+}
+
+nsresult
 RuntimeService::CreateSharedWorkerInternal(const GlobalObject& aGlobal,
                                            const nsAString& aScriptURL,
                                            const nsACString& aName,
                                            WorkerType aType,
                                            SharedWorker** aSharedWorker)
 {
   AssertIsOnMainThread();
   MOZ_ASSERT(aType == WorkerTypeShared || aType == WorkerTypeService);
@@ -2176,53 +2203,72 @@ RuntimeService::CreateSharedWorkerIntern
 
   JSContext* cx = aGlobal.Context();
 
   WorkerPrivate::LoadInfo loadInfo;
   nsresult rv = WorkerPrivate::GetLoadInfo(cx, window, nullptr, aScriptURL,
                                            false, &loadInfo);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  MOZ_ASSERT(loadInfo.mResolvedScriptURI);
-
-  nsCString scriptSpec;
-  rv = loadInfo.mResolvedScriptURI->GetSpec(scriptSpec);
-  NS_ENSURE_SUCCESS(rv, rv);
+  return CreateSharedWorkerFromLoadInfo(cx, loadInfo, aScriptURL, aName, aType,
+                                        aSharedWorker);
+}
+
+nsresult
+RuntimeService::CreateSharedWorkerFromLoadInfo(JSContext* aCx,
+                                               WorkerPrivate::LoadInfo aLoadInfo,
+                                               const nsAString& aScriptURL,
+                                               const nsACString& aName,
+                                               WorkerType aType,
+                                               SharedWorker** aSharedWorker)
+{
+  AssertIsOnMainThread();
+
+  MOZ_ASSERT(aLoadInfo.mResolvedScriptURI);
 
   nsRefPtr<WorkerPrivate> workerPrivate;
   {
     MutexAutoLock lock(mMutex);
 
     WorkerDomainInfo* domainInfo;
     SharedWorkerInfo* sharedWorkerInfo;
 
+    nsCString scriptSpec;
+    nsresult rv = aLoadInfo.mResolvedScriptURI->GetSpec(scriptSpec);
+    NS_ENSURE_SUCCESS(rv, rv);
+
     nsAutoCString key;
     GenerateSharedWorkerKey(scriptSpec, aName, key);
 
-    if (mDomainMap.Get(loadInfo.mDomain, &domainInfo) &&
+    if (mDomainMap.Get(aLoadInfo.mDomain, &domainInfo) &&
         domainInfo->mSharedWorkerInfos.Get(key, &sharedWorkerInfo)) {
       workerPrivate = sharedWorkerInfo->mWorkerPrivate;
     }
   }
 
+  // Keep a reference to the window before spawning the worker. If the worker is
+  // a Shared/Service worker and the worker script loads and executes before
+  // the SharedWorker object itself is created before then WorkerScriptLoaded()
+  // will reset the loadInfo's window.
+  nsCOMPtr<nsPIDOMWindow> window = aLoadInfo.mWindow;
+
   bool created = false;
-
   if (!workerPrivate) {
     ErrorResult rv;
     workerPrivate =
-      WorkerPrivate::Constructor(aGlobal, aScriptURL, false,
-                                 aType, aName, &loadInfo, rv);
+      WorkerPrivate::Constructor(aCx, aScriptURL, false,
+                                 aType, aName, &aLoadInfo, rv);
     NS_ENSURE_TRUE(workerPrivate, rv.ErrorCode());
 
     created = true;
   }
 
   nsRefPtr<SharedWorker> sharedWorker = new SharedWorker(window, workerPrivate);
 
-  if (!workerPrivate->RegisterSharedWorker(cx, sharedWorker)) {
+  if (!workerPrivate->RegisterSharedWorker(aCx, sharedWorker)) {
     NS_WARNING("Worker is unreachable, this shouldn't happen!");
     sharedWorker->Close();
     return NS_ERROR_FAILURE;
   }
 
   // This is normally handled in RegisterWorker, but that wasn't called if the
   // worker already existed.
   if (!created) {
--- a/dom/workers/RuntimeService.h
+++ b/dom/workers/RuntimeService.h
@@ -12,16 +12,17 @@
 
 #include "nsIObserver.h"
 
 #include "mozilla/TimeStamp.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "nsClassHashtable.h"
 #include "nsHashKeys.h"
 #include "nsTArray.h"
+#include "WorkerPrivate.h"
 
 class nsIRunnable;
 class nsIThread;
 class nsITimer;
 class nsPIDOMWindow;
 
 BEGIN_WORKERS_NAMESPACE
 
@@ -153,16 +154,23 @@ public:
   }
 
   nsresult
   CreateServiceWorker(const GlobalObject& aGlobal,
                       const nsAString& aScriptURL,
                       const nsACString& aScope,
                       ServiceWorker** aServiceWorker);
 
+  nsresult
+  CreateServiceWorkerFromLoadInfo(JSContext* aCx,
+                                  WorkerPrivate::LoadInfo aLoadInfo,
+                                  const nsAString& aScriptURL,
+                                  const nsACString& aScope,
+                                  ServiceWorker** aServiceWorker);
+
   void
   ForgetSharedWorker(WorkerPrivate* aWorkerPrivate);
 
   const NavigatorProperties&
   GetNavigatorProperties() const
   {
     return mNavigatorProperties;
   }
@@ -291,13 +299,21 @@ private:
   JSVersionChanged(const char* aPrefName, void* aClosure);
 
   nsresult
   CreateSharedWorkerInternal(const GlobalObject& aGlobal,
                              const nsAString& aScriptURL,
                              const nsACString& aName,
                              WorkerType aType,
                              SharedWorker** aSharedWorker);
+
+  nsresult
+  CreateSharedWorkerFromLoadInfo(JSContext* aCx,
+                                 WorkerPrivate::LoadInfo aLoadInfo,
+                                 const nsAString& aScriptURL,
+                                 const nsACString& aName,
+                                 WorkerType aType,
+                                 SharedWorker** aSharedWorker);
 };
 
 END_WORKERS_NAMESPACE
 
 #endif /* mozilla_dom_workers_runtimeservice_h__ */
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -1,35 +1,40 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ServiceWorkerManager.h"
 
 #include "nsIDocument.h"
+#include "nsIScriptSecurityManager.h"
 #include "nsPIDOMWindow.h"
 
 #include "jsapi.h"
 
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/DOMError.h"
 #include "mozilla/dom/ErrorEvent.h"
+#include "mozilla/dom/InstallEventBinding.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
 
 #include "nsContentUtils.h"
 #include "nsCxPusher.h"
 #include "nsNetUtil.h"
 #include "nsProxyRelease.h"
 #include "nsTArray.h"
 
 #include "RuntimeService.h"
 #include "ServiceWorker.h"
+#include "ServiceWorkerEvents.h"
 #include "WorkerInlines.h"
 #include "WorkerPrivate.h"
 #include "WorkerRunnable.h"
+#include "WorkerScope.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 BEGIN_WORKERS_NAMESPACE
 
 NS_IMPL_ISUPPORTS0(ServiceWorkerRegistration)
 
@@ -703,21 +708,253 @@ ServiceWorkerManager::HandleError(JSCont
     RejectUpdatePromiseObservers(registration, init);
     // We don't need to abort here since the worker has already run.
     registration->mUpdateInstance = nullptr;
   } else {
     // FIXME(nsm): Bug 983497 Fire 'error' on ServiceWorkerContainers.
   }
 }
 
+class FinishInstallRunnable MOZ_FINAL : public nsRunnable
+{
+  nsMainThreadPtrHandle<ServiceWorkerRegistration> mRegistration;
+
+public:
+  explicit FinishInstallRunnable(
+    const nsMainThreadPtrHandle<ServiceWorkerRegistration>& aRegistration)
+    : mRegistration(aRegistration)
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+  }
+
+  NS_IMETHOD
+  Run() MOZ_OVERRIDE
+  {
+    AssertIsOnMainThread();
+
+    nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+    swm->FinishInstall(mRegistration.get());
+    return NS_OK;
+  }
+};
+
+class CancelServiceWorkerInstallationRunnable MOZ_FINAL : public nsRunnable
+{
+  nsMainThreadPtrHandle<ServiceWorkerRegistration> mRegistration;
+
+public:
+  explicit CancelServiceWorkerInstallationRunnable(
+    const nsMainThreadPtrHandle<ServiceWorkerRegistration>& aRegistration)
+    : mRegistration(aRegistration)
+  {
+  }
+
+  NS_IMETHOD
+  Run() MOZ_OVERRIDE
+  {
+    AssertIsOnMainThread();
+    // FIXME(nsm): Change installing worker state to redundant.
+    // FIXME(nsm): Fire statechange.
+    mRegistration->mInstallingWorker.Invalidate();
+    return NS_OK;
+  }
+};
+
+/*
+ * Used to handle InstallEvent::waitUntil() and proceed with installation.
+ */
+class FinishInstallHandler MOZ_FINAL : public PromiseNativeHandler
+{
+  nsMainThreadPtrHandle<ServiceWorkerRegistration> mRegistration;
+
+  virtual
+  ~FinishInstallHandler()
+  { }
+
+public:
+  explicit FinishInstallHandler(
+    const nsMainThreadPtrHandle<ServiceWorkerRegistration>& aRegistration)
+    : mRegistration(aRegistration)
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+  }
+
+  void
+  ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) MOZ_OVERRIDE
+  {
+    WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+    MOZ_ASSERT(workerPrivate);
+    workerPrivate->AssertIsOnWorkerThread();
+
+    nsRefPtr<FinishInstallRunnable> r = new FinishInstallRunnable(mRegistration);
+    NS_DispatchToMainThread(r);
+  }
+
+  void
+  RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) MOZ_OVERRIDE
+  {
+    nsRefPtr<CancelServiceWorkerInstallationRunnable> r =
+      new CancelServiceWorkerInstallationRunnable(mRegistration);
+    NS_DispatchToMainThread(r);
+  }
+};
+
+/*
+ * 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 InstallEventRunnable MOZ_FINAL : public WorkerRunnable
+{
+  nsMainThreadPtrHandle<ServiceWorkerRegistration> mRegistration;
+  nsCString mScope;
+
+public:
+  InstallEventRunnable(
+    WorkerPrivate* aWorkerPrivate,
+    const nsMainThreadPtrHandle<ServiceWorkerRegistration>& aRegistration)
+      : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount),
+        mRegistration(aRegistration),
+        mScope(aRegistration.get()->mScope) // copied for access on worker thread.
+  {
+    AssertIsOnMainThread();
+    MOZ_ASSERT(aWorkerPrivate);
+  }
+
+  bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+  {
+    MOZ_ASSERT(aWorkerPrivate);
+    return DispatchInstallEvent(aCx, aWorkerPrivate);
+  }
+
+private:
+  bool
+  DispatchInstallEvent(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+  {
+    aWorkerPrivate->AssertIsOnWorkerThread();
+    MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
+    InstallEventInit init;
+    init.mBubbles = false;
+    init.mCancelable = true;
+
+    // FIXME(nsm): Bug 982787 pass previous active worker.
+
+    nsRefPtr<EventTarget> target = aWorkerPrivate->GlobalScope();
+    nsRefPtr<InstallEvent> event =
+      InstallEvent::Constructor(target, NS_LITERAL_STRING("install"), init);
+
+    event->SetTrusted(true);
+
+    nsRefPtr<Promise> waitUntilPromise;
+
+    nsresult rv = target->DispatchDOMEvent(nullptr, event, nullptr, nullptr);
+
+    nsCOMPtr<nsIGlobalObject> sgo = aWorkerPrivate->GlobalScope();
+    if (NS_SUCCEEDED(rv)) {
+      waitUntilPromise = event->GetPromise();
+      if (!waitUntilPromise) {
+        ErrorResult rv;
+        waitUntilPromise =
+          Promise::Resolve(sgo,
+                           aCx, JS::UndefinedHandleValue, rv);
+      }
+    } else {
+      ErrorResult rv;
+      // Continue with a canceled install.
+      waitUntilPromise = Promise::Reject(sgo, aCx,
+                                         JS::UndefinedHandleValue, rv);
+    }
+
+    nsRefPtr<FinishInstallHandler> handler =
+      new FinishInstallHandler(mRegistration);
+    waitUntilPromise->AppendNativeHandler(handler);
+    return true;
+  }
+};
+
 void
 ServiceWorkerManager::Install(ServiceWorkerRegistration* aRegistration,
                               ServiceWorkerInfo aServiceWorkerInfo)
 {
-  // FIXME(nsm): Same bug, different patch.
+  AssertIsOnMainThread();
+  aRegistration->mInstallingWorker = aServiceWorkerInfo;
+
+  nsMainThreadPtrHandle<ServiceWorkerRegistration> handle =
+    new nsMainThreadPtrHolder<ServiceWorkerRegistration>(aRegistration);
+
+  nsRefPtr<ServiceWorker> serviceWorker;
+  nsresult rv =
+    CreateServiceWorker(aServiceWorkerInfo.GetScriptSpec(),
+                        aRegistration->mScope,
+                        getter_AddRefs(serviceWorker));
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRegistration->mInstallingWorker.Invalidate();
+    return;
+  }
+
+  nsRefPtr<InstallEventRunnable> r =
+    new InstallEventRunnable(serviceWorker->GetWorkerPrivate(), handle);
+
+  AutoSafeJSContext cx;
+  r->Dispatch(cx);
+
+  // When this function exits, although we've lost references to the ServiceWorker,
+  // which means the underlying WorkerPrivate has no references, the worker
+  // will stay alive due to the modified busy count until the install event has
+  // been dispatched.
+  // NOTE: The worker spec does not require Promises to keep a worker alive, so
+  // the waitUntil() construct by itself will not keep a worker alive beyond
+  // the event dispatch. On the other hand, networking, IDB and so on do keep
+  // the worker alive, so the waitUntil() is only relevant if the Promise is
+  // gated on those actions. I (nsm) am not sure if it is worth requiring
+  // a special spec mention saying the install event should keep the worker
+  // alive indefinitely purely on the basis of calling waitUntil(), since
+  // a wait is likely to be required only when performing networking or storage
+  // transactions in the first place.
+
+  // FIXME(nsm): Bug 983497. Fire "updatefound" on ServiceWorkerContainers.
+}
+
+class ActivationRunnable : public nsRunnable
+{
+public:
+  explicit ActivationRunnable(ServiceWorkerRegistration* aRegistration)
+  { }
+};
+
+void
+ServiceWorkerManager::FinishInstall(ServiceWorkerRegistration* aRegistration)
+{
+  AssertIsOnMainThread();
+
+  if (aRegistration->mWaitingWorker.IsValid()) {
+    // FIXME(nsm): Actually update the state of active ServiceWorker instances.
+  }
+
+  aRegistration->mWaitingWorker = aRegistration->mInstallingWorker;
+  aRegistration->mInstallingWorker.Invalidate();
+
+  // FIXME(nsm): Actually update state of active ServiceWorker instances to
+  // installed.
+  // FIXME(nsm): Fire statechange on the instances.
+
+  // FIXME(nsm): Handle replace().
+
+  // FIXME(nsm): Check that no document is using the registration!
+
+  nsRefPtr<ActivationRunnable> r =
+    new ActivationRunnable(aRegistration);
+
+  nsresult rv = NS_DispatchToMainThread(r);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    // FIXME(nsm): Handle error.
+  }
 }
 
 NS_IMETHODIMP
 ServiceWorkerManager::CreateServiceWorkerForWindow(nsPIDOMWindow* aWindow,
                                                    const nsACString& aScriptSpec,
                                                    const nsACString& aScope,
                                                    ServiceWorker** aServiceWorker)
 {
@@ -742,9 +979,57 @@ ServiceWorkerManager::CreateServiceWorke
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   serviceWorker.forget(aServiceWorker);
   return rv;
 }
 
+NS_IMETHODIMP
+ServiceWorkerManager::CreateServiceWorker(const nsACString& aScriptSpec,
+                                          const nsACString& aScope,
+                                          ServiceWorker** aServiceWorker)
+{
+  AssertIsOnMainThread();
+
+  WorkerPrivate::LoadInfo info;
+  nsresult rv = NS_NewURI(getter_AddRefs(info.mBaseURI), aScriptSpec, nullptr, nullptr);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  info.mResolvedScriptURI = info.mBaseURI;
+
+  rv = info.mBaseURI->GetHost(info.mDomain);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // FIXME(nsm): Create correct principal based on app-ness.
+  // Would it make sense to store the nsIPrincipal of the first register() in
+  // the ServiceWorkerRegistration and use that?
+  nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+  rv = ssm->GetNoAppCodebasePrincipal(info.mBaseURI, getter_AddRefs(info.mPrincipal));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  AutoSafeJSContext cx;
+
+  nsRefPtr<ServiceWorker> serviceWorker;
+  RuntimeService* rs = RuntimeService::GetService();
+  if (!rs) {
+    return NS_ERROR_FAILURE;
+  }
+
+  rv = rs->CreateServiceWorkerFromLoadInfo(cx, info, NS_ConvertUTF8toUTF16(aScriptSpec), aScope,
+                                           getter_AddRefs(serviceWorker));
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  serviceWorker.forget(aServiceWorker);
+  return NS_OK;
+}
+
 END_WORKERS_NAMESPACE
--- a/dom/workers/ServiceWorkerManager.h
+++ b/dom/workers/ServiceWorkerManager.h
@@ -20,16 +20,17 @@
 
 class nsIScriptError;
 
 namespace mozilla {
 namespace dom {
 namespace workers {
 
 class ServiceWorker;
+class ServiceWorkerContainer;
 class ServiceWorkerUpdateInstance;
 
 /**
  * UpdatePromise is a utility class that sort of imitates Promise, but not
  * completely. Using DOM Promise from C++ is a pain when we know the precise types
  * we're dealing with since it involves dealing with JSAPI. In this case we
  * also don't (yet) need the 'thenables added after resolution should trigger
  * immediately' support and other things like that. All we want is something
@@ -237,16 +238,19 @@ public:
   RejectUpdatePromiseObservers(ServiceWorkerRegistration* aRegistration,
                                const ErrorEventInit& aErrorDesc);
 
   void
   FinishFetch(ServiceWorkerRegistration* aRegistration,
               nsPIDOMWindow* aWindow);
 
   void
+  FinishInstall(ServiceWorkerRegistration* aRegistration);
+
+  void
   HandleError(JSContext* aCx,
               const nsACString& aScope,
               const nsAString& aWorkerURL,
               nsString aMessage,
               nsString aFilename,
               nsString aLine,
               uint32_t aLineNumber,
               uint32_t aColumnNumber,
@@ -261,22 +265,27 @@ private:
 
   NS_IMETHOD
   Update(ServiceWorkerRegistration* aRegistration, nsPIDOMWindow* aWindow);
 
   void
   Install(ServiceWorkerRegistration* aRegistration,
           ServiceWorkerInfo aServiceWorkerInfo);
 
-  NS_IMETHODIMP
+  NS_IMETHOD
   CreateServiceWorkerForWindow(nsPIDOMWindow* aWindow,
                                const nsACString& aScriptSpec,
                                const nsACString& aScope,
                                ServiceWorker** aServiceWorker);
 
+  NS_IMETHOD
+  CreateServiceWorker(const nsACString& aScriptSpec,
+                      const nsACString& aScope,
+                      ServiceWorker** aServiceWorker);
+
   static PLDHashOperator
   CleanupServiceWorkerInformation(const nsACString& aDomain,
                                   ServiceWorkerDomainInfo* aDomainInfo,
                                   void *aUnused);
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(ServiceWorkerManager,
                               NS_SERVICEWORKERMANAGER_IMPL_IID);
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/install_event_worker.js
@@ -0,0 +1,4 @@
+oninstall = function(e) {
+  dump("NSM Got install event\n");
+  dump(e.activeWorker);
+}
--- a/dom/workers/test/serviceworkers/mochitest.ini
+++ b/dom/workers/test/serviceworkers/mochitest.ini
@@ -1,9 +1,11 @@
 [DEFAULT]
 support-files =
   worker.js
   worker2.js
   worker3.js
   parse_error_worker.js
+  install_event_worker.js
 
 [test_installation_simple.html]
+[test_install_event.html]
 [test_navigator.html]
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_install_event.html
@@ -0,0 +1,49 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 94048 - test install event.</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+  function simpleRegister() {
+    var p = navigator.serviceWorker.register("worker.js");
+    return p;
+  }
+
+  function nextRegister() {
+    var p = navigator.serviceWorker.register("install_event_worker.js");
+    todo(false, "Check for onupdatefound event");
+    return p;
+  }
+
+  function runTest() {
+    simpleRegister()
+      .then(nextRegister)
+      .then(function() {
+        SimpleTest.finish();
+      }).catch(function(e) {
+        ok(false, "Some test failed with error " + e);
+        SimpleTest.finish();
+      });
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  SpecialPowers.pushPrefEnv({"set": [
+    ["dom.serviceWorkers.enabled", true],
+    ["dom.serviceWorkers.testing.enabled", true]
+  ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
+