Bug 916893 - Patch 1 - Notification on workers. r=khuey,wchen
☠☠ backed out by 531f100d2bd8 ☠ ☠
authorNikhil Marathe <nsm.nikhil@gmail.com>
Thu, 25 Jun 2015 11:36:53 -0700
changeset 250272 61c3f24cc908f0d46008dfa38939298841dfbee2
parent 250271 e7ae00df02ec976f778cb1f2d8ee7a69ce9b504f
child 250273 c3b07f0d1a6000e231395e3a2261f980271779e1
push id28951
push usercbook@mozilla.com
push dateFri, 26 Jun 2015 11:19:38 +0000
treeherdermozilla-central@56e207dbb3bd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskhuey, wchen
bugs916893
milestone41.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 916893 - Patch 1 - Notification on workers. r=khuey,wchen Does not implement the Service Worker API - https://notifications.spec.whatwg.org/#service-worker-api *** Folded: Bug 916893 - Better ownership model. r=khuey Fix for bug found by ASan where we were touching the NotificationFeature after releasing it.
dom/notification/Notification.cpp
dom/notification/Notification.h
dom/notification/moz.build
dom/webidl/Notification.webidl
dom/workers/RuntimeService.cpp
dom/workers/WorkerPrivate.h
dom/workers/Workers.h
dom/workers/test/mochitest.ini
dom/workers/test/notification_permission_worker.js
dom/workers/test/notification_worker.js
dom/workers/test/notification_worker_child-child.js
dom/workers/test/notification_worker_child-parent.js
dom/workers/test/test_notification.html
dom/workers/test/test_notification_child.html
dom/workers/test/test_notification_permission.html
modules/libpref/init/all.js
--- a/dom/notification/Notification.cpp
+++ b/dom/notification/Notification.cpp
@@ -4,44 +4,52 @@
  * 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 "mozilla/dom/Notification.h"
 #include "mozilla/dom/AppNotificationServiceOptionsBinding.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/OwningNonNull.h"
 #include "mozilla/dom/Promise.h"
+#include "mozilla/Move.h"
 #include "mozilla/Preferences.h"
 #include "nsContentUtils.h"
 #include "nsIAlertsService.h"
 #include "nsIAppsService.h"
 #include "nsIContentPermissionPrompt.h"
 #include "nsIDocument.h"
 #include "nsINotificationStorage.h"
 #include "nsIPermissionManager.h"
 #include "nsIUUIDGenerator.h"
 #include "nsServiceManagerUtils.h"
 #include "nsStructuredCloneContainer.h"
 #include "nsToolkitCompsCID.h"
 #include "nsGlobalWindow.h"
 #include "nsDOMJSUtils.h"
+#include "nsProxyRelease.h"
+#include "nsNetUtil.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIXPConnect.h"
 #include "mozilla/dom/PermissionMessageUtils.h"
 #include "mozilla/dom/Event.h"
 #include "mozilla/Services.h"
 #include "nsContentPermissionHelper.h"
 #include "nsILoadContext.h"
 #ifdef MOZ_B2G
 #include "nsIDOMDesktopNotification.h"
 #endif
 
+#include "WorkerPrivate.h"
+#include "WorkerRunnable.h"
+
 namespace mozilla {
 namespace dom {
 
+using namespace workers;
+
 class NotificationStorageCallback final : public nsINotificationStorageCallback
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(NotificationStorageCallback)
 
   NotificationStorageCallback(const GlobalObject& aGlobal, nsPIDOMWindow* aWindow, Promise* aPromise)
     : mCount(0),
@@ -73,17 +81,18 @@ public:
     RootedDictionary<NotificationOptions> options(aCx);
     options.mDir = Notification::StringToDirection(nsString(aDir));
     options.mLang = aLang;
     options.mBody = aBody;
     options.mTag = aTag;
     options.mIcon = aIcon;
     options.mMozbehavior.Init(aBehavior);
     nsRefPtr<Notification> notification;
-    notification = Notification::CreateInternal(mWindow,
+    nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
+    notification = Notification::CreateInternal(global,
                                                 aID,
                                                 aTitle,
                                                 options);
     ErrorResult rv;
     notification->InitFromBase64(aCx, aData, rv);
     if (rv.Failed()) {
       return rv.StealNSResult();
     }
@@ -186,49 +195,227 @@ protected:
   nsresult DispatchCallback();
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsCOMPtr<nsPIDOMWindow> mWindow;
   NotificationPermission mPermission;
   nsRefPtr<NotificationPermissionCallback> mCallback;
   nsCOMPtr<nsIContentPermissionRequester> mRequester;
 };
 
-class NotificationObserver : public nsIObserver
+namespace {
+class ReleaseNotificationControlRunnable final : public MainThreadWorkerControlRunnable
+{
+  Notification* mNotification;
+
+public:
+  explicit ReleaseNotificationControlRunnable(Notification* aNotification)
+    : MainThreadWorkerControlRunnable(aNotification->mWorkerPrivate)
+    , mNotification(aNotification)
+  { }
+
+  bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+  {
+    mNotification->ReleaseObject();
+    return true;
+  }
+};
+
+class GetPermissionRunnable final : public WorkerMainThreadRunnable
+{
+  NotificationPermission mPermission;
+
+public:
+  explicit GetPermissionRunnable(WorkerPrivate* aWorker)
+    : WorkerMainThreadRunnable(aWorker)
+    , mPermission(NotificationPermission::Denied)
+  { }
+
+  bool
+  MainThreadRun() override
+  {
+    ErrorResult result;
+    mPermission =
+      Notification::GetPermissionInternal(mWorkerPrivate->GetPrincipal(),
+                                          result);
+    return true;
+  }
+
+  NotificationPermission
+  GetPermission()
+  {
+    return mPermission;
+  }
+};
+
+class FocusWindowRunnable final : public nsRunnable
+{
+  nsMainThreadPtrHandle<nsPIDOMWindow> mWindow;
+public:
+  explicit FocusWindowRunnable(const nsMainThreadPtrHandle<nsPIDOMWindow>& aWindow)
+    : mWindow(aWindow)
+  { }
+
+  NS_IMETHOD
+  Run()
+  {
+    AssertIsOnMainThread();
+    if (!mWindow->IsCurrentInnerWindow()) {
+      // Window has been closed, this observer is not valid anymore
+      return NS_OK;
+    }
+
+    nsIDocument* doc = mWindow->GetExtantDoc();
+    if (doc) {
+      // Browser UI may use DOMWebNotificationClicked to focus the tab
+      // from which the event was dispatched.
+      nsContentUtils::DispatchChromeEvent(doc, mWindow->GetOuterWindow(),
+                                          NS_LITERAL_STRING("DOMWebNotificationClicked"),
+                                          true, true);
+    }
+
+    return NS_OK;
+  }
+};
+} // anonymous namespace
+
+// Subclass that can be directly dispatched to child workers from the main
+// thread.
+class NotificationWorkerRunnable : public WorkerRunnable
+{
+protected:
+  explicit NotificationWorkerRunnable(WorkerPrivate* aWorkerPrivate)
+    : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
+  {
+  }
+
+  bool
+  PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+  {
+    return true;
+  }
+
+  void
+  PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+               bool aDispatchResult) override
+  {
+  }
+
+  bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+  {
+    aWorkerPrivate->AssertIsOnWorkerThread();
+    aWorkerPrivate->ModifyBusyCountFromWorker(aCx, true);
+    WorkerRunInternal();
+    return true;
+  }
+
+  void
+  PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+          bool aRunResult) override
+  {
+    aWorkerPrivate->ModifyBusyCountFromWorker(aCx, false);
+  }
+
+  virtual void
+  WorkerRunInternal() = 0;
+};
+
+// Overrides dispatch and run handlers so we can directly dispatch from main
+// thread to child workers.
+class NotificationEventWorkerRunnable final : public NotificationWorkerRunnable
+{
+  Notification* mNotification;
+  const nsString mEventName;
+public:
+  NotificationEventWorkerRunnable(Notification* aNotification,
+                                  const nsString& aEventName)
+    : NotificationWorkerRunnable(aNotification->mWorkerPrivate)
+    , mNotification(aNotification)
+    , mEventName(aEventName)
+  {}
+
+  void
+  WorkerRunInternal() override
+  {
+    mNotification->DispatchTrustedEvent(mEventName);
+  }
+};
+
+class NotificationRef final {
+  friend class WorkerNotificationObserver;
+
+private:
+  Notification* mNotification;
+
+  // Only useful for workers.
+  void
+  Forget()
+  {
+    mNotification = nullptr;
+  }
+
+public:
+  explicit NotificationRef(Notification* aNotification)
+    : mNotification(aNotification)
+  {
+    MOZ_ASSERT(mNotification);
+    if (mNotification->mWorkerPrivate) {
+      mNotification->mWorkerPrivate->AssertIsOnWorkerThread();
+    } else {
+      AssertIsOnMainThread();
+    }
+
+    mNotification->AddRefObject();
+  }
+
+  ~NotificationRef()
+  {
+    if (mNotification) {
+      if (mNotification->mWorkerPrivate && NS_IsMainThread()) {
+        nsRefPtr<ReleaseNotificationControlRunnable> r =
+          new ReleaseNotificationControlRunnable(mNotification);
+        AutoSafeJSContext cx;
+        if (!r->Dispatch(cx)) {
+          MOZ_CRASH("Will leak worker thread Notification!");
+        }
+      } else {
+        mNotification->AssertIsOnTargetThread();
+        mNotification->ReleaseObject();
+      }
+      mNotification = nullptr;
+    }
+  }
+
+  // XXXnsm, is it worth having some sort of WeakPtr like wrapper instead of
+  // a rawptr that the NotificationRef can invalidate?
+  Notification*
+  GetNotification()
+  {
+    return mNotification;
+  }
+};
+
+class NotificationTask : public nsRunnable
 {
 public:
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSIOBSERVER
-
-  explicit NotificationObserver(Notification* aNotification)
-    : mNotification(aNotification) {}
-
-protected:
-  virtual ~NotificationObserver() {}
-
-  nsRefPtr<Notification> mNotification;
-};
-
-class NotificationTask : public nsIRunnable
-{
-public:
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSIRUNNABLE
-
   enum NotificationAction {
     eShow,
     eClose
   };
 
-  NotificationTask(Notification* aNotification, NotificationAction aAction)
-    : mNotification(aNotification), mAction(aAction) {}
+  NotificationTask(UniquePtr<NotificationRef> aRef, NotificationAction aAction)
+    : mNotificationRef(Move(aRef)), mAction(aAction) {}
 
+  NS_IMETHOD
+  Run() override;
 protected:
   virtual ~NotificationTask() {}
 
-  nsRefPtr<Notification> mNotification;
+  UniquePtr<NotificationRef> mNotificationRef;
   NotificationAction mAction;
 };
 
 uint32_t Notification::sCount = 0;
 
 NS_IMPL_CYCLE_COLLECTION(NotificationPermissionRequest, mWindow)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(NotificationPermissionRequest)
@@ -348,198 +535,247 @@ NotificationPermissionRequest::GetTypes(
 {
   nsTArray<nsString> emptyOptions;
   return nsContentPermissionUtils::CreatePermissionArray(NS_LITERAL_CSTRING("desktop-notification"),
                                                          NS_LITERAL_CSTRING("unused"),
                                                          emptyOptions,
                                                          aTypes);
 }
 
-NS_IMPL_ISUPPORTS(NotificationTask, nsIRunnable)
-
-NS_IMETHODIMP
-NotificationTask::Run()
+class NotificationObserver : public nsIObserver
 {
-  switch (mAction) {
-  case eShow:
-    mNotification->ShowInternal();
-    break;
-  case eClose:
-    mNotification->CloseInternal();
-    break;
-  default:
-    MOZ_CRASH("Unexpected action for NotificationTask.");
+public:
+  UniquePtr<NotificationRef> mNotificationRef;
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+
+  explicit NotificationObserver(UniquePtr<NotificationRef> aRef)
+    : mNotificationRef(Move(aRef))
+  {
+    AssertIsOnMainThread();
   }
-  return NS_OK;
-}
+
+protected:
+  virtual ~NotificationObserver()
+  {
+    AssertIsOnMainThread();
+  }
+};
 
 NS_IMPL_ISUPPORTS(NotificationObserver, nsIObserver)
 
 NS_IMETHODIMP
-NotificationObserver::Observe(nsISupports* aSubject, const char* aTopic,
-                              const char16_t* aData)
+NotificationTask::Run()
 {
-  nsCOMPtr<nsPIDOMWindow> window = mNotification->GetOwner();
-  if (!window || !window->IsCurrentInnerWindow()) {
-    // Window has been closed, this observer is not valid anymore
-    return NS_ERROR_FAILURE;
+  AssertIsOnMainThread();
+
+  // Get a pointer to notification before the notification takes ownership of
+  // the ref (it owns itself temporarily, with ShowInternal() and
+  // CloseInternal() passing on the ownership appropriately.)
+  Notification* notif = mNotificationRef->GetNotification();
+  notif->mTempRef.swap(mNotificationRef);
+  if (mAction == eShow) {
+    notif->ShowInternal();
+  } else if (mAction == eClose) {
+    notif->CloseInternal();
+  } else {
+    MOZ_CRASH("Invalid action");
   }
 
-  if (!strcmp("alertclickcallback", aTopic)) {
-    nsCOMPtr<nsIDOMEvent> event;
-    NS_NewDOMEvent(getter_AddRefs(event), mNotification, nullptr, nullptr);
-    nsresult rv = event->InitEvent(NS_LITERAL_STRING("click"), false, true);
-    NS_ENSURE_SUCCESS(rv, rv);
-    event->SetTrusted(true);
-    WantsPopupControlCheck popupControlCheck(event);
-    bool doDefaultAction = true;
-    mNotification->DispatchEvent(event, &doDefaultAction);
-    if (doDefaultAction) {
-      nsIDocument* doc = window ? window->GetExtantDoc() : nullptr;
-      if (doc) {
-        // Browser UI may use DOMWebNotificationClicked to focus the tab
-        // from which the event was dispatched.
-        nsContentUtils::DispatchChromeEvent(doc, window->GetOuterWindow(),
-                                            NS_LITERAL_STRING("DOMWebNotificationClicked"),
-                                            true, true);
-      }
-    }
-  } else if (!strcmp("alertfinished", aTopic)) {
-    nsCOMPtr<nsINotificationStorage> notificationStorage =
-      do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID);
-    if (notificationStorage && mNotification->IsStored()) {
-      nsString origin;
-      nsresult rv = Notification::GetOrigin(mNotification->GetOwner(), origin);
-      if (NS_SUCCEEDED(rv)) {
-        nsString id;
-        mNotification->GetID(id);
-        notificationStorage->Delete(origin, id);
-      }
-      mNotification->SetStoredState(false);
-    }
-    mNotification->mIsClosed = true;
-    mNotification->DispatchTrustedEvent(NS_LITERAL_STRING("close"));
-  } else if (!strcmp("alertshow", aTopic)) {
-    mNotification->DispatchTrustedEvent(NS_LITERAL_STRING("show"));
+  MOZ_ASSERT(!mNotificationRef);
+  return NS_OK;
+}
+
+// static
+bool
+Notification::PrefEnabled(JSContext* aCx, JSObject* aObj)
+{
+  if (NS_IsMainThread()) {
+    return Preferences::GetBool("dom.webnotifications.enabled", false);
   }
 
-  return NS_OK;
+  WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
+  if (!workerPrivate) {
+    return false;
+  }
+
+  return workerPrivate->DOMWorkerNotificationEnabled();
+}
+
+// static
+bool
+Notification::IsGetEnabled(JSContext* aCx, JSObject* aObj)
+{
+  return NS_IsMainThread();
 }
 
 Notification::Notification(const nsAString& aID, const nsAString& aTitle, const nsAString& aBody,
                            NotificationDirection aDir, const nsAString& aLang,
                            const nsAString& aTag, const nsAString& aIconUrl,
-                           const NotificationBehavior& aBehavior, nsPIDOMWindow* aWindow)
-  : DOMEventTargetHelper(aWindow),
+                           const NotificationBehavior& aBehavior, nsIGlobalObject* aGlobal)
+  : DOMEventTargetHelper(),
+    mWorkerPrivate(nullptr), mObserver(nullptr),
     mID(aID), mTitle(aTitle), mBody(aBody), mDir(aDir), mLang(aLang),
-    mTag(aTag), mIconUrl(aIconUrl), mBehavior(aBehavior), mIsClosed(false), mIsStored(false)
+    mTag(aTag), mIconUrl(aIconUrl), mBehavior(aBehavior), mIsClosed(false),
+    mIsStored(false), mTaskCount(0)
 {
   nsAutoString alertName;
-  DebugOnly<nsresult> rv = GetOrigin(GetOwner(), alertName);
-  MOZ_ASSERT(NS_SUCCEEDED(rv), "GetOrigin should not have failed");
+  if (NS_IsMainThread()) {
+    nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal);
+    MOZ_ASSERT(window);
+    BindToOwner(window);
+
+    DebugOnly<nsresult> rv = GetOrigin(window, alertName);
+    MOZ_ASSERT(NS_SUCCEEDED(rv), "GetOrigin should not have failed");
+  } else {
+    mWorkerPrivate = GetCurrentThreadWorkerPrivate();
+    MOZ_ASSERT(mWorkerPrivate);
+
+    DebugOnly<nsresult> rv = GetOriginWorker(alertName);
+    MOZ_ASSERT(NS_SUCCEEDED(rv), "GetOrigin should not have failed");
+  }
 
   // Get the notification name that is unique per origin + tag/ID.
   // The name of the alert is of the form origin#tag/ID.
   alertName.Append('#');
   if (!mTag.IsEmpty()) {
     alertName.AppendLiteral("tag:");
     alertName.Append(mTag);
   } else {
     alertName.AppendLiteral("notag:");
     alertName.Append(mID);
   }
 
   mAlertName = alertName;
 }
 
+// May be called on any thread.
 // static
 already_AddRefed<Notification>
 Notification::Constructor(const GlobalObject& aGlobal,
                           const nsAString& aTitle,
                           const NotificationOptions& aOptions,
                           ErrorResult& aRv)
 {
-  MOZ_ASSERT(NS_IsMainThread());
-  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
-  MOZ_ASSERT(window, "Window should not be null.");
-
-  nsRefPtr<Notification> notification = CreateInternal(window,
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+  nsRefPtr<Notification> notification = CreateInternal(global,
                                                        EmptyString(),
                                                        aTitle,
                                                        aOptions);
-
   // Make a structured clone of the aOptions.mData object
   JS::Rooted<JS::Value> data(aGlobal.Context(), aOptions.mData);
   notification->InitFromJSVal(aGlobal.Context(), data, aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
 
+  auto n = MakeUnique<NotificationRef>(notification);
   // Queue a task to show the notification.
   nsCOMPtr<nsIRunnable> showNotificationTask =
-    new NotificationTask(notification, NotificationTask::eShow);
-  NS_DispatchToCurrentThread(showNotificationTask);
+    new NotificationTask(Move(n), NotificationTask::eShow);
+  nsresult rv = NS_DispatchToMainThread(showNotificationTask);
+  if (NS_FAILED(rv)) {
+    notification->DispatchTrustedEvent(NS_LITERAL_STRING("error"));
+  }
 
-  // Persist the notification.
+  //XXXnsm The persistence service seems like a prime candidate of
+  //PBackground.
+  // On workers, persistence is done in the NotificationTask.
+  if (NS_IsMainThread()) {
+    nsresult rv = notification->PersistNotification();
+    if (NS_FAILED(rv)) {
+      NS_WARNING("Could not persist main thread Notification");
+    }
+  }
+
+  return notification.forget();
+}
+
+nsresult
+Notification::PersistNotification()
+{
+  AssertIsOnMainThread();
   nsresult rv;
   nsCOMPtr<nsINotificationStorage> notificationStorage =
     do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv);
   if (NS_FAILED(rv)) {
-    aRv.Throw(rv);
-    return nullptr;
+    return rv;
   }
 
   nsString origin;
-  aRv = GetOrigin(window, origin);
-  if (aRv.Failed()) {
-    return nullptr;
+  if (mWorkerPrivate) {
+    rv = GetOriginWorker(origin);
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+  } else {
+    nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(GetOwner());
+    MOZ_ASSERT(window);
+    rv = GetOrigin(window, origin);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
   }
 
   nsString id;
-  notification->GetID(id);
+  GetID(id);
 
   nsString alertName;
-  notification->GetAlertName(alertName);
+  GetAlertName(alertName);
 
   nsString dataString;
   nsCOMPtr<nsIStructuredCloneContainer> scContainer;
-  scContainer = notification->GetDataCloneContainer();
+  scContainer = GetDataCloneContainer();
   if (scContainer) {
     scContainer->GetDataAsBase64(dataString);
   }
 
   nsAutoString behavior;
-  if (!aOptions.mMozbehavior.ToJSON(behavior)) {
-    aRv.Throw(NS_ERROR_FAILURE);
-    return nullptr;
+  if (!mBehavior.ToJSON(behavior)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  rv = notificationStorage->Put(origin,
+                                id,
+                                mTitle,
+                                DirectionToString(mDir),
+                                mLang,
+                                mBody,
+                                mTag,
+                                mIconUrl,
+                                alertName,
+                                dataString,
+                                behavior);
+
+  if (NS_FAILED(rv)) {
+    return rv;
   }
 
-  aRv = notificationStorage->Put(origin,
-                                 id,
-                                 aTitle,
-                                 DirectionToString(aOptions.mDir),
-                                 aOptions.mLang,
-                                 aOptions.mBody,
-                                 aOptions.mTag,
-                                 aOptions.mIcon,
-                                 alertName,
-                                 dataString,
-                                 behavior);
+  SetStoredState(true);
+  return NS_OK;
+}
 
-  if (aRv.Failed()) {
-    return nullptr;
+void
+Notification::UnpersistNotification()
+{
+  AssertIsOnMainThread();
+  if (mIsStored) {
+    nsCOMPtr<nsINotificationStorage> notificationStorage =
+      do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID);
+    if (notificationStorage) {
+      nsString origin;
+      nsresult rv = GetOrigin(GetOwner(), origin);
+      if (NS_SUCCEEDED(rv)) {
+        notificationStorage->Delete(origin, mID);
+      }
+    }
+    SetStoredState(false);
   }
-
-  notification->SetStoredState(true);
-
-  return notification.forget();
 }
 
 already_AddRefed<Notification>
-Notification::CreateInternal(nsPIDOMWindow* aWindow,
+Notification::CreateInternal(nsIGlobalObject* aGlobal,
                              const nsAString& aID,
                              const nsAString& aTitle,
                              const NotificationOptions& aOptions)
 {
   nsString id;
   if (!aID.IsEmpty()) {
     id = aID;
   } else {
@@ -559,21 +795,26 @@ Notification::CreateInternal(nsPIDOMWind
   nsRefPtr<Notification> notification = new Notification(id,
                                                          aTitle,
                                                          aOptions.mBody,
                                                          aOptions.mDir,
                                                          aOptions.mLang,
                                                          aOptions.mTag,
                                                          aOptions.mIcon,
                                                          aOptions.mMozbehavior,
-                                                         aWindow);
+                                                         aGlobal);
   return notification.forget();
 }
 
-Notification::~Notification() {}
+Notification::~Notification()
+{
+  AssertIsOnTargetThread();
+  MOZ_ASSERT(!mFeature);
+  MOZ_ASSERT(!mTempRef);
+}
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(Notification)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Notification, DOMEventTargetHelper)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mData)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDataObjectContainer)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Notification, DOMEventTargetHelper)
@@ -585,87 +826,311 @@ NS_IMPL_ADDREF_INHERITED(Notification, D
 NS_IMPL_RELEASE_INHERITED(Notification, DOMEventTargetHelper)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(Notification)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
 nsIPrincipal*
 Notification::GetPrincipal()
 {
-  nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(GetOwner());
-  NS_ENSURE_TRUE(sop, nullptr);
-  return sop->GetPrincipal();
+  AssertIsOnMainThread();
+  MOZ_ASSERT_IF(mWorkerPrivate, mFeature);
+  if (mWorkerPrivate) {
+    return mWorkerPrivate->GetPrincipal();
+  } else {
+    nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(GetOwner());
+    NS_ENSURE_TRUE(sop, nullptr);
+    return sop->GetPrincipal();
+  }
+}
+
+class WorkerNotificationObserver final : public NotificationObserver
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_NSIOBSERVER
+
+  explicit WorkerNotificationObserver(UniquePtr<NotificationRef> aRef)
+    : NotificationObserver(Move(aRef))
+  {
+    AssertIsOnMainThread();
+    MOZ_ASSERT(mNotificationRef->GetNotification()->mWorkerPrivate);
+  }
+
+  void
+  ForgetNotification()
+  {
+    AssertIsOnMainThread();
+    mNotificationRef->Forget();
+  }
+
+protected:
+  virtual ~WorkerNotificationObserver()
+  {
+    AssertIsOnMainThread();
+
+    MOZ_ASSERT(mNotificationRef);
+    Notification* notification = mNotificationRef->GetNotification();
+    if (!notification) {
+      return;
+    }
+
+    // Try to pass ownership back to the worker. If the dispatch succeeds we
+    // are guaranteed this runnable will run, and that it will run after queued
+    // event runnables, so event runnables will have a safe pointer to the
+    // Notification.
+    //
+    // If the dispatch fails, the worker isn't running anymore and the event
+    // runnables have already run. We can just let the standard NotificationRef
+    // release routine take over when ReleaseNotificationRunnable gets deleted.
+    class ReleaseNotificationRunnable final : public NotificationWorkerRunnable
+    {
+      UniquePtr<NotificationRef> mNotificationRef;
+    public:
+      explicit ReleaseNotificationRunnable(UniquePtr<NotificationRef> aRef)
+        : NotificationWorkerRunnable(aRef->GetNotification()->mWorkerPrivate)
+        , mNotificationRef(Move(aRef))
+      {}
+
+      void
+      WorkerRunInternal() override
+      {
+        UniquePtr<NotificationRef> ref;
+        mozilla::Swap(ref, mNotificationRef);
+        // Gets released at the end of the function.
+      }
+    };
+
+    nsRefPtr<ReleaseNotificationRunnable> r =
+      new ReleaseNotificationRunnable(Move(mNotificationRef));
+    notification = nullptr;
+
+    AutoJSAPI jsapi;
+    jsapi.Init();
+    r->Dispatch(jsapi.cx());
+  }
+};
+
+NS_IMPL_ISUPPORTS_INHERITED0(WorkerNotificationObserver, NotificationObserver)
+
+bool
+Notification::DispatchClickEvent()
+{
+  AssertIsOnTargetThread();
+  nsCOMPtr<nsIDOMEvent> event;
+  NS_NewDOMEvent(getter_AddRefs(event), this, nullptr, nullptr);
+  nsresult rv = event->InitEvent(NS_LITERAL_STRING("click"), false, true);
+  NS_ENSURE_SUCCESS(rv, false);
+  event->SetTrusted(true);
+  WantsPopupControlCheck popupControlCheck(event);
+  bool doDefaultAction = true;
+  DispatchEvent(event, &doDefaultAction);
+  return doDefaultAction;
+}
+
+// Overrides dispatch and run handlers so we can directly dispatch from main
+// thread to child workers.
+class NotificationClickWorkerRunnable final : public NotificationWorkerRunnable
+{
+  Notification* mNotification;
+  // Optional window that gets focused if click event is not
+  // preventDefault()ed.
+  nsMainThreadPtrHandle<nsPIDOMWindow> mWindow;
+public:
+  NotificationClickWorkerRunnable(Notification* aNotification,
+                                  const nsMainThreadPtrHandle<nsPIDOMWindow>& aWindow)
+    : NotificationWorkerRunnable(aNotification->mWorkerPrivate)
+    , mNotification(aNotification)
+    , mWindow(aWindow)
+  {}
+
+  void
+  WorkerRunInternal() override
+  {
+    bool doDefaultAction = mNotification->DispatchClickEvent();
+    if (doDefaultAction) {
+      nsRefPtr<FocusWindowRunnable> r = new FocusWindowRunnable(mWindow);
+      NS_DispatchToMainThread(r);
+    }
+  }
+};
+
+NS_IMETHODIMP
+NotificationObserver::Observe(nsISupports* aSubject, const char* aTopic,
+                              const char16_t* aData)
+{
+  AssertIsOnMainThread();
+  MOZ_ASSERT(mNotificationRef);
+  Notification* notification = mNotificationRef->GetNotification();
+  MOZ_ASSERT(notification);
+  if (!strcmp("alertclickcallback", aTopic)) {
+    nsCOMPtr<nsPIDOMWindow> window = notification->GetOwner();
+    if (!window || !window->IsCurrentInnerWindow()) {
+      // Window has been closed, this observer is not valid anymore
+      return NS_ERROR_FAILURE;
+    }
+
+    bool doDefaultAction = notification->DispatchClickEvent();
+    if (doDefaultAction) {
+      nsIDocument* doc = window ? window->GetExtantDoc() : nullptr;
+      if (doc) {
+        // Browser UI may use DOMWebNotificationClicked to focus the tab
+        // from which the event was dispatched.
+        nsContentUtils::DispatchChromeEvent(doc, window->GetOuterWindow(),
+                                            NS_LITERAL_STRING("DOMWebNotificationClicked"),
+                                            true, true);
+      }
+    }
+  } else if (!strcmp("alertfinished", aTopic)) {
+    notification->UnpersistNotification();
+    notification->mIsClosed = true;
+    notification->DispatchTrustedEvent(NS_LITERAL_STRING("close"));
+  } else if (!strcmp("alertshow", aTopic)) {
+    notification->DispatchTrustedEvent(NS_LITERAL_STRING("show"));
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerNotificationObserver::Observe(nsISupports* aSubject, const char* aTopic,
+                                    const char16_t* aData)
+{
+  AssertIsOnMainThread();
+  MOZ_ASSERT(mNotificationRef);
+  // For an explanation of why it is OK to pass this rawptr to the event
+  // runnables, see the Notification class comment.
+  Notification* notification = mNotificationRef->GetNotification();
+  // We can't assert notification here since the feature could've unset it.
+  if (!notification) {
+    return NS_ERROR_FAILURE;
+  }
+
+  MOZ_ASSERT(notification->mWorkerPrivate);
+
+  nsRefPtr<WorkerRunnable> r;
+  if (!strcmp("alertclickcallback", aTopic)) {
+    nsCOMPtr<nsPIDOMWindow> window = notification->mWorkerPrivate->GetWindow();
+    if (!window || !window->IsCurrentInnerWindow()) {
+      // Window has been closed, this observer is not valid anymore
+      return NS_ERROR_FAILURE;
+    }
+
+    // Instead of bothering with adding features and other worker lifecycle
+    // management, we simply hold strongrefs to the window and document.
+    nsMainThreadPtrHandle<nsPIDOMWindow> windowHandle(
+      new nsMainThreadPtrHolder<nsPIDOMWindow>(window));
+
+    r = new NotificationClickWorkerRunnable(notification, windowHandle);
+  } else if (!strcmp("alertfinished", aTopic)) {
+    notification->UnpersistNotification();
+    notification->mIsClosed = true;
+    r = new NotificationEventWorkerRunnable(notification,
+                                            NS_LITERAL_STRING("close"));
+  } else if (!strcmp("alertshow", aTopic)) {
+    r = new NotificationEventWorkerRunnable(notification,
+                                            NS_LITERAL_STRING("show"));
+  }
+
+  MOZ_ASSERT(r);
+  AutoSafeJSContext cx;
+  if (!r->Dispatch(cx)) {
+    NS_WARNING("Could not dispatch event to worker notification");
+  }
+  return NS_OK;
 }
 
 void
 Notification::ShowInternal()
 {
+  AssertIsOnMainThread();
+  MOZ_ASSERT(mTempRef, "Notification should take ownership of itself before"
+                       "calling ShowInternal!");
+  // A notification can only have one observer and one call to ShowInternal.
+  MOZ_ASSERT(!mObserver);
+
+  // Transfer ownership to local scope so we can either release it at the end
+  // of this function or transfer it to the observer.
+  UniquePtr<NotificationRef> ownership;
+  mozilla::Swap(ownership, mTempRef);
+  MOZ_ASSERT(ownership->GetNotification() == this);
+
+  if (mWorkerPrivate) {
+    // Persist the notification now since we couldn't do it on the worker
+    // thread.
+    nsresult rv = PersistNotification();
+    if (NS_FAILED(rv)) {
+      NS_WARNING("Could not persist worker Notification");
+    }
+  }
+
   nsCOMPtr<nsIAlertsService> alertService =
     do_GetService(NS_ALERTSERVICE_CONTRACTID);
 
   ErrorResult result;
-  if (GetPermissionInternal(GetOwner(), result) !=
-    NotificationPermission::Granted || !alertService) {
+  NotificationPermission permission = NotificationPermission::Denied;
+  if (mWorkerPrivate) {
+    permission = GetPermissionInternal(mWorkerPrivate->GetPrincipal(), result);
+  } else {
+    permission = GetPermissionInternal(GetOwner(), result);
+  }
+  if (permission != NotificationPermission::Granted || !alertService) {
     // We do not have permission to show a notification or alert service
     // is not available.
-    DispatchTrustedEvent(NS_LITERAL_STRING("error"));
+    if (mWorkerPrivate) {
+      nsRefPtr<NotificationEventWorkerRunnable> r =
+        new NotificationEventWorkerRunnable(this,
+                                            NS_LITERAL_STRING("error"));
+      AutoSafeJSContext cx;
+      if (!r->Dispatch(cx)) {
+        NS_WARNING("Could not dispatch event to worker notification");
+      }
+    } else {
+      DispatchTrustedEvent(NS_LITERAL_STRING("error"));
+    }
     return;
   }
 
-  nsresult rv;
-  nsAutoString absoluteUrl;
+  nsAutoString iconUrl;
   nsAutoString soundUrl;
-  // Resolve image URL against document base URI.
-  nsIDocument* doc = GetOwner()->GetExtantDoc();
-  if (doc) {
-    nsCOMPtr<nsIURI> baseUri = doc->GetBaseURI();
-    if (baseUri) {
-      if (mIconUrl.Length() > 0) {
-        nsCOMPtr<nsIURI> srcUri;
-        rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(srcUri),
-                                                       mIconUrl, doc, baseUri);
-        if (NS_SUCCEEDED(rv)) {
-          nsAutoCString src;
-          srcUri->GetSpec(src);
-          absoluteUrl = NS_ConvertUTF8toUTF16(src);
-        }
-      }
-      if (mBehavior.mSoundFile.Length() > 0) {
-        nsCOMPtr<nsIURI> srcUri;
-        rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(srcUri),
-            mBehavior.mSoundFile, doc, baseUri);
-        if (NS_SUCCEEDED(rv)) {
-          nsAutoCString src;
-          srcUri->GetSpec(src);
-          soundUrl = NS_ConvertUTF8toUTF16(src);
-        }
-      }
-    }
+  ResolveIconAndSoundURL(iconUrl, soundUrl);
+
+  // Ownership passed to observer.
+  nsCOMPtr<nsIObserver> observer;
+  if (mWorkerPrivate) {
+    // Keep a pointer so that the feature can tell the observer not to release
+    // the notification.
+    mObserver = new WorkerNotificationObserver(Move(ownership));
+    observer = mObserver;
+  } else {
+    observer = new NotificationObserver(Move(ownership));
   }
 
-  nsCOMPtr<nsIObserver> observer = new NotificationObserver(this);
-
   // mDataObjectContainer might be uninitialized here because the notification
   // was constructed with an undefined data property.
   nsString dataStr;
   if (mDataObjectContainer) {
     mDataObjectContainer->GetDataAsBase64(dataStr);
   }
 
 #ifdef MOZ_B2G
   nsCOMPtr<nsIAppNotificationService> appNotifier =
     do_GetService("@mozilla.org/system-alerts-service;1");
   if (appNotifier) {
-    nsCOMPtr<nsPIDOMWindow> window = GetOwner();
-    uint32_t appId = (window.get())->GetDoc()->NodePrincipal()->GetAppId();
+    uint32_t appId = nsIScriptSecurityManager::UNKNOWN_APP_ID;
+    if (mWorkerPrivate) {
+      appId = mWorkerPrivate->GetPrincipal()->GetAppId();
+    } else {
+      nsCOMPtr<nsPIDOMWindow> window = GetOwner();
+      appId = (window.get())->GetDoc()->NodePrincipal()->GetAppId();
+    }
 
     if (appId != nsIScriptSecurityManager::UNKNOWN_APP_ID) {
       nsCOMPtr<nsIAppsService> appsService = do_GetService("@mozilla.org/AppsService;1");
       nsString manifestUrl = EmptyString();
-      rv = appsService->GetManifestURLByLocalId(appId, manifestUrl);
+      nsresult rv = appsService->GetManifestURLByLocalId(appId, manifestUrl);
       if (NS_SUCCEEDED(rv)) {
         mozilla::AutoSafeJSContext cx;
         JS::Rooted<JS::Value> val(cx);
         AppNotificationServiceOptions ops;
         ops.mTextClickable = true;
         ops.mManifestURL = manifestUrl;
         ops.mId = mAlertName;
         ops.mDbId = mID;
@@ -676,40 +1141,61 @@ Notification::ShowInternal()
         ops.mMozbehavior = mBehavior;
         ops.mMozbehavior.mSoundFile = soundUrl;
 
         if (!ToJSValue(cx, ops, &val)) {
           NS_WARNING("Converting dict to object failed!");
           return;
         }
 
-        appNotifier->ShowAppNotification(absoluteUrl, mTitle, mBody,
+        appNotifier->ShowAppNotification(iconUrl, mTitle, mBody,
                                          observer, val);
         return;
       }
     }
   }
 #endif
 
   // In the case of IPC, the parent process uses the cookie to map to
   // nsIObserver. Thus the cookie must be unique to differentiate observers.
   nsString uniqueCookie = NS_LITERAL_STRING("notification:");
   uniqueCookie.AppendInt(sCount++);
+  //XXXnsm Should this default to true?
   bool inPrivateBrowsing = false;
+  nsIDocument* doc = mWorkerPrivate ? mWorkerPrivate->GetDocument()
+                                    : GetOwner()->GetExtantDoc();
   if (doc) {
     nsCOMPtr<nsILoadContext> loadContext = doc->GetLoadContext();
     inPrivateBrowsing = loadContext && loadContext->UsePrivateBrowsing();
+  } else if (mWorkerPrivate) {
+    // Not all workers may have a document, but with Bug 1107516 fixed, they
+    // should all have a loadcontext.
+    nsCOMPtr<nsILoadGroup> loadGroup = mWorkerPrivate->GetLoadGroup();
+    nsCOMPtr<nsILoadContext> loadContext;
+    NS_QueryNotificationCallbacks(nullptr, loadGroup, NS_GET_IID(nsILoadContext),
+                                  getter_AddRefs(loadContext));
+    inPrivateBrowsing = loadContext && loadContext->UsePrivateBrowsing();
   }
-  alertService->ShowAlertNotification(absoluteUrl, mTitle, mBody, true,
+  alertService->ShowAlertNotification(iconUrl, mTitle, mBody, true,
                                       uniqueCookie, observer, mAlertName,
                                       DirectionToString(mDir), mLang,
                                       dataStr, GetPrincipal(),
                                       inPrivateBrowsing);
 }
 
+/* static */ bool
+Notification::RequestPermissionEnabledForScope(JSContext* aCx, JSObject* /* unused */)
+{
+  // requestPermission() is not allowed on workers. The calling page should ask
+  // for permission on the worker's behalf. This is to prevent 'which window
+  // should show the browser pop-up'. See discussion:
+  // http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2013-October/041272.html
+  return NS_IsMainThread();
+}
+
 void
 Notification::RequestPermission(const GlobalObject& aGlobal,
                                 const Optional<OwningNonNull<NotificationPermissionCallback> >& aCallback,
                                 ErrorResult& aRv)
 {
   // Get principal from global to make permission request for notifications.
   nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
   nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aGlobal.GetAsSupports());
@@ -727,36 +1213,59 @@ Notification::RequestPermission(const Gl
     new NotificationPermissionRequest(principal, window, permissionCallback);
 
   NS_DispatchToMainThread(request);
 }
 
 NotificationPermission
 Notification::GetPermission(const GlobalObject& aGlobal, ErrorResult& aRv)
 {
-  return GetPermissionInternal(aGlobal.GetAsSupports(), aRv);
+  if (NS_IsMainThread()) {
+    return GetPermissionInternal(aGlobal.GetAsSupports(), aRv);
+  } else {
+    WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+    MOZ_ASSERT(worker);
+    nsRefPtr<GetPermissionRunnable> r =
+      new GetPermissionRunnable(worker);
+    if (!r->Dispatch(worker->GetJSContext())) {
+      aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
+      return NotificationPermission::Denied;
+    }
+
+    return r->GetPermission();
+  }
 }
 
-NotificationPermission
+/* static */ NotificationPermission
 Notification::GetPermissionInternal(nsISupports* aGlobal, ErrorResult& aRv)
 {
   // Get principal from global to check permission for notifications.
   nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aGlobal);
   if (!sop) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return NotificationPermission::Denied;
   }
 
   nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
-  if (nsContentUtils::IsSystemPrincipal(principal)) {
+  return GetPermissionInternal(principal, aRv);
+}
+
+/* static */ NotificationPermission
+Notification::GetPermissionInternal(nsIPrincipal* aPrincipal,
+                                    ErrorResult& aRv)
+{
+  AssertIsOnMainThread();
+  MOZ_ASSERT(aPrincipal);
+
+  if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
     return NotificationPermission::Granted;
   } else {
     // Allow files to show notifications by default.
     nsCOMPtr<nsIURI> uri;
-    principal->GetURI(getter_AddRefs(uri));
+    aPrincipal->GetURI(getter_AddRefs(uri));
     if (uri) {
       bool isFile;
       uri->SchemeIs("file", &isFile);
       if (isFile) {
         return NotificationPermission::Granted;
       }
     }
   }
@@ -770,36 +1279,92 @@ Notification::GetPermissionInternal(nsIS
     }
   }
 
   uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
 
   nsCOMPtr<nsIPermissionManager> permissionManager =
     services::GetPermissionManager();
 
-  permissionManager->TestPermissionFromPrincipal(principal,
+  permissionManager->TestPermissionFromPrincipal(aPrincipal,
                                                  "desktop-notification",
                                                  &permission);
 
   // Convert the result to one of the enum types.
   switch (permission) {
   case nsIPermissionManager::ALLOW_ACTION:
     return NotificationPermission::Granted;
   case nsIPermissionManager::DENY_ACTION:
     return NotificationPermission::Denied;
   default:
     return NotificationPermission::Default;
   }
 }
 
+nsresult
+Notification::ResolveIconAndSoundURL(nsString& iconUrl, nsString& soundUrl)
+{
+  AssertIsOnMainThread();
+  nsresult rv = NS_OK;
+
+  nsCOMPtr<nsIURI> baseUri;
+
+  // XXXnsm If I understand correctly, the character encoding for resolving
+  // URIs in new specs is dictated by the URL spec, which states that unless
+  // the URL parser is passed an override encoding, the charset to be used is
+  // UTF-8. The new Notification icon/sound specification just says to use the
+  // Fetch API, where the Request constructor defers to URL parsing specifying
+  // the API base URL and no override encoding. So we've to use UTF-8 on
+  // workers, but for backwards compat keeping it document charset on main
+  // thread.
+  const char* charset = "UTF-8";
+
+  if (mWorkerPrivate) {
+    baseUri = mWorkerPrivate->GetBaseURI();
+  } else {
+    nsIDocument* doc = GetOwner()->GetExtantDoc();
+    if (doc) {
+      baseUri = doc->GetBaseURI();
+      charset = doc->GetDocumentCharacterSet().get();
+    } else {
+      NS_WARNING("No document found for main thread notification!");
+      return NS_ERROR_FAILURE;
+    }
+  }
+
+  if (baseUri) {
+    if (mIconUrl.Length() > 0) {
+      nsCOMPtr<nsIURI> srcUri;
+      rv = NS_NewURI(getter_AddRefs(srcUri), mIconUrl, charset, baseUri);
+      if (NS_SUCCEEDED(rv)) {
+        nsAutoCString src;
+        srcUri->GetSpec(src);
+        iconUrl = NS_ConvertUTF8toUTF16(src);
+      }
+    }
+    if (mBehavior.mSoundFile.Length() > 0) {
+      nsCOMPtr<nsIURI> srcUri;
+      rv = NS_NewURI(getter_AddRefs(srcUri), mBehavior.mSoundFile, charset, baseUri);
+      if (NS_SUCCEEDED(rv)) {
+        nsAutoCString src;
+        srcUri->GetSpec(src);
+        soundUrl = NS_ConvertUTF8toUTF16(src);
+      }
+    }
+  }
+
+  return rv;
+}
+
 already_AddRefed<Promise>
 Notification::Get(const GlobalObject& aGlobal,
                   const GetNotificationOptions& aFilter,
                   ErrorResult& aRv)
 {
+  AssertIsOnMainThread();
   nsCOMPtr<nsIGlobalObject> global =
     do_QueryInterface(aGlobal.GetAsSupports());
   MOZ_ASSERT(global);
   nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(global);
   MOZ_ASSERT(window);
   nsIDocument* doc = window->GetExtantDoc();
   if (!doc) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
@@ -841,39 +1406,40 @@ JSObject*
 Notification::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return mozilla::dom::NotificationBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
 Notification::Close()
 {
-  // Queue a task to close the notification.
+  AssertIsOnTargetThread();
+  auto ref = MakeUnique<NotificationRef>(this);
+
   nsCOMPtr<nsIRunnable> closeNotificationTask =
-    new NotificationTask(this, NotificationTask::eClose);
-  NS_DispatchToMainThread(closeNotificationTask);
+    new NotificationTask(Move(ref), NotificationTask::eClose);
+  nsresult rv = NS_DispatchToMainThread(closeNotificationTask);
+
+  if (NS_FAILED(rv)) {
+    DispatchTrustedEvent(NS_LITERAL_STRING("error"));
+    // If dispatch fails, NotificationTask will release the ref when it goes
+    // out of scope at the end of this function.
+  }
 }
 
 void
 Notification::CloseInternal()
 {
-  if (mIsStored) {
-    // Don't bail out if notification storage fails, since we still
-    // want to send the close event through the alert service.
-    nsCOMPtr<nsINotificationStorage> notificationStorage =
-      do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID);
-    if (notificationStorage) {
-      nsString origin;
-      nsresult rv = GetOrigin(GetOwner(), origin);
-      if (NS_SUCCEEDED(rv)) {
-        notificationStorage->Delete(origin, mID);
-      }
-    }
-    SetStoredState(false);
-  }
+  // Transfer ownership (if any) to local scope so we can release it at the end
+  // of this function. This is relevant when the call is from
+  // NotificationTask::Run().
+  UniquePtr<NotificationRef> ownership;
+  mozilla::Swap(ownership, mTempRef);
+
+  UnpersistNotification();
   if (!mIsClosed) {
     nsCOMPtr<nsIAlertsService> alertService =
       do_GetService(NS_ALERTSERVICE_CONTRACTID);
     if (alertService) {
       alertService->CloseAlert(mAlertName, GetPrincipal());
     }
   }
 }
@@ -905,16 +1471,24 @@ Notification::GetOrigin(nsPIDOMWindow* a
       do_GetService("@mozilla.org/AppsService;1", &rv);
     NS_ENSURE_SUCCESS(rv, rv);
     appsService->GetManifestURLByLocalId(appId, aOrigin);
   }
 
   return NS_OK;
 }
 
+nsresult
+Notification::GetOriginWorker(nsString& aOrigin)
+{
+  MOZ_ASSERT(mWorkerPrivate);
+  aOrigin = mWorkerPrivate->GetLocationInfo().mOrigin;
+  return NS_OK;
+}
+
 nsIStructuredCloneContainer* Notification::GetDataCloneContainer()
 {
   return mDataObjectContainer;
 }
 
 void
 Notification::GetData(JSContext* aCx,
                       JS::MutableHandle<JS::Value> aRetval)
@@ -953,11 +1527,118 @@ void Notification::InitFromBase64(JSCont
   }
 
   auto container = new nsStructuredCloneContainer();
   aRv = container->InitFromBase64(aData, JS_STRUCTURED_CLONE_VERSION,
                                   aCx);
   mDataObjectContainer = container;
 }
 
+void
+Notification::AddRefObject()
+{
+  AssertIsOnTargetThread();
+  AddRef();
+  MOZ_ASSERT_IF(mWorkerPrivate&& !mFeature, mTaskCount == 0);
+  MOZ_ASSERT_IF(mWorkerPrivate && mFeature, mTaskCount > 0);
+  if (mWorkerPrivate && !mFeature) {
+    RegisterFeature();
+  }
+  ++mTaskCount;
+}
+
+void
+Notification::ReleaseObject()
+{
+  AssertIsOnTargetThread();
+  MOZ_ASSERT(mTaskCount > 0);
+
+  --mTaskCount;
+  if (mWorkerPrivate && mFeature && mTaskCount == 0) {
+    UnregisterFeature();
+  }
+  Release();
+}
+
+NotificationFeature::NotificationFeature(Notification* aNotification)
+  : mNotification(aNotification)
+{
+  MOZ_ASSERT(mNotification->mWorkerPrivate);
+  mNotification->mWorkerPrivate->AssertIsOnWorkerThread();
+}
+
+/*
+ * Called from the worker, runs on main thread, blocks worker.
+ *
+ * We can freely access mNotification here because the feature supplied it and
+ * the Notification owns the feature.
+ */
+class CloseNotificationRunnable final
+  : public WorkerMainThreadRunnable
+{
+  Notification* mNotification;
+
+  public:
+  explicit CloseNotificationRunnable(Notification* aNotification)
+    : WorkerMainThreadRunnable(aNotification->mWorkerPrivate)
+    , mNotification(aNotification)
+  {}
+
+  bool
+  MainThreadRun() override
+  {
+    if (mNotification->mObserver) {
+      // The Notify() take's responsibility of releasing the Notification.
+      mNotification->mObserver->ForgetNotification();
+      mNotification->mObserver = nullptr;
+    }
+    mNotification->CloseInternal();
+    return true;
+  }
+};
+
+bool
+NotificationFeature::Notify(JSContext* aCx, Status aStatus)
+{
+  MOZ_ASSERT(aStatus >= Canceling);
+
+  // Dispatched to main thread, blocks on closing the Notification.
+  nsRefPtr<CloseNotificationRunnable> r =
+    new CloseNotificationRunnable(mNotification);
+  r->Dispatch(aCx);
+
+  mNotification->ReleaseObject();
+  // From this point we cannot touch properties of this feature because
+  // ReleaseObject() may have led to the notification going away and the
+  // notification owns this feature!
+  return true;
+}
+
+void
+Notification::RegisterFeature()
+{
+  MOZ_ASSERT(mWorkerPrivate);
+  mWorkerPrivate->AssertIsOnWorkerThread();
+  MOZ_ASSERT(!mFeature);
+  // Only calls to show() and close() should lead to AddFeature(), both of
+  // which come from JS. So we can assert that AddFeature() should succeed.
+  mFeature = MakeUnique<NotificationFeature>(this);
+  bool ok =
+    mWorkerPrivate->AddFeature(mWorkerPrivate->GetJSContext(),
+                               mFeature.get());
+  if (!ok) {
+    MOZ_CRASH("AddFeature failed");
+  }
+}
+
+void
+Notification::UnregisterFeature()
+{
+  MOZ_ASSERT(mWorkerPrivate);
+  mWorkerPrivate->AssertIsOnWorkerThread();
+  MOZ_ASSERT(mFeature);
+  mWorkerPrivate->RemoveFeature(mWorkerPrivate->GetJSContext(),
+                                mFeature.get());
+  mFeature = nullptr;
+}
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/notification/Notification.h
+++ b/dom/notification/Notification.h
@@ -3,49 +3,131 @@
 /* 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/. */
 
 #ifndef mozilla_dom_notification_h__
 #define mozilla_dom_notification_h__
 
 #include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/UniquePtr.h"
 #include "mozilla/dom/NotificationBinding.h"
+#include "mozilla/dom/workers/bindings/WorkerFeature.h"
 
 #include "nsIObserver.h"
 
 #include "nsCycleCollectionParticipant.h"
 
 class nsIPrincipal;
 class nsIStructuredCloneContainer;
 class nsIVariant;
 
 namespace mozilla {
 namespace dom {
 
-
-class NotificationObserver;
+class NotificationRef;
+class WorkerNotificationObserver;
 class Promise;
 
+namespace workers {
+  class WorkerPrivate;
+} // namespace workers
+
+class Notification;
+class NotificationFeature final : public workers::WorkerFeature
+{
+  // Held alive by Notification itself before feature is added, and only
+  // released after feature is removed.
+  Notification* mNotification;
+
+public:
+  explicit NotificationFeature(Notification* aNotification);
+
+  bool
+  Notify(JSContext* aCx, workers::Status aStatus) override;
+};
+
+
+/*
+ * Notifications on workers introduce some liveness issues. The property we
+ * are trying to satisfy is:
+ *   Whenever a task is dispatched to the main thread to operate on
+ *   a Notification, the Notification should be addrefed on the worker thread
+ *   and a feature should be added to observe the worker lifetime. This main
+ *   thread owner should ensure it properly releases the reference to the
+ *   Notification, additionally removing the feature if necessary.
+ *
+ * To enforce the correct addref and release, along with managing the feature,
+ * we introduce a NotificationRef. Only one object may ever own
+ * a NotificationRef, so UniquePtr<> is used throughout.  The NotificationRef
+ * constructor calls AddRefObject(). When it is destroyed (on any thread) it
+ * releases the Notification on the correct thread.
+ *
+ * Code should only access the underlying Notification object when it can
+ * guarantee that it retains ownership of the NotificationRef while doing so.
+ *
+ * The one kink in this mechanism is that the worker feature may be Notify()ed
+ * if the worker stops running script, even if the Notification's corresponding
+ * UI is still visible to the user. We handle this case with the following
+ * steps:
+ *   a) Close the notification. This is done by blocking the worker on the main
+ *   thread.
+ *   b) Ask the observer to let go of its NotificationRef's underlying
+ *   Notification without proper cleanup since the feature will handle the
+ *   release. This is only OK because every notification has only one
+ *   associated observer. The NotificationRef itself is still owned by the
+ *   observer and deleted by the UniquePtr, but it doesn't do anything since
+ *   the underlying Notification is null.
+ *
+ * To unify code-paths, we use the same NotificationRef in the main
+ * thread implementation too.
+ *
+ * Note that the Notification's JS wrapper does it's standard
+ * AddRef()/Release() and is not affected by any of this.
+ *
+ * There is one case related to the WorkerNotificationObserver having to
+ * dispatch WorkerRunnables to the worker thread which will use the
+ * Notification object. We can end up in a situation where an event runnable is
+ * dispatched to the worker, gets queued in the worker's event queue, but then,
+ * the worker yields to the main thread. Here the main thread observer is
+ * destroyed, which frees its NotificationRef. The NotificationRef dispatches
+ * a ControlRunnable to the worker, which runs before the event runnable,
+ * leading to the event runnable possibly not having a valid Notification
+ * reference.
+ * We solve this problem by having WorkerNotificationObserver's dtor
+ * dispatching a standard WorkerRunnable to do the release (this guarantees the
+ * ordering of the release is after the event runnables). All WorkerRunnables
+ * that get dispatched successfully are guaranteed to run on the worker before
+ * it shuts down. If that dispatch fails, the standard ControlRunnable based
+ * shutdown is acceptable since the already dispatched event runnables have
+ * already run or canceled (the worker is already past Running).
+ *
+ */
 class Notification : public DOMEventTargetHelper
 {
+  friend class CloseNotificationRunnable;
   friend class NotificationTask;
   friend class NotificationPermissionRequest;
   friend class NotificationObserver;
   friend class NotificationStorageCallback;
+  friend class WorkerNotificationObserver;
 
 public:
   IMPL_EVENT_HANDLER(click)
   IMPL_EVENT_HANDLER(show)
   IMPL_EVENT_HANDLER(error)
   IMPL_EVENT_HANDLER(close)
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Notification, DOMEventTargetHelper)
 
+  static bool PrefEnabled(JSContext* aCx, JSObject* aObj);
+  // Returns if Notification.get() is allowed for the current global.
+  static bool IsGetEnabled(JSContext* aCx, JSObject* aObj);
+
   static already_AddRefed<Notification> Constructor(const GlobalObject& aGlobal,
                                                     const nsAString& aTitle,
                                                     const NotificationOptions& aOption,
                                                     ErrorResult& aRv);
   void GetID(nsAString& aRetval) {
     aRetval = mID;
   }
 
@@ -86,16 +168,18 @@ public:
 
   bool IsStored()
   {
     return mIsStored;
   }
 
   nsIStructuredCloneContainer* GetDataCloneContainer();
 
+  static bool RequestPermissionEnabledForScope(JSContext* aCx, JSObject* /* unused */);
+
   static void RequestPermission(const GlobalObject& aGlobal,
                                 const Optional<OwningNonNull<NotificationPermissionCallback> >& aCallback,
                                 ErrorResult& aRv);
 
   static NotificationPermission GetPermission(const GlobalObject& aGlobal,
                                               ErrorResult& aRv);
 
   static already_AddRefed<Promise> Get(const GlobalObject& aGlobal,
@@ -112,23 +196,46 @@ public:
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   void GetData(JSContext* aCx, JS::MutableHandle<JS::Value> aRetval);
 
   void InitFromJSVal(JSContext* aCx, JS::Handle<JS::Value> aData, ErrorResult& aRv);
 
   void InitFromBase64(JSContext* aCx, const nsAString& aData, ErrorResult& aRv);
 
+  void AssertIsOnTargetThread() const
+  {
+    MOZ_ASSERT(IsTargetThread());
+  }
+
+  workers::WorkerPrivate* mWorkerPrivate;
+  // Main thread only.
+  WorkerNotificationObserver* mObserver;
+
+  // The NotificationTask calls ShowInternal()/CloseInternal() on the
+  // Notification. At this point the task has ownership of the Notification. It
+  // passes this on to the Notification itself via mTempRef so that
+  // ShowInternal()/CloseInternal() may pass it along appropriately (or release
+  // it).
+  UniquePtr<NotificationRef> mTempRef;
+
+  void AddRefObject();
+  void ReleaseObject();
+
+  static NotificationPermission GetPermissionInternal(nsIPrincipal* aPrincipal,
+                                                      ErrorResult& rv);
+
+  bool DispatchClickEvent();
 protected:
   Notification(const nsAString& aID, const nsAString& aTitle, const nsAString& aBody,
                NotificationDirection aDir, const nsAString& aLang,
                const nsAString& aTag, const nsAString& aIconUrl,
-               const NotificationBehavior& aBehavior, nsPIDOMWindow* aWindow);
+               const NotificationBehavior& aBehavior, nsIGlobalObject* aGlobal);
 
-  static already_AddRefed<Notification> CreateInternal(nsPIDOMWindow* aWindow,
+  static already_AddRefed<Notification> CreateInternal(nsIGlobalObject* aGlobal,
                                                        const nsAString& aID,
                                                        const nsAString& aTitle,
                                                        const NotificationOptions& aOptions);
 
   void ShowInternal();
   void CloseInternal();
 
   static NotificationPermission GetPermissionInternal(nsISupports* aGlobal,
@@ -153,50 +260,68 @@ protected:
     }
     if (aDirection.EqualsLiteral("rtl")) {
       return NotificationDirection::Rtl;
     }
     return NotificationDirection::Auto;
   }
 
   static nsresult GetOrigin(nsPIDOMWindow* aWindow, nsString& aOrigin);
+  nsresult GetOriginWorker(nsString& aOrigin);
 
   void GetAlertName(nsAString& aRetval)
   {
     aRetval = mAlertName;
   }
 
-  nsString mID;
-  nsString mTitle;
-  nsString mBody;
-  NotificationDirection mDir;
-  nsString mLang;
-  nsString mTag;
-  nsString mIconUrl;
+  const nsString mID;
+  const nsString mTitle;
+  const nsString mBody;
+  const NotificationDirection mDir;
+  const nsString mLang;
+  const nsString mTag;
+  const nsString mIconUrl;
   nsCOMPtr<nsIStructuredCloneContainer> mDataObjectContainer;
-  NotificationBehavior mBehavior;
+  const NotificationBehavior mBehavior;
 
   // It's null until GetData is first called
   nsCOMPtr<nsIVariant> mData;
 
   nsString mAlertName;
 
+  // Main thread only.
   bool mIsClosed;
 
   // We need to make a distinction between the notification being closed i.e.
   // removed from any pending or active lists, and the notification being
   // removed from the database. NotificationDB might fail when trying to remove
   // the notification.
   bool mIsStored;
 
   static uint32_t sCount;
 
 private:
   virtual ~Notification();
 
   nsIPrincipal* GetPrincipal();
+
+  nsresult PersistNotification();
+  void UnpersistNotification();
+
+  bool IsTargetThread() const
+  {
+    return NS_IsMainThread() == !mWorkerPrivate;
+  }
+
+  void RegisterFeature();
+  void UnregisterFeature();
+
+  nsresult ResolveIconAndSoundURL(nsString&, nsString&);
+
+  UniquePtr<NotificationFeature> mFeature;
+  uint32_t mTaskCount;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_notification_h__
 
--- a/dom/notification/moz.build
+++ b/dom/notification/moz.build
@@ -28,11 +28,12 @@ UNIFIED_SOURCES += [
 FAIL_ON_WARNINGS = True
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
     '/dom/base',
     '/dom/ipc',
+    '/dom/workers',
 ]
 
 XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
--- a/dom/webidl/Notification.webidl
+++ b/dom/webidl/Notification.webidl
@@ -6,27 +6,28 @@
  * The origin of this IDL file is
  * http://notifications.spec.whatwg.org/
  *
  * Copyright:
  * To the extent possible under law, the editors have waived all copyright and
  * related or neighboring rights to this work.
  */
 
-[Pref="dom.webnotifications.enabled",
- Constructor(DOMString title, optional NotificationOptions options),
+[Constructor(DOMString title, optional NotificationOptions options),
+ Exposed=(Window,Worker),
+ Func="mozilla::dom::Notification::PrefEnabled",
  UnsafeInPrerendering]
 interface Notification : EventTarget {
   [GetterThrows]
   static readonly attribute NotificationPermission permission;
 
-  [Throws]
+  [Throws, Func="mozilla::dom::Notification::RequestPermissionEnabledForScope"]
   static void requestPermission(optional NotificationPermissionCallback permissionCallback);
 
-  [Throws]
+  [Throws, Func="mozilla::dom::Notification::IsGetEnabled"]
   static Promise<sequence<Notification>> get(optional GetNotificationOptions filter);
 
   attribute EventHandler onclick;
 
   attribute EventHandler onshow;
 
   attribute EventHandler onerror;
 
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -153,16 +153,17 @@ static_assert(MAX_WORKERS_PER_DOMAIN >= 
 #define PREF_GCZEAL "gcZeal"
 
 #if !(defined(DEBUG) || defined(MOZ_ENABLE_JS_DUMP))
 #define DUMP_CONTROLLED_BY_PREF 1
 #define PREF_DOM_WINDOW_DUMP_ENABLED "browser.dom.window.dump.enabled"
 #endif
 
 #define PREF_DOM_CACHES_ENABLED        "dom.caches.enabled"
+#define PREF_DOM_WORKERNOTIFICATION_ENABLED  "dom.webnotifications.workers.enabled"
 #define PREF_WORKERS_LATEST_JS_VERSION "dom.workers.latestJSVersion"
 #define PREF_INTL_ACCEPT_LANGUAGES     "intl.accept_languages"
 #define PREF_SERVICEWORKERS_ENABLED    "dom.serviceWorkers.enabled"
 #define PREF_INTERCEPTION_ENABLED      "dom.serviceWorkers.interception.enabled"
 
 namespace {
 
 const uint32_t kNoIndex = uint32_t(-1);
@@ -1899,16 +1900,20 @@ RuntimeService::Init()
                                   reinterpret_cast<void *>(WORKERPREF_DUMP))) ||
 #endif
       NS_FAILED(Preferences::RegisterCallbackAndCall(
                                   WorkerPrefChanged,
                                   PREF_DOM_CACHES_ENABLED,
                                   reinterpret_cast<void *>(WORKERPREF_DOM_CACHES))) ||
       NS_FAILED(Preferences::RegisterCallbackAndCall(
                                   WorkerPrefChanged,
+                                  PREF_DOM_WORKERNOTIFICATION_ENABLED,
+                                  reinterpret_cast<void *>(WORKERPREF_DOM_WORKERNOTIFICATION))) ||
+      NS_FAILED(Preferences::RegisterCallbackAndCall(
+                                  WorkerPrefChanged,
                                   PREF_SERVICEWORKERS_ENABLED,
                                   reinterpret_cast<void *>(WORKERPREF_SERVICEWORKERS))) ||
       NS_FAILED(Preferences::RegisterCallbackAndCall(
                                   WorkerPrefChanged,
                                   PREF_INTERCEPTION_ENABLED,
                                   reinterpret_cast<void *>(WORKERPREF_INTERCEPTION_ENABLED))) ||
       NS_FAILED(Preferences::RegisterCallback(LoadRuntimeOptions,
                                               PREF_JS_OPTIONS_PREFIX,
@@ -2112,16 +2117,20 @@ RuntimeService::Cleanup()
         NS_FAILED(Preferences::UnregisterCallback(
                                   WorkerPrefChanged,
                                   PREF_SERVICEWORKERS_ENABLED,
                                   reinterpret_cast<void *>(WORKERPREF_SERVICEWORKERS))) ||
         NS_FAILED(Preferences::UnregisterCallback(
                                   WorkerPrefChanged,
                                   PREF_DOM_CACHES_ENABLED,
                                   reinterpret_cast<void *>(WORKERPREF_DOM_CACHES))) ||
+        NS_FAILED(Preferences::UnregisterCallback(
+                                  WorkerPrefChanged,
+                                  PREF_DOM_WORKERNOTIFICATION_ENABLED,
+                                  reinterpret_cast<void *>(WORKERPREF_DOM_WORKERNOTIFICATION))) ||
 #if DUMP_CONTROLLED_BY_PREF
         NS_FAILED(Preferences::UnregisterCallback(
                                   WorkerPrefChanged,
                                   PREF_DOM_WINDOW_DUMP_ENABLED,
                                   reinterpret_cast<void *>(WORKERPREF_DUMP))) ||
 #endif
 #ifdef JS_GC_ZEAL
         NS_FAILED(Preferences::UnregisterCallback(
@@ -2646,38 +2655,36 @@ RuntimeService::WorkerPrefChanged(const 
   AssertIsOnMainThread();
 
   uintptr_t tmp = reinterpret_cast<uintptr_t>(aClosure);
   MOZ_ASSERT(tmp < WORKERPREF_COUNT);
   WorkerPreference key = static_cast<WorkerPreference>(tmp);
 
 #ifdef DUMP_CONTROLLED_BY_PREF
   if (key == WORKERPREF_DUMP) {
-    key = WORKERPREF_DUMP;
-    sDefaultPreferences[WORKERPREF_DUMP] =
+    sDefaultPreferences[key] =
       Preferences::GetBool(PREF_DOM_WINDOW_DUMP_ENABLED, false);
   }
 #endif
 
   if (key == WORKERPREF_DOM_CACHES) {
-    key = WORKERPREF_DOM_CACHES;
     sDefaultPreferences[WORKERPREF_DOM_CACHES] =
       Preferences::GetBool(PREF_DOM_CACHES_ENABLED, false);
+  } else if (key == WORKERPREF_DOM_WORKERNOTIFICATION) {
+    sDefaultPreferences[key] =
+      Preferences::GetBool(PREF_DOM_WORKERNOTIFICATION_ENABLED, false);
   } else if (key == WORKERPREF_SERVICEWORKERS) {
     key = WORKERPREF_SERVICEWORKERS;
     sDefaultPreferences[WORKERPREF_SERVICEWORKERS] =
       Preferences::GetBool(PREF_SERVICEWORKERS_ENABLED, false);
   } else if (key == WORKERPREF_INTERCEPTION_ENABLED) {
     key = WORKERPREF_INTERCEPTION_ENABLED;
     sDefaultPreferences[key] =
       Preferences::GetBool(PREF_INTERCEPTION_ENABLED, false);
   }
-  // This function should never be registered as a callback for a preference it
-  // does not handle.
-  MOZ_ASSERT(key != WORKERPREF_COUNT);
 
   RuntimeService* rts = RuntimeService::GetService();
   if (rts) {
     rts->UpdateAllWorkerPreference(key, sDefaultPreferences[key]);
   }
 }
 
 void
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -1240,16 +1240,23 @@ public:
   }
 
   bool
   InterceptionEnabled() const
   {
     AssertIsOnWorkerThread();
     return mPreferences[WORKERPREF_INTERCEPTION_ENABLED];
   }
+  
+  bool
+  DOMWorkerNotificationEnabled() const
+  {
+    AssertIsOnWorkerThread();
+    return mPreferences[WORKERPREF_DOM_WORKERNOTIFICATION];
+  }
 
   bool
   OnLine() const
   {
     AssertIsOnWorkerThread();
     return mOnLine;
   }
 
--- a/dom/workers/Workers.h
+++ b/dom/workers/Workers.h
@@ -194,16 +194,17 @@ struct JSSettings
 };
 
 enum WorkerPreference
 {
   WORKERPREF_DUMP = 0, // browser.dom.window.dump.enabled
   WORKERPREF_DOM_CACHES, // dom.caches.enabled
   WORKERPREF_SERVICEWORKERS, // dom.serviceWorkers.enabled
   WORKERPREF_INTERCEPTION_ENABLED, // dom.serviceWorkers.interception.enabled
+  WORKERPREF_DOM_WORKERNOTIFICATION, // dom.webnotifications.workers.enabled
   WORKERPREF_COUNT
 };
 
 // Implemented in WorkerPrivate.cpp
 
 struct WorkerLoadInfo
 {
   // All of these should be released in WorkerPrivateParent::ForgetMainThreadObjects.
--- a/dom/workers/test/mochitest.ini
+++ b/dom/workers/test/mochitest.ini
@@ -36,16 +36,20 @@ support-files =
   loadEncoding_worker.js
   location_worker.js
   longThread_worker.js
   multi_sharedWorker_frame.html
   multi_sharedWorker_sharedWorker.js
   navigator_languages_worker.js
   navigator_worker.js
   newError_worker.js
+  notification_worker.js
+  notification_worker_child-child.js
+  notification_worker_child-parent.js
+  notification_permission_worker.js
   onLine_worker.js
   onLine_worker_child.js
   onLine_worker_head.js
   promise_worker.js
   recursion_worker.js
   recursiveOnerror_worker.js
   relativeLoad_import.js
   relativeLoad_worker.js
@@ -153,16 +157,19 @@ skip-if = (toolkit == 'gonk' && debug) #
 [test_location.html]
 [test_longThread.html]
 [test_multi_sharedWorker.html]
 [test_multi_sharedWorker_lifetimes.html]
 [test_navigator.html]
 [test_navigator_languages.html]
 skip-if = buildapp == 'mulet'
 [test_newError.html]
+[test_notification.html]
+[test_notification_child.html]
+[test_notification_permission.html]
 [test_onLine.html]
 skip-if = (toolkit == 'gonk' && debug) #debug-only failure
 [test_performance_user_timing.html]
 [test_promise.html]
 [test_promise_resolved_with_string.html]
 [test_recursion.html]
 [test_recursiveOnerror.html]
 [test_relativeLoad.html]
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/notification_permission_worker.js
@@ -0,0 +1,56 @@
+function info(message) {
+  dump("INFO: " + message + "\n");
+}
+
+function ok(test, message) {
+  postMessage({ type: 'ok', test: test, message: message });
+}
+
+function is(a, b, message) {
+  postMessage({ type: 'is', test1: a, test2: b, message: message });
+}
+
+if (self.Notification) {
+  var steps = [
+    function (done) {
+      info("Test notification permission");
+      ok(typeof Notification === "function", "Notification constructor exists");
+      ok(Notification.permission === "denied", "Notification.permission is denied.");
+
+      var n = new Notification("Hello");
+      n.onerror = function(e) {
+        ok(true, "error called due to permission denied.");
+        done();
+      }
+    },
+  ];
+
+  onmessage = function(e) {
+    var context = {};
+    (function executeRemainingTests(remainingTests) {
+      if (!remainingTests.length) {
+        postMessage({type: 'finish'});
+        return;
+      }
+
+      var nextTest = remainingTests.shift();
+      var finishTest = executeRemainingTests.bind(null, remainingTests);
+      var startTest = nextTest.call.bind(nextTest, context, finishTest);
+
+      try {
+        startTest();
+        // if no callback was defined for test function,
+        // we must manually invoke finish to continue
+        if (nextTest.length === 0) {
+          finishTest();
+        }
+      } catch (e) {
+        ok(false, "Test threw exception! " + nextTest + " " + e);
+        finishTest();
+      }
+    })(steps);
+  }
+} else {
+  ok(true, "Notifications are not enabled in workers on the platform.");
+  postMessage({ type: 'finish' });
+}
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/notification_worker.js
@@ -0,0 +1,89 @@
+function ok(test, message) {
+  postMessage({ type: 'ok', test: test, message: message });
+}
+
+function is(a, b, message) {
+  postMessage({ type: 'is', test1: a, test2: b, message: message });
+}
+
+if (self.Notification) {
+  var steps = [
+    function () {
+      ok(typeof Notification === "function", "Notification constructor exists");
+      ok(Notification.permission, "Notification.permission exists");
+      ok(typeof Notification.requestPermission === "undefined", "Notification.requestPermission should not exist");
+    },
+
+    function (done) {
+      var options = {
+        dir: "auto",
+        lang: "",
+        body: "This is a notification body",
+        tag: "sometag",
+        icon: "icon.png"
+      };
+      var notification = new Notification("This is a title", options);
+
+      ok(notification !== undefined, "Notification exists");
+      is(notification.onclick, null, "onclick() should be null");
+      is(notification.onshow, null, "onshow() should be null");
+      is(notification.onerror, null, "onerror() should be null");
+      is(notification.onclose, null, "onclose() should be null");
+      is(typeof notification.close, "function", "close() should exist");
+
+      is(notification.dir, options.dir, "auto should get set");
+      is(notification.lang, options.lang, "lang should get set");
+      is(notification.body, options.body, "body should get set");
+      is(notification.tag, options.tag, "tag should get set");
+      is(notification.icon, options.icon, "icon should get set");
+
+      // store notification in test context
+      this.notification = notification;
+
+      notification.onshow = function () {
+        ok(true, "onshow handler should be called");
+        done();
+      };
+    },
+
+    function (done) {
+      var notification = this.notification;
+
+      notification.onclose = function () {
+        ok(true, "onclose handler should be called");
+        done();
+      };
+
+      notification.close();
+    },
+  ];
+
+  onmessage = function(e) {
+    var context = {};
+    (function executeRemainingTests(remainingTests) {
+      if (!remainingTests.length) {
+        postMessage({type: 'finish'});
+        return;
+      }
+
+      var nextTest = remainingTests.shift();
+      var finishTest = executeRemainingTests.bind(null, remainingTests);
+      var startTest = nextTest.call.bind(nextTest, context, finishTest);
+
+      try {
+        startTest();
+        // if no callback was defined for test function,
+        // we must manually invoke finish to continue
+        if (nextTest.length === 0) {
+          finishTest();
+        }
+      } catch (e) {
+        ok(false, "Test threw exception! " + nextTest + " " + e);
+        finishTest();
+      }
+    })(steps);
+  }
+} else {
+  ok(true, "Notifications are not enabled in workers on the platform.");
+  postMessage({ type: 'finish' });
+}
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/notification_worker_child-child.js
@@ -0,0 +1,92 @@
+function ok(test, message) {
+  postMessage({ type: 'ok', test: test, message: message });
+}
+
+function is(a, b, message) {
+  postMessage({ type: 'is', test1: a, test2: b, message: message });
+}
+
+if (self.Notification) {
+  var steps = [
+    function () {
+      ok(typeof Notification === "function", "Notification constructor exists");
+      ok(Notification.permission, "Notification.permission exists");
+      ok(typeof Notification.requestPermission === "undefined", "Notification.requestPermission should not exist");
+      //ok(typeof Notification.get === "function", "Notification.get exists");
+    },
+
+    function (done) {
+
+      var options = {
+        dir: "auto",
+        lang: "",
+        body: "This is a notification body",
+        tag: "sometag",
+        icon: "icon.png"
+      };
+      var notification = new Notification("This is a title", options);
+
+      ok(notification !== undefined, "Notification exists");
+      is(notification.onclick, null, "onclick() should be null");
+      is(notification.onshow, null, "onshow() should be null");
+      is(notification.onerror, null, "onerror() should be null");
+      is(notification.onclose, null, "onclose() should be null");
+      is(typeof notification.close, "function", "close() should exist");
+
+      is(notification.dir, options.dir, "auto should get set");
+      is(notification.lang, options.lang, "lang should get set");
+      is(notification.body, options.body, "body should get set");
+      is(notification.tag, options.tag, "tag should get set");
+      is(notification.icon, options.icon, "icon should get set");
+
+      // store notification in test context
+      this.notification = notification;
+
+      notification.onshow = function () {
+        ok(true, "onshow handler should be called");
+        done();
+      };
+    },
+
+    function (done) {
+      var notification = this.notification;
+
+      notification.onclose = function () {
+        ok(true, "onclose handler should be called");
+        done();
+      };
+
+      notification.close();
+    },
+  ];
+
+  onmessage = function(e) {
+    var context = {};
+    (function executeRemainingTests(remainingTests) {
+      if (!remainingTests.length) {
+        postMessage({type: 'finish'});
+        return;
+      }
+
+      var nextTest = remainingTests.shift();
+      var finishTest = executeRemainingTests.bind(null, remainingTests);
+      var startTest = nextTest.call.bind(nextTest, context, finishTest);
+
+      try {
+        startTest();
+        // if no callback was defined for test function,
+        // we must manually invoke finish to continue
+        if (nextTest.length === 0) {
+          finishTest();
+        }
+      } catch (e) {
+        ok(false, "Test threw exception! " + nextTest + " " + e);
+        finishTest();
+      }
+    })(steps);
+  }
+} else {
+  ok(true, "Notifications are not enabled in workers on the platform.");
+  postMessage({ type: 'finish' });
+}
+
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/notification_worker_child-parent.js
@@ -0,0 +1,26 @@
+function ok(test, message) {
+  postMessage({ type: 'ok', test: test, message: message });
+}
+
+function is(a, b, message) {
+  postMessage({ type: 'is', test1: a, test2: b, message: message });
+}
+
+if (self.Notification) {
+  var child = new Worker("notification_worker_child-child.js");
+  child.onerror = function(e) {
+    ok(false, "Error loading child worker " + e);
+    postMessage({ type: 'finish' });
+  }
+
+  child.onmessage = function(e) {
+    postMessage(e.data);
+  }
+
+  onmessage = function(e) {
+    child.postMessage('start');
+  }
+} else {
+  ok(true, "Notifications are not enabled in workers on the platform.");
+  postMessage({ type: 'finish' });
+}
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/test_notification.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=916893
+-->
+<head>
+  <title>Bug 916893</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/MockServices.js"></script>
+  <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/NotificationTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=916893">Bug 916893</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+<script type="text/javascript">
+  SimpleTest.requestFlakyTimeout("Mock alert service dispatches show event.");
+
+  function runTest() {
+    MockServices.register();
+    var w = new Worker("notification_worker.js");
+    w.onmessage = function(e) {
+      if (e.data.type === 'finish') {
+        MockServices.unregister();
+        SimpleTest.finish();
+      } else if (e.data.type === 'ok') {
+        ok(e.data.test, e.data.message);
+      } else if (e.data.type === 'is') {
+        is(e.data.test1, e.data.test2, e.data.message);
+      }
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    // turn on testing pref (used by notification.cpp, and mock the alerts
+    SpecialPowers.setBoolPref("notification.prompt.testing", true);
+    w.postMessage('start')
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  SpecialPowers.pushPrefEnv(
+    {"set": [["dom.webnotifications.workers.enabled", true]]},
+    runTest
+  );
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/test_notification_child.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=916893
+-->
+<head>
+  <title>Bug 916893 - Test Notifications in child workers.</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/MockServices.js"></script>
+  <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/NotificationTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=916893">Bug 916893</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+<script type="text/javascript">
+  SimpleTest.requestFlakyTimeout("Mock alert service dispatches show event.");
+  function runTest() {
+    MockServices.register();
+    var w = new Worker("notification_worker_child-parent.js");
+    w.onmessage = function(e) {
+      if (e.data.type === 'finish') {
+        MockServices.unregister();
+        SimpleTest.finish();
+      } else if (e.data.type === 'ok') {
+        ok(e.data.test, e.data.message);
+      } else if (e.data.type === 'is') {
+        is(e.data.test1, e.data.test2, e.data.message);
+      }
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    // turn on testing pref (used by notification.cpp, and mock the alerts
+    SpecialPowers.setBoolPref("notification.prompt.testing", true);
+    w.postMessage('start')
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  SpecialPowers.pushPrefEnv(
+    {"set": [["dom.webnotifications.workers.enabled", true]]},
+    runTest
+  );
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/test_notification_permission.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=916893
+-->
+<head>
+  <title>Bug 916893 - Make sure error is fired on Notification if permission is denied.</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/MockServices.js"></script>
+  <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/NotificationTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=916893">Bug 916893</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+<script type="text/javascript">
+  SimpleTest.requestFlakyTimeout("Mock alert service dispatches show event.");
+  function runTest() {
+    MockServices.register();
+    var w = new Worker("notification_permission_worker.js");
+    w.onmessage = function(e) {
+      if (e.data.type === 'finish') {
+        SpecialPowers.setBoolPref("notification.prompt.testing.allow", true);
+        MockServices.unregister();
+        SimpleTest.finish();
+      } else if (e.data.type === 'ok') {
+        ok(e.data.test, e.data.message);
+      } else if (e.data.type === 'is') {
+        is(e.data.test1, e.data.test2, e.data.message);
+      }
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    // turn on testing pref (used by notification.cpp, and mock the alerts
+    SpecialPowers.setBoolPref("notification.prompt.testing", true);
+    SpecialPowers.setBoolPref("notification.prompt.testing.allow", false);
+    w.postMessage('start')
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  SpecialPowers.pushPrefEnv(
+    {"set": [["dom.webnotifications.workers.enabled", true]]},
+    runTest
+  );
+</script>
+</body>
+</html>
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4318,16 +4318,17 @@ pref("extensions.minCompatiblePlatformVe
 pref("network.buffer.cache.count", 24);
 pref("network.buffer.cache.size",  32768);
 
 // Desktop Notification
 pref("notification.feature.enabled", false);
 
 // Web Notification
 pref("dom.webnotifications.enabled", true);
+pref("dom.webnotifications.workers.enabled", false);
 
 // Alert animation effect, name is disableSlidingEffect for backwards-compat.
 pref("alerts.disableSlidingEffect", false);
 
 // DOM full-screen API.
 pref("full-screen-api.enabled", false);
 pref("full-screen-api.allow-trusted-requests-only", true);
 pref("full-screen-api.pointer-lock.enabled", true);