Bug 1155505 - Part 2. Implement native Window Toasts as a notification backend. r=jmathies
☠☠ backed out by 10b62fac5269 ☠ ☠
authorEdouard Oger <eoger@fastmail.com>
Wed, 03 Oct 2018 13:15:43 +0900
changeset 439368 5400ec20792c04d86c825e7debdcddd6f1e3b5f5
parent 439367 82e129f7545e704a77b3a8530124670e409b747f
child 439369 f5e706d5a1433a95a8d3015c684cfef10c13e850
push id34768
push useraciure@mozilla.com
push dateWed, 03 Oct 2018 16:45:02 +0000
treeherdermozilla-central@3530790e23d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjmathies
bugs1155505
milestone64.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 1155505 - Part 2. Implement native Window Toasts as a notification backend. r=jmathies Implemnt notification backend by Windows Toast API that is from Windows 8+. Original patch is me and add some features by eoger. Differential Revision: https://phabricator.services.mozilla.com/D3003
toolkit/library/moz.build
widget/windows/ToastNotification.cpp
widget/windows/ToastNotification.h
widget/windows/ToastNotificationHandler.cpp
widget/windows/ToastNotificationHandler.h
widget/windows/moz.build
widget/windows/nsWidgetFactory.cpp
--- a/toolkit/library/moz.build
+++ b/toolkit/library/moz.build
@@ -305,16 +305,17 @@ if CONFIG['OS_ARCH'] == 'WINNT':
         'secur32',
         'sensorsapi',
         'portabledeviceguids',
         'wininet',
         'wbemuuid',
         'wintrust',
         'wtsapi32',
         'locationapi',
+        'runtimeobject',
         'sapi',
         'dxguid',
         'dhcpcsvc',
     ]
     if CONFIG['ACCESSIBILITY']:
         OS_LIBS += [
             'oleacc',
         ]
new file mode 100644
--- /dev/null
+++ b/widget/windows/ToastNotification.cpp
@@ -0,0 +1,209 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sts=2 sw=2 et cin: */
+/* 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 "ToastNotification.h"
+
+#include "mozilla/WindowsVersion.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCOMPtr.h"
+#include "nsIObserverService.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "ToastNotificationHandler.h"
+#include "WinTaskbar.h"
+
+
+namespace mozilla {
+namespace widget {
+
+NS_IMPL_ISUPPORTS(ToastNotification, nsIAlertsService, nsIObserver, nsISupportsWeakReference)
+
+ToastNotification::ToastNotification()
+= default;
+
+ToastNotification::~ToastNotification()
+= default;
+
+nsresult
+ToastNotification::Init()
+{
+  if (!IsWin8OrLater()) {
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+
+  nsAutoString uid;
+  if (NS_WARN_IF(!WinTaskbar::GetAppUserModelID(uid))) {
+    // Windows Toast Notification requires AppId
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+
+  nsresult rv = NS_NewNamedThread("ToastBgThread", getter_AddRefs(mBackgroundThread));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsCOMPtr<nsIObserverService> obsServ =
+      do_GetService("@mozilla.org/observer-service;1");
+  if (obsServ) {
+    obsServ->AddObserver(this, "quit-application", true);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+ToastNotification::BackgroundDispatch(nsIRunnable* runnable)
+{
+  return mBackgroundThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+ToastNotification::Observe(nsISupports *aSubject, const char *aTopic,
+                           const char16_t *aData)
+{
+  // Got quit-application
+  // The handlers destructors will do the right thing (de-register with Windows).
+  mActiveHandlers.Clear();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ToastNotification::ShowAlertNotification(const nsAString & aImageUrl,
+                                         const nsAString & aAlertTitle,
+                                         const nsAString & aAlertText,
+                                         bool aAlertTextClickable,
+                                         const nsAString & aAlertCookie,
+                                         nsIObserver * aAlertListener,
+                                         const nsAString & aAlertName,
+                                         const nsAString & aBidi,
+                                         const nsAString & aLang,
+                                         const nsAString & aData,
+                                         nsIPrincipal * aPrincipal,
+                                         bool aInPrivateBrowsing,
+                                         bool aRequireInteraction)
+{
+  nsCOMPtr<nsIAlertNotification> alert =
+    do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID);
+  if (NS_WARN_IF(!alert)) {
+    return NS_ERROR_FAILURE;
+  }
+  nsresult rv = alert->Init(aAlertName, aImageUrl, aAlertTitle,
+                            aAlertText, aAlertTextClickable,
+                            aAlertCookie, aBidi, aLang, aData,
+                            aPrincipal, aInPrivateBrowsing,
+                            aRequireInteraction);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  return ShowAlert(alert, aAlertListener);
+}
+
+NS_IMETHODIMP
+ToastNotification::ShowPersistentNotification(const nsAString& aPersistentData,
+                                              nsIAlertNotification* aAlert,
+                                              nsIObserver* aAlertListener)
+{
+  return ShowAlert(aAlert, aAlertListener);
+}
+
+NS_IMETHODIMP
+ToastNotification::ShowAlert(nsIAlertNotification* aAlert,
+                             nsIObserver* aAlertListener)
+{
+  if (NS_WARN_IF(!aAlert)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  nsAutoString cookie;
+  nsresult rv = aAlert->GetCookie(cookie);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsAutoString name;
+  rv = aAlert->GetName(name);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsAutoString title;
+  rv = aAlert->GetTitle(title);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsAutoString text;
+  rv = aAlert->GetText(text);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  bool textClickable;
+  rv = aAlert->GetTextClickable(&textClickable);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsAutoString hostPort;
+  rv = aAlert->GetSource(hostPort);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  RefPtr<ToastNotificationHandler> handler =
+    new ToastNotificationHandler(this, aAlertListener, name, cookie, title,
+                                 text, hostPort, textClickable);
+  mActiveHandlers.Put(name, handler);
+
+  rv = handler->InitAlertAsync(aAlert);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    mActiveHandlers.Remove(name);
+    return rv;
+  }
+
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ToastNotification::CloseAlert(const nsAString& aAlertName,
+                              nsIPrincipal* aPrincipal)
+{
+  RefPtr<ToastNotificationHandler> handler;
+  if (NS_WARN_IF(!mActiveHandlers.Get(aAlertName, getter_AddRefs(handler)))) {
+    return NS_OK;
+  }
+  mActiveHandlers.Remove(aAlertName);
+  return NS_OK;
+}
+
+bool
+ToastNotification::IsActiveHandler(const nsAString& aAlertName,
+                                   ToastNotificationHandler* aHandler)
+{
+  RefPtr<ToastNotificationHandler> handler;
+  if (NS_WARN_IF(!mActiveHandlers.Get(aAlertName, getter_AddRefs(handler)))) {
+    return false;
+  }
+  return handler == aHandler;
+}
+
+void
+ToastNotification::RemoveHandler(const nsAString& aAlertName,
+                                 ToastNotificationHandler* aHandler)
+{
+  // The alert may have been replaced; only remove it from the active
+  // handlers map if it's the same.
+  if (IsActiveHandler(aAlertName, aHandler)) {
+    // Terrible things happen if the destructor of a handler is called inside
+    // the hashtable .Remove() method. Wait until we have returned from there.
+    RefPtr<ToastNotificationHandler> kungFuDeathGrip(aHandler);
+    mActiveHandlers.Remove(aAlertName);
+  }
+}
+
+} // namespace widget
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/widget/windows/ToastNotification.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 ToastNotification_h__
+#define ToastNotification_h__
+
+#include "nsIAlertsService.h"
+#include "nsIObserver.h"
+#include "nsIThread.h"
+#include "nsRefPtrHashtable.h"
+#include "nsWeakReference.h"
+
+namespace mozilla {
+namespace widget {
+
+class ToastNotificationHandler;
+
+class ToastNotification final : public nsIAlertsService,
+                                public nsIObserver,
+                                public nsSupportsWeakReference
+{
+public:
+  NS_DECL_NSIALERTSSERVICE
+  NS_DECL_NSIOBSERVER
+  NS_DECL_ISUPPORTS
+
+  ToastNotification();
+
+  nsresult Init();
+
+  bool IsActiveHandler(const nsAString& aAlertName,
+                       ToastNotificationHandler* aHandler);
+  void RemoveHandler(const nsAString& aAlertName,
+                     ToastNotificationHandler* aHandler);
+
+  nsresult BackgroundDispatch(nsIRunnable* runnable);
+
+protected:
+  virtual ~ToastNotification();
+
+  nsRefPtrHashtable<nsStringHashKey, ToastNotificationHandler> mActiveHandlers;
+
+  nsCOMPtr<nsIThread> mBackgroundThread;
+};
+
+} // widget
+} // mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/widget/windows/ToastNotificationHandler.cpp
@@ -0,0 +1,575 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sts=2 sw=2 et cin: */
+/* 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 "ToastNotificationHandler.h"
+
+#include "imgIRequest.h"
+#include "mozilla/gfx/2D.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIStringBundle.h"
+#include "nsIURI.h"
+#include "nsIUUIDGenerator.h"
+#include "nsNetUtil.h"
+#include "nsProxyRelease.h"
+#include "WinTaskbar.h"
+#include "WinUtils.h"
+
+#include "ToastNotification.h"
+
+namespace mozilla {
+namespace widget {
+
+typedef ABI::Windows::Foundation::ITypedEventHandler<
+  ABI::Windows::UI::Notifications::ToastNotification*, IInspectable*>
+  ToastActivationHandler;
+typedef ABI::Windows::Foundation::ITypedEventHandler<
+  ABI::Windows::UI::Notifications::ToastNotification*,
+  ABI::Windows::UI::Notifications::ToastDismissedEventArgs*> ToastDismissedHandler;
+typedef ABI::Windows::Foundation::ITypedEventHandler<
+  ABI::Windows::UI::Notifications::ToastNotification*,
+  ABI::Windows::UI::Notifications::ToastFailedEventArgs*> ToastFailedHandler;
+
+using namespace ABI::Windows::Data::Xml::Dom;
+using namespace ABI::Windows::Foundation;
+using namespace ABI::Windows::UI::Notifications;
+using namespace Microsoft::WRL;
+using namespace Microsoft::WRL::Wrappers;
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(ToastNotificationHandler, nsIAlertNotificationImageListener)
+
+static bool
+SetNodeValueString(const nsString& aString,
+                   IXmlNode* node,
+                   IXmlDocument* xml)
+{
+  ComPtr<IXmlText> inputText;
+  if (NS_WARN_IF(FAILED(xml->CreateTextNode(
+        HStringReference(static_cast<const wchar_t*>(aString.get())).Get(),
+        &inputText)))) {
+    return false;
+  }
+  ComPtr<IXmlNode> inputTextNode;
+  if (NS_WARN_IF(FAILED(inputText.As(&inputTextNode)))) {
+    return false;
+  }
+  ComPtr<IXmlNode> appendedChild;
+  if (NS_WARN_IF(FAILED(node->AppendChild(inputTextNode.Get(),
+                                          &appendedChild)))) {
+    return false;
+  }
+  return true;
+}
+
+static bool
+SetAttribute(IXmlElement* element, const HSTRING name,
+             const nsAString& value)
+{
+  HSTRING valueStr =
+    HStringReference(
+      static_cast<const wchar_t*>(PromiseFlatString(value).get())).Get();
+  if (NS_WARN_IF(FAILED(element->SetAttribute(name, valueStr)))) {
+    return false;
+  }
+  return true;
+}
+
+static bool
+AddActionNode(IXmlDocument* toastXml, IXmlNode* actionsNode,
+              const nsAString& actionTitle, const nsAString& actionArgs)
+{
+  ComPtr<IXmlElement> action;
+  HRESULT hr = toastXml->CreateElement(HStringReference(L"action").Get(),
+                                       &action);
+  if (NS_WARN_IF(FAILED(hr))) {
+    return false;
+  }
+
+  if (NS_WARN_IF(!SetAttribute(action.Get(), HStringReference(L"content").Get(),
+                               actionTitle))) {
+    return false;
+  }
+
+  if (NS_WARN_IF(!SetAttribute(action.Get(),
+                               HStringReference(L"arguments").Get(),
+                               actionArgs))) {
+    return false;
+  }
+  if (NS_WARN_IF(!SetAttribute(action.Get(),
+                               HStringReference(L"placement").Get(),
+                               NS_LITERAL_STRING("contextmenu")))) {
+    return false;
+  }
+
+  // Add <action> to <actions>
+  ComPtr<IXmlNode> actionNode;
+  hr = action.As(&actionNode);
+  if (NS_WARN_IF(FAILED(hr))) {
+    return false;
+  }
+
+  ComPtr<IXmlNode> appendedChild;
+  hr = actionsNode->AppendChild(actionNode.Get(), &appendedChild);
+  if (NS_WARN_IF(FAILED(hr))) {
+    return false;
+  }
+
+  return true;
+}
+
+static ComPtr<IToastNotificationManagerStatics>
+GetToastNotificationManagerStatics()
+{
+  ComPtr<IToastNotificationManagerStatics> toastNotificationManagerStatics;
+  if (NS_WARN_IF(FAILED(GetActivationFactory(
+                          HStringReference(
+                            RuntimeClass_Windows_UI_Notifications_ToastNotificationManager).Get(),
+                          &toastNotificationManagerStatics)))) {
+    return nullptr;
+  }
+
+  return toastNotificationManagerStatics;
+}
+
+ToastNotificationHandler::~ToastNotificationHandler()
+{
+  if (mImageRequest) {
+    mImageRequest->Cancel(NS_BINDING_ABORTED);
+    mImageRequest = nullptr;
+  }
+
+  if (mHasImage) {
+    DebugOnly<nsresult> rv = mImageFile->Remove(false);
+    NS_ASSERTION(NS_SUCCEEDED(rv), "Cannot remove temporary image file");
+  }
+
+  if (mNotification && mNotifier) {
+    mNotification->remove_Dismissed(mDismissedToken);
+    mNotification->remove_Activated(mActivatedToken);
+    mNotification->remove_Failed(mFailedToken);
+    mNotifier->Hide(mNotification.Get());
+  }
+}
+
+ComPtr<IXmlDocument>
+ToastNotificationHandler::InitializeXmlForTemplate(
+  ToastTemplateType templateType)
+{
+  ComPtr<IToastNotificationManagerStatics> toastNotificationManagerStatics =
+    GetToastNotificationManagerStatics();
+
+  ComPtr<IXmlDocument> toastXml;
+  toastNotificationManagerStatics->GetTemplateContent(templateType, &toastXml);
+
+  return toastXml;
+}
+
+nsresult
+ToastNotificationHandler::InitAlertAsync(nsIAlertNotification* aAlert)
+{
+  return aAlert->LoadImage(/* aTimeout = */ 0, this, /* aUserData = */ nullptr,
+                           getter_AddRefs(mImageRequest));
+}
+
+bool
+ToastNotificationHandler::ShowAlert()
+{
+  if (!mBackend->IsActiveHandler(mName, this)) {
+    return true;
+  }
+
+  ComPtr<IXmlDocument> toastXml =
+    InitializeXmlForTemplate(
+      !mHasImage ?
+        ToastTemplateType::ToastTemplateType_ToastText03 :
+        ToastTemplateType::ToastTemplateType_ToastImageAndText03);
+
+  if (!toastXml) {
+    return false;
+  }
+
+  HRESULT hr;
+
+  if (mHasImage) {
+    ComPtr<IXmlNodeList> toastImageElements;
+    hr = toastXml->GetElementsByTagName(HStringReference(L"image").Get(),
+                                        &toastImageElements);
+    if (NS_WARN_IF(FAILED(hr))) {
+      return false;
+    }
+    ComPtr<IXmlNode> imageNode;
+    hr = toastImageElements->Item(0, &imageNode);
+    if (NS_WARN_IF(FAILED(hr))) {
+      return false;
+    }
+    ComPtr<IXmlElement> image;
+    hr = imageNode.As(&image);
+    if (NS_WARN_IF(FAILED(hr))) {
+      return false;
+    }
+    if (NS_WARN_IF(!SetAttribute(image.Get(),
+                                 HStringReference(L"src").Get(),
+                                 mImageUri))) {
+      return false;
+    }
+  }
+
+  ComPtr<IXmlNodeList> toastTextElements;
+  hr = toastXml->GetElementsByTagName(HStringReference(L"text").Get(),
+                                      &toastTextElements);
+  if (NS_WARN_IF(FAILED(hr))) {
+    return false;
+  }
+
+  ComPtr<IXmlNode> titleTextNodeRoot;
+  hr = toastTextElements->Item(0, &titleTextNodeRoot);
+  if (NS_WARN_IF(FAILED(hr))) {
+    return false;
+  }
+  ComPtr<IXmlNode> msgTextNodeRoot;
+  hr = toastTextElements->Item(1, &msgTextNodeRoot);
+  if (NS_WARN_IF(FAILED(hr))) {
+    return false;
+  }
+
+  if (NS_WARN_IF(!SetNodeValueString(mTitle, titleTextNodeRoot.Get(),
+                                     toastXml.Get()))) {
+    return false;
+  }
+  if (NS_WARN_IF(!SetNodeValueString(mMsg, msgTextNodeRoot.Get(),
+                                     toastXml.Get()))) {
+    return false;
+  }
+
+  ComPtr<IXmlNodeList> toastElements;
+  hr = toastXml->GetElementsByTagName(HStringReference(L"toast").Get(),
+                                      &toastElements);
+  if (NS_WARN_IF(FAILED(hr))) {
+    return false;
+  }
+
+  ComPtr<IXmlNode> toastNodeRoot;
+  hr = toastElements->Item(0, &toastNodeRoot);
+  if (NS_WARN_IF(FAILED(hr))) {
+    return false;
+  }
+
+  ComPtr<IXmlElement> actions;
+  hr = toastXml->CreateElement(HStringReference(L"actions").Get(), &actions);
+  if (NS_WARN_IF(FAILED(hr))) {
+    return false;
+  }
+
+  ComPtr<IXmlNode> actionsNode;
+  hr = actions.As(&actionsNode);
+  if (NS_WARN_IF(FAILED(hr))) {
+    return false;
+  }
+
+  nsCOMPtr<nsIStringBundleService> sbs =
+    do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+  if (NS_WARN_IF(!sbs)) {
+    return false;
+  }
+
+  nsCOMPtr<nsIStringBundle> bundle;
+  sbs->CreateBundle("chrome://alerts/locale/alert.properties",
+                    getter_AddRefs(bundle));
+  if (NS_WARN_IF(!bundle)) {
+    return false;
+  }
+
+  if (!mHostPort.IsEmpty()) {
+    nsAutoString disableButtonTitle;
+    const char16_t* formatStrings[] = { mHostPort.get() };
+    bundle->FormatStringFromName("webActions.disableForOrigin.label",
+                                 formatStrings,
+                                 ArrayLength(formatStrings),
+                                 disableButtonTitle);
+
+    AddActionNode(toastXml.Get(), actionsNode.Get(), disableButtonTitle,
+                  NS_LITERAL_STRING("snooze"));
+  }
+
+  nsAutoString settingsButtonTitle;
+  bundle->GetStringFromName("webActions.settings.label", settingsButtonTitle);
+  AddActionNode(toastXml.Get(), actionsNode.Get(), settingsButtonTitle,
+                NS_LITERAL_STRING("settings"));
+
+  ComPtr<IXmlNode> appendedChild;
+  hr = toastNodeRoot->AppendChild(actionsNode.Get(), &appendedChild);
+  if (NS_WARN_IF(FAILED(hr))) {
+    return false;
+  }
+
+  return CreateWindowsNotificationFromXml(toastXml.Get());
+}
+
+bool
+ToastNotificationHandler::CreateWindowsNotificationFromXml(
+  IXmlDocument *aXml)
+{
+  ComPtr<IToastNotificationFactory> factory;
+  HRESULT hr =
+    GetActivationFactory(
+      HStringReference(
+        RuntimeClass_Windows_UI_Notifications_ToastNotification).Get(),
+      &factory);
+  if (NS_WARN_IF(FAILED(hr))) {
+    return false;
+  }
+
+  hr = factory->CreateToastNotification(aXml, &mNotification);
+  if (NS_WARN_IF(FAILED(hr))) {
+    return false;
+  }
+
+  hr = mNotification->add_Activated(
+         Callback<ToastActivationHandler>(
+           this, &ToastNotificationHandler::OnActivate).Get(),
+         &mActivatedToken);
+  if (NS_WARN_IF(FAILED(hr))) {
+    return false;
+  }
+
+  hr = mNotification->add_Dismissed(
+         Callback<ToastDismissedHandler>(
+           this, &ToastNotificationHandler::OnDismiss).Get(),
+         &mDismissedToken);
+  if (NS_WARN_IF(FAILED(hr))) {
+    return false;
+  }
+
+  hr = mNotification->add_Failed(
+         Callback<ToastFailedHandler>(
+           this, &ToastNotificationHandler::OnFail).Get(),
+         &mFailedToken);
+  if (NS_WARN_IF(FAILED(hr))) {
+    return false;
+  }
+
+  ComPtr<IToastNotificationManagerStatics> toastNotificationManagerStatics =
+    GetToastNotificationManagerStatics();
+  if (NS_WARN_IF(!toastNotificationManagerStatics)) {
+    return false;
+  }
+
+  nsAutoString uid;
+  if (NS_WARN_IF(!WinTaskbar::GetAppUserModelID(uid))) {
+    return false;
+  }
+
+  HSTRING uidStr =
+    HStringReference(static_cast<const wchar_t*>(uid.get())).Get();
+  hr = toastNotificationManagerStatics->CreateToastNotifierWithId(uidStr,
+                                                                  &mNotifier);
+  if (NS_WARN_IF(FAILED(hr))) {
+    return false;
+  }
+
+  hr = mNotifier->Show(mNotification.Get());
+  if (NS_WARN_IF(FAILED(hr))) {
+    return false;
+  }
+
+  if (mAlertListener) {
+    mAlertListener->Observe(nullptr, "alertshow", mCookie.get());
+  }
+
+  return true;
+}
+
+HRESULT
+ToastNotificationHandler::OnActivate(IToastNotification *notification,
+                                     IInspectable *inspectable)
+{
+  if (mAlertListener) {
+    ComPtr<IToastActivatedEventArgs> eventArgs;
+    HRESULT hr = inspectable->QueryInterface(__uuidof(IToastActivatedEventArgs),
+                                           (void**)&eventArgs);
+    nsAutoString argString;
+    if (SUCCEEDED(hr)) {
+      HSTRING arguments;
+      hr = eventArgs->get_Arguments(&arguments);
+      if (SUCCEEDED(hr)) {
+        uint32_t len = 0;
+        const wchar_t* buffer = WindowsGetStringRawBuffer(arguments, &len);
+        if (buffer) {
+          argString.Assign(buffer, len);
+        }
+      }
+    }
+
+    if (argString.EqualsLiteral("settings")) {
+      mAlertListener->Observe(nullptr, "alertsettingscallback", mCookie.get());
+    } else if (argString.EqualsLiteral("snooze")) {
+      mAlertListener->Observe(nullptr, "alertdisablecallback", mCookie.get());
+    } else if (mClickable) {
+      mAlertListener->Observe(nullptr, "alertclickcallback", mCookie.get());
+    }
+  }
+  mBackend->RemoveHandler(mName, this);
+  return S_OK;
+}
+
+HRESULT
+ToastNotificationHandler::OnDismiss(IToastNotification *notification,
+                                    IToastDismissedEventArgs* aArgs)
+{
+  if (mAlertListener) {
+    mAlertListener->Observe(nullptr, "alertfinished", mCookie.get());
+  }
+  mBackend->RemoveHandler(mName, this);
+  return S_OK;
+}
+
+HRESULT
+ToastNotificationHandler::OnFail(IToastNotification *notification,
+                                 IToastFailedEventArgs* aArgs)
+{
+  if (mAlertListener) {
+    mAlertListener->Observe(nullptr, "alertfinished", mCookie.get());
+  }
+  mBackend->RemoveHandler(mName, this);
+  return S_OK;
+}
+
+nsresult
+ToastNotificationHandler::TryShowAlert()
+{
+  if (NS_WARN_IF(!ShowAlert())) {
+    mBackend->RemoveHandler(mName, this);
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+NS_IMETHODIMP
+ToastNotificationHandler::OnImageMissing(nsISupports*)
+{
+  return TryShowAlert();
+}
+
+NS_IMETHODIMP
+ToastNotificationHandler::OnImageReady(nsISupports*, imgIRequest* aRequest)
+{
+  nsresult rv = AsyncSaveImage(aRequest);
+  if (NS_FAILED(rv)) {
+    return TryShowAlert();
+  }
+  return rv;
+}
+
+nsresult
+ToastNotificationHandler::AsyncSaveImage(imgIRequest* aRequest)
+{
+  nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR,
+                                       getter_AddRefs(mImageFile));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = mImageFile->Append(NS_LITERAL_STRING("notificationimages"));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = mImageFile->Create(nsIFile::DIRECTORY_TYPE, 0500);
+  if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS) {
+    return rv;
+  }
+
+  nsCOMPtr<nsIUUIDGenerator> idGen =
+    do_GetService("@mozilla.org/uuid-generator;1", &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsID uuid;
+  rv = idGen->GenerateUUIDInPlace(&uuid);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  char uuidChars[NSID_LENGTH];
+  uuid.ToProvidedString(uuidChars);
+  // Remove the brackets at the beginning and ending of the generated UUID.
+  nsAutoCString uuidStr(Substring(uuidChars + 1, uuidChars + NSID_LENGTH - 2));
+  uuidStr.AppendLiteral(".bmp");
+  mImageFile->AppendNative(uuidStr);
+
+  nsCOMPtr<imgIContainer> imgContainer;
+  rv = aRequest->GetImage(getter_AddRefs(imgContainer));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsMainThreadPtrHandle<ToastNotificationHandler> self(
+    new nsMainThreadPtrHolder<ToastNotificationHandler>(
+      "ToastNotificationHandler", this));
+
+  nsCOMPtr<nsIFile> imageFile(mImageFile);
+  RefPtr<mozilla::gfx::SourceSurface> surface =
+    imgContainer->GetFrame(imgIContainer::FRAME_FIRST,
+                           imgIContainer::FLAG_SYNC_DECODE);
+  nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+    "ToastNotificationHandler::AsyncWriteBitmap",
+    [self, imageFile, surface]() -> void {
+
+      nsresult rv;
+      if (!surface) {
+        rv = NS_ERROR_FAILURE;
+      } else {
+        rv = WinUtils::WriteBitmap(imageFile, surface);
+      }
+
+      nsCOMPtr<nsIRunnable> cbRunnable = NS_NewRunnableFunction(
+        "ToastNotificationHandler::AsyncWriteBitmapCb",
+        [self, rv]() -> void {
+          auto handler = const_cast<ToastNotificationHandler*>(self.get());
+          handler->OnWriteBitmapFinished(rv);
+        });
+
+      NS_DispatchToMainThread(cbRunnable);
+    });
+
+  return mBackend->BackgroundDispatch(r);
+}
+
+void
+ToastNotificationHandler::OnWriteBitmapFinished(nsresult rv)
+{
+  if (NS_SUCCEEDED(rv)) {
+    OnWriteBitmapSuccess();
+  }
+  TryShowAlert();
+}
+
+nsresult
+ToastNotificationHandler::OnWriteBitmapSuccess()
+{
+  nsresult rv;
+
+  nsCOMPtr<nsIURI> fileURI;
+  rv = NS_NewFileURI(getter_AddRefs(fileURI), mImageFile);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsAutoCString uriStr;
+  rv = fileURI->GetSpec(uriStr);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  AppendUTF8toUTF16(uriStr, mImageUri);
+
+  mHasImage = true;
+
+  return NS_OK;
+}
+
+} // namespace widget
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/widget/windows/ToastNotificationHandler.h
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 ToastNotificationHandler_h__
+#define ToastNotificationHandler_h__
+
+#include <windows.ui.notifications.h>
+#include <windows.data.xml.dom.h>
+#include <wrl.h>
+#include "imgIContainer.h"
+#include "nsCOMPtr.h"
+#include "nsIAlertsService.h"
+#include "nsICancelable.h"
+#include "nsIFile.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace widget {
+
+class ToastNotification;
+
+class ToastNotificationHandler final : public nsIAlertNotificationImageListener
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIALERTNOTIFICATIONIMAGELISTENER
+
+  ToastNotificationHandler(ToastNotification* backend, nsIObserver* aAlertListener,
+                           const nsAString& aName, const nsAString& aCookie,
+                           const nsAString& aTitle, const nsAString& aMsg,
+                           const nsAString& aHostPort,
+                           bool aClickable)
+    : mBackend(backend)
+    , mHasImage(false)
+    , mAlertListener(aAlertListener)
+    , mName(aName)
+    , mCookie(aCookie)
+    , mTitle(aTitle)
+    , mMsg(aMsg)
+    , mHostPort(aHostPort)
+    , mClickable(aClickable)
+  {
+  }
+
+  nsresult InitAlertAsync(nsIAlertNotification* aAlert);
+
+  void OnWriteBitmapFinished(nsresult rv);
+
+protected:
+  virtual ~ToastNotificationHandler();
+
+  typedef ABI::Windows::Data::Xml::Dom::IXmlDocument IXmlDocument;
+  typedef ABI::Windows::UI::Notifications::IToastNotifier
+          IToastNotifier;
+  typedef ABI::Windows::UI::Notifications::IToastNotification
+          IToastNotification;
+  typedef ABI::Windows::UI::Notifications::IToastDismissedEventArgs
+          IToastDismissedEventArgs;
+  typedef ABI::Windows::UI::Notifications::IToastFailedEventArgs
+          IToastFailedEventArgs;
+  typedef ABI::Windows::UI::Notifications::ToastTemplateType ToastTemplateType;
+
+  Microsoft::WRL::ComPtr<IToastNotification> mNotification;
+  Microsoft::WRL::ComPtr<IToastNotifier> mNotifier;
+
+  RefPtr<ToastNotification> mBackend;
+
+  nsCOMPtr<nsICancelable> mImageRequest;
+  nsCOMPtr<nsIFile> mImageFile;
+  nsString mImageUri;
+  bool mHasImage;
+
+  EventRegistrationToken mActivatedToken;
+  EventRegistrationToken mDismissedToken;
+  EventRegistrationToken mFailedToken;
+
+  nsCOMPtr<nsIObserver> mAlertListener;
+  nsString mName;
+  nsString mCookie;
+  nsString mTitle;
+  nsString mMsg;
+  nsString mHostPort;
+  bool mClickable;
+
+  nsresult TryShowAlert();
+  bool ShowAlert();
+  nsresult AsyncSaveImage(imgIRequest* aRequest);
+  nsresult OnWriteBitmapSuccess();
+
+  bool CreateWindowsNotificationFromXml(IXmlDocument* aToastXml);
+  Microsoft::WRL::ComPtr<IXmlDocument> InitializeXmlForTemplate(
+                         ToastTemplateType templateType);
+
+  HRESULT OnActivate(IToastNotification *notification,
+                     IInspectable *inspectable);
+  HRESULT OnDismiss(IToastNotification *notification,
+                    IToastDismissedEventArgs* aArgs);
+  HRESULT OnFail(IToastNotification *notification,
+                 IToastFailedEventArgs* aArgs);
+};
+
+} // widget
+} // mozilla
+
+#endif
--- a/widget/windows/moz.build
+++ b/widget/windows/moz.build
@@ -89,16 +89,18 @@ UNIFIED_SOURCES += [
 ]
 
 # The following files cannot be built in unified mode because of name clashes.
 SOURCES += [
     'JumpListBuilder.cpp',
     'nsBidiKeyboard.cpp',
     'nsFilePicker.cpp',
     'nsWidgetFactory.cpp',
+    'ToastNotification.cpp',
+    'ToastNotificationHandler.cpp',
     'WinCompositorWidget.cpp',
     'WindowsUIUtils.cpp',
     'WinMouseScrollHandler.cpp',
 ]
 
 # Needs INITGUID and we don't allow INITGUID in unified sources since bug 970429.
 SOURCES += [
     'InputDeviceUtils.cpp',
@@ -166,12 +168,14 @@ RESFILE = 'widget.res'
 CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
 
 OS_LIBS += [
     'rpcrt4',
 ]
 
 if CONFIG['CC_TYPE'] in ('msvc', 'clang-cl'):
     # C5038: Suppress initializer list order warnings from wrl.h
+    SOURCES['ToastNotification.cpp'].flags += ['-wd5038']
+    SOURCES['ToastNotificationHandler.cpp'].flags += ['-wd5038']
     SOURCES['WindowsUIUtils.cpp'].flags += ['-wd5038']
 
 if CONFIG['CC_TYPE'] == 'clang-cl':
     AllowCompilerWarnings()  # workaround for bug 1090497
--- a/widget/windows/nsWidgetFactory.cpp
+++ b/widget/windows/nsWidgetFactory.cpp
@@ -37,16 +37,19 @@
 #include "nsDragService.h"
 #include "nsTransferable.h"
 #include "nsHTMLFormatConverter.h"
 
 #include "WinTaskbar.h"
 #include "JumpListBuilder.h"
 #include "JumpListItem.h"
 #include "TaskbarPreview.h"
+// Toast notification support
+#include "ToastNotification.h"
+#include "nsToolkitCompsCID.h"
 
 #include "WindowsUIUtils.h"
 
 #ifdef NS_PRINTING
 #include "nsDeviceContextSpecWin.h"
 #include "nsPrintDialogWin.h"
 #include "nsPrintSettingsServiceWin.h"
 #include "nsPrintSession.h"
@@ -105,16 +108,17 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(JumpListB
 NS_GENERIC_FACTORY_CONSTRUCTOR(JumpListItem)
 NS_GENERIC_FACTORY_CONSTRUCTOR(JumpListSeparator)
 NS_GENERIC_FACTORY_CONSTRUCTOR(JumpListLink)
 NS_GENERIC_FACTORY_CONSTRUCTOR(JumpListShortcut)
 NS_GENERIC_FACTORY_CONSTRUCTOR(WindowsUIUtils)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsTransferable)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsHTMLFormatConverter)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsDragService)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(ToastNotification, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR(TaskbarPreviewCallback)
 #ifdef NS_PRINTING
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintDialogServiceWin, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintSettingsServiceWin, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsPrinterEnumeratorWin)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintSession, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsDeviceContextSpecWin)
 #endif
@@ -140,16 +144,17 @@ NS_DEFINE_NAMED_CID(NS_HTMLFORMATCONVERT
 NS_DEFINE_NAMED_CID(NS_WIN_TASKBAR_CID);
 NS_DEFINE_NAMED_CID(NS_WIN_JUMPLISTBUILDER_CID);
 NS_DEFINE_NAMED_CID(NS_WIN_JUMPLISTITEM_CID);
 NS_DEFINE_NAMED_CID(NS_WIN_JUMPLISTSEPARATOR_CID);
 NS_DEFINE_NAMED_CID(NS_WIN_JUMPLISTLINK_CID);
 NS_DEFINE_NAMED_CID(NS_WIN_JUMPLISTSHORTCUT_CID);
 NS_DEFINE_NAMED_CID(NS_WINDOWS_UIUTILS_CID);
 NS_DEFINE_NAMED_CID(NS_DRAGSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_SYSTEMALERTSSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_TASKBARPREVIEWCALLBACK_CID);
 #ifdef NS_PRINTING
 NS_DEFINE_NAMED_CID(NS_PRINTDIALOGSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_PRINTSETTINGSSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_PRINTER_ENUMERATOR_CID);
 NS_DEFINE_NAMED_CID(NS_PRINTSESSION_CID);
 NS_DEFINE_NAMED_CID(NS_DEVICE_CONTEXT_SPEC_CID);
 #endif
@@ -171,16 +176,17 @@ static const mozilla::Module::CIDEntry k
   { &kNS_WIN_TASKBAR_CID, false, nullptr, WinTaskbarConstructor },
   { &kNS_WIN_JUMPLISTBUILDER_CID, false, nullptr, JumpListBuilderConstructor },
   { &kNS_WIN_JUMPLISTITEM_CID, false, nullptr, JumpListItemConstructor },
   { &kNS_WIN_JUMPLISTSEPARATOR_CID, false, nullptr, JumpListSeparatorConstructor },
   { &kNS_WIN_JUMPLISTLINK_CID, false, nullptr, JumpListLinkConstructor },
   { &kNS_WIN_JUMPLISTSHORTCUT_CID, false, nullptr, JumpListShortcutConstructor },
   { &kNS_WINDOWS_UIUTILS_CID, false, nullptr, WindowsUIUtilsConstructor },
   { &kNS_DRAGSERVICE_CID, false, nullptr, nsDragServiceConstructor, Module::MAIN_PROCESS_ONLY },
+  { &kNS_SYSTEMALERTSSERVICE_CID, false, nullptr, ToastNotificationConstructor, Module::MAIN_PROCESS_ONLY },
   { &kNS_TASKBARPREVIEWCALLBACK_CID, false, nullptr, TaskbarPreviewCallbackConstructor },
 #ifdef NS_PRINTING
   { &kNS_PRINTDIALOGSERVICE_CID, false, nullptr, nsPrintDialogServiceWinConstructor, Module::MAIN_PROCESS_ONLY },
   { &kNS_PRINTSETTINGSSERVICE_CID, false, nullptr, nsPrintSettingsServiceWinConstructor },
   { &kNS_PRINTER_ENUMERATOR_CID, false, nullptr, nsPrinterEnumeratorWinConstructor },
   { &kNS_PRINTSESSION_CID, false, nullptr, nsPrintSessionConstructor },
   { &kNS_DEVICE_CONTEXT_SPEC_CID, false, nullptr, nsDeviceContextSpecWinConstructor },
 #endif
@@ -202,16 +208,17 @@ static const mozilla::Module::ContractID
   { "@mozilla.org/windows-taskbar;1", &kNS_WIN_TASKBAR_CID },
   { "@mozilla.org/windows-jumplistbuilder;1", &kNS_WIN_JUMPLISTBUILDER_CID },
   { "@mozilla.org/windows-jumplistitem;1", &kNS_WIN_JUMPLISTITEM_CID },
   { "@mozilla.org/windows-jumplistseparator;1", &kNS_WIN_JUMPLISTSEPARATOR_CID },
   { "@mozilla.org/windows-jumplistlink;1", &kNS_WIN_JUMPLISTLINK_CID },
   { "@mozilla.org/windows-jumplistshortcut;1", &kNS_WIN_JUMPLISTSHORTCUT_CID },
   { "@mozilla.org/windows-ui-utils;1", &kNS_WINDOWS_UIUTILS_CID },
   { "@mozilla.org/widget/dragservice;1", &kNS_DRAGSERVICE_CID, Module::MAIN_PROCESS_ONLY },
+  { NS_SYSTEMALERTSERVICE_CONTRACTID, &kNS_SYSTEMALERTSSERVICE_CID, Module::MAIN_PROCESS_ONLY },
   { "@mozilla.org/widget/taskbar-preview-callback;1", &kNS_TASKBARPREVIEWCALLBACK_CID },
 #ifdef NS_PRINTING
   { NS_PRINTDIALOGSERVICE_CONTRACTID, &kNS_PRINTDIALOGSERVICE_CID },
   { "@mozilla.org/gfx/printsettings-service;1", &kNS_PRINTSETTINGSSERVICE_CID },
   { "@mozilla.org/gfx/printerenumerator;1", &kNS_PRINTER_ENUMERATOR_CID },
   { "@mozilla.org/gfx/printsession;1", &kNS_PRINTSESSION_CID },
   { "@mozilla.org/gfx/devicecontextspec;1", &kNS_DEVICE_CONTEXT_SPEC_CID },
 #endif