summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/notificationserver
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /toolkit/mozapps/notificationserver
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--toolkit/mozapps/notificationserver/EventLog.h16
-rw-r--r--toolkit/mozapps/notificationserver/NotificationCallback.cpp264
-rw-r--r--toolkit/mozapps/notificationserver/NotificationCallback.h72
-rw-r--r--toolkit/mozapps/notificationserver/NotificationComServer.cpp132
-rw-r--r--toolkit/mozapps/notificationserver/NotificationFactory.cpp33
-rw-r--r--toolkit/mozapps/notificationserver/NotificationFactory.h31
-rw-r--r--toolkit/mozapps/notificationserver/moz.build33
-rw-r--r--toolkit/mozapps/notificationserver/notificationserver.def6
8 files changed, 587 insertions, 0 deletions
diff --git a/toolkit/mozapps/notificationserver/EventLog.h b/toolkit/mozapps/notificationserver/EventLog.h
new file mode 100644
index 0000000000..fbd5d3343b
--- /dev/null
+++ b/toolkit/mozapps/notificationserver/EventLog.h
@@ -0,0 +1,16 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 NotificationServer_EventLog_h__
+#define NotificationServer_EventLog_h__
+
+#include "mozilla/WindowsEventLog.h"
+
+#define LOG_ERROR_MESSAGE(format, ...) \
+ MOZ_WIN_EVENT_LOG_ERROR_MESSAGE( \
+ L"" MOZ_APP_DISPLAYNAME " Notification Server", format, __VA_ARGS__)
+
+#endif // NotificationServer_EventLog_h__
diff --git a/toolkit/mozapps/notificationserver/NotificationCallback.cpp b/toolkit/mozapps/notificationserver/NotificationCallback.cpp
new file mode 100644
index 0000000000..dca9d327a2
--- /dev/null
+++ b/toolkit/mozapps/notificationserver/NotificationCallback.cpp
@@ -0,0 +1,264 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 "EventLog.h"
+#include "NotificationCallback.h"
+
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "mozilla/CmdLineAndEnvUtils.h"
+#include "mozilla/ToastNotificationHeaderOnlyUtils.h"
+
+using namespace mozilla::widget::toastnotification;
+
+HRESULT STDMETHODCALLTYPE
+NotificationCallback::QueryInterface(REFIID riid, void** ppvObject) {
+ if (!ppvObject) {
+ return E_POINTER;
+ }
+
+ *ppvObject = nullptr;
+
+ if (!(riid == guid || riid == __uuidof(INotificationActivationCallback) ||
+ riid == __uuidof(IUnknown))) {
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ *ppvObject = reinterpret_cast<void*>(this);
+
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE NotificationCallback::Activate(
+ LPCWSTR appUserModelId, LPCWSTR invokedArgs,
+ const NOTIFICATION_USER_INPUT_DATA* data, ULONG dataCount) {
+ HandleActivation(invokedArgs);
+
+ // Windows 8 style callbacks are not called and notifications are not removed
+ // from the Action Center unless we return `S_OK`, so always do so even if
+ // we're unable to handle the notification properly.
+ return S_OK;
+}
+
+void NotificationCallback::HandleActivation(LPCWSTR invokedArgs) {
+ LOG_ERROR_MESSAGE(L"Invoked with arguments: '%s'", invokedArgs);
+
+ auto maybeArgs = ParseToastArguments(invokedArgs);
+ if (!maybeArgs) {
+ LOG_ERROR_MESSAGE(L"COM server disabled for toast");
+ return;
+ }
+ const auto& args = maybeArgs.value();
+ auto [programPath, cmdLine] = BuildRunCommand(args);
+
+ // This pipe object will let Firefox notify us when it has handled the
+ // notification. Create this before interacting with the application so the
+ // application can rely on it existing.
+ auto maybePipe = CreatePipe(args.windowsTag);
+
+ // Run the application.
+
+ STARTUPINFOW si = {};
+ si.cb = sizeof(STARTUPINFOW);
+ PROCESS_INFORMATION pi = {};
+
+ // Runs `{program path} [--profile {profile path}] [--notification-windowsTag
+ // {tag}]`.
+ CreateProcessW(programPath.c_str(), cmdLine.get(), nullptr, nullptr, false,
+ DETACHED_PROCESS | NORMAL_PRIORITY_CLASS, nullptr, nullptr,
+ &si, &pi);
+
+ LOG_ERROR_MESSAGE(L"Invoked %s", cmdLine.get());
+
+ // Transfer `SetForegroundWindow` permission to the launched application.
+
+ maybePipe.apply([](const auto& pipe) {
+ if (ConnectPipeWithTimeout(pipe)) {
+ HandlePipeMessages(pipe);
+ }
+ });
+}
+
+mozilla::Maybe<ToastArgs> NotificationCallback::ParseToastArguments(
+ LPCWSTR invokedArgs) {
+ ToastArgs parsedArgs;
+ std::wistringstream args(invokedArgs);
+ bool serverDisabled = true;
+
+ for (std::wstring key, value;
+ std::getline(args, key) && std::getline(args, value);) {
+ if (key == kLaunchArgProgram) {
+ serverDisabled = false;
+ } else if (key == kLaunchArgProfile) {
+ parsedArgs.profile = value;
+ } else if (key == kLaunchArgTag) {
+ parsedArgs.windowsTag = value;
+ } else if (key == kLaunchArgAction) {
+ // Remainder of args are from the Web Notification action, don't parse.
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=1781929.
+ break;
+ }
+ }
+
+ if (serverDisabled) {
+ return mozilla::Nothing();
+ }
+
+ return mozilla::Some(parsedArgs);
+}
+
+std::tuple<path, mozilla::UniquePtr<wchar_t[]>>
+NotificationCallback::BuildRunCommand(const ToastArgs& args) {
+ path programPath = installDir / L"" MOZ_APP_NAME;
+ programPath += L".exe";
+
+ std::vector<const wchar_t*> childArgv;
+ childArgv.push_back(programPath.c_str());
+
+ if (!args.profile.empty()) {
+ childArgv.push_back(L"--profile");
+ childArgv.push_back(args.profile.c_str());
+ } else {
+ LOG_ERROR_MESSAGE(L"No profile; invocation will choose default profile");
+ }
+
+ if (!args.windowsTag.empty()) {
+ childArgv.push_back(L"--notification-windowsTag");
+ childArgv.push_back(args.windowsTag.c_str());
+ } else {
+ LOG_ERROR_MESSAGE(L"No windowsTag; invoking anyway");
+ }
+
+ return {programPath,
+ mozilla::MakeCommandLine(childArgv.size(), childArgv.data())};
+}
+
+mozilla::Maybe<nsAutoHandle> NotificationCallback::CreatePipe(
+ const std::wstring& tag) {
+ if (tag.empty()) {
+ return mozilla::Nothing();
+ }
+
+ // Prefix required by pipe API.
+ std::wstring pipeName = GetNotificationPipeName(tag.c_str());
+
+ nsAutoHandle pipe(CreateNamedPipeW(
+ pipeName.c_str(), PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
+ PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT |
+ PIPE_REJECT_REMOTE_CLIENTS,
+ 1, sizeof(ToastNotificationPermissionMessage),
+ sizeof(ToastNotificationPidMessage), 0, nullptr));
+ if (pipe.get() == INVALID_HANDLE_VALUE) {
+ LOG_ERROR_MESSAGE(L"Error creating pipe %s, error %lu", pipeName.c_str(),
+ GetLastError());
+ return mozilla::Nothing();
+ }
+
+ return mozilla::Some(pipe.out());
+}
+
+bool NotificationCallback::ConnectPipeWithTimeout(const nsAutoHandle& pipe) {
+ nsAutoHandle overlappedEvent(CreateEventW(nullptr, TRUE, FALSE, nullptr));
+ if (!overlappedEvent) {
+ LOG_ERROR_MESSAGE(L"Error creating pipe connect event, error %lu",
+ GetLastError());
+ return false;
+ }
+
+ OVERLAPPED overlappedConnect{};
+ overlappedConnect.hEvent = overlappedEvent.get();
+
+ BOOL result = ConnectNamedPipe(pipe.get(), &overlappedConnect);
+ DWORD lastError = GetLastError();
+ if (lastError == ERROR_IO_PENDING) {
+ LOG_ERROR_MESSAGE(L"Waiting on pipe connection");
+
+ if (!WaitEventWithTimeout(overlappedEvent)) {
+ LOG_ERROR_MESSAGE(
+ L"Pipe connect wait failed, cancelling (connection may still "
+ L"succeed)");
+
+ CancelIo(pipe.get());
+ DWORD undefined;
+ BOOL overlappedResult =
+ GetOverlappedResult(pipe.get(), &overlappedConnect, &undefined, TRUE);
+ if (!overlappedResult || GetLastError() != ERROR_PIPE_CONNECTED) {
+ LOG_ERROR_MESSAGE(L"Pipe connect failed, error %lu", GetLastError());
+ return false;
+ }
+
+ // Pipe connected before cancellation, fall through.
+ }
+ } else if (result) {
+ // Overlapped `ConnectNamedPipe` should return 0.
+ LOG_ERROR_MESSAGE(L"Error connecting pipe, error %lu", lastError);
+ return false;
+ } else if (lastError != ERROR_PIPE_CONNECTED) {
+ LOG_ERROR_MESSAGE(L"Error connecting pipe, error %lu", lastError);
+ return false;
+ }
+
+ LOG_ERROR_MESSAGE(L"Pipe connected!");
+ return true;
+}
+
+void NotificationCallback::HandlePipeMessages(const nsAutoHandle& pipe) {
+ ToastNotificationPidMessage in{};
+ auto read = [&](OVERLAPPED& overlapped) {
+ return ReadFile(pipe.get(), &in, sizeof(in), nullptr, &overlapped);
+ };
+ if (!SyncDoOverlappedIOWithTimeout(pipe, sizeof(in), read)) {
+ LOG_ERROR_MESSAGE(L"Pipe read failed");
+ return;
+ }
+
+ ToastNotificationPermissionMessage out{};
+ out.setForegroundPermissionGranted = TransferForegroundPermission(in.pid);
+ auto write = [&](OVERLAPPED& overlapped) {
+ return WriteFile(pipe.get(), &out, sizeof(out), nullptr, &overlapped);
+ };
+ if (!SyncDoOverlappedIOWithTimeout(pipe, sizeof(out), write)) {
+ LOG_ERROR_MESSAGE(L"Pipe write failed");
+ return;
+ }
+
+ LOG_ERROR_MESSAGE(L"Pipe write succeeded!");
+}
+
+DWORD NotificationCallback::TransferForegroundPermission(DWORD pid) {
+ // When the instance of Firefox is still running we need to grant it
+ // foreground permission to bring itself to the foreground. We're able to do
+ // this even though the COM server is not the foreground process likely due to
+ // Windows granting permission to the COM object via
+ // `CoAllowSetForegroundWindow`.
+ //
+ // Note that issues surrounding `SetForegroundWindow` permissions are obscured
+ // when builds are run with a debugger, whereupon Windows grants
+ // `SetForegroundWindow` permission in all instances.
+ //
+ // We can not rely on granting this permission to the process created above
+ // because remote server clients do not meet the criteria to receive
+ // `SetForegroundWindow` permissions without unsupported hacks.
+ if (!pid) {
+ LOG_ERROR_MESSAGE(
+ L"`pid` received from pipe was 0, no process to grant "
+ L"`SetForegroundWindow` permission to");
+ return FALSE;
+ }
+ // When this call succeeds, the COM process loses the `SetForegroundWindow`
+ // permission.
+ if (!AllowSetForegroundWindow(pid)) {
+ LOG_ERROR_MESSAGE(
+ L"Failed to grant `SetForegroundWindow` permission, error %lu",
+ GetLastError());
+ return FALSE;
+ }
+
+ return TRUE;
+}
diff --git a/toolkit/mozapps/notificationserver/NotificationCallback.h b/toolkit/mozapps/notificationserver/NotificationCallback.h
new file mode 100644
index 0000000000..3fcb50a812
--- /dev/null
+++ b/toolkit/mozapps/notificationserver/NotificationCallback.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 NotificationCallback_h__
+#define NotificationCallback_h__
+
+#include <filesystem>
+#include <tuple>
+#include <unknwn.h>
+#include <wrl.h>
+
+#include "mozilla/Maybe.h"
+#include "nsWindowsHelpers.h"
+
+using namespace Microsoft::WRL;
+using namespace std::filesystem;
+
+// Windows 10+ declarations.
+// TODO remove declarations and add `#include
+// <notificationactivationcallback.h>` when Windows 10 is the minimum supported.
+typedef struct NOTIFICATION_USER_INPUT_DATA {
+ LPCWSTR Key;
+ LPCWSTR Value;
+} NOTIFICATION_USER_INPUT_DATA;
+
+MIDL_INTERFACE("53E31837-6600-4A81-9395-75CFFE746F94")
+INotificationActivationCallback : public IUnknown {
+ public:
+ virtual HRESULT STDMETHODCALLTYPE Activate(
+ LPCWSTR appUserModelId, LPCWSTR invokedArgs,
+ const NOTIFICATION_USER_INPUT_DATA* data, ULONG count) = 0;
+};
+
+struct ToastArgs {
+ std::wstring profile;
+ std::wstring windowsTag;
+};
+
+class NotificationCallback final
+ : public RuntimeClass<RuntimeClassFlags<ClassicCom>,
+ INotificationActivationCallback> {
+ public:
+ HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) final;
+
+ HRESULT STDMETHODCALLTYPE Activate(LPCWSTR appUserModelId,
+ LPCWSTR invokedArgs,
+ const NOTIFICATION_USER_INPUT_DATA* data,
+ ULONG dataCount) final;
+
+ explicit NotificationCallback(const GUID& runtimeGuid,
+ const path& dllInstallDir)
+ : guid(runtimeGuid), installDir(dllInstallDir) {}
+
+ private:
+ const GUID guid = {};
+ const path installDir = {};
+
+ void HandleActivation(LPCWSTR invokedArgs);
+ mozilla::Maybe<ToastArgs> ParseToastArguments(LPCWSTR invokedArgs);
+ std::tuple<path, mozilla::UniquePtr<wchar_t[]>> BuildRunCommand(
+ const ToastArgs& args);
+
+ static mozilla::Maybe<nsAutoHandle> CreatePipe(const std::wstring& tag);
+ static bool ConnectPipeWithTimeout(const nsAutoHandle& pipe);
+ static void HandlePipeMessages(const nsAutoHandle& pipe);
+ static DWORD TransferForegroundPermission(const DWORD pid);
+};
+
+#endif
diff --git a/toolkit/mozapps/notificationserver/NotificationComServer.cpp b/toolkit/mozapps/notificationserver/NotificationComServer.cpp
new file mode 100644
index 0000000000..184b55be44
--- /dev/null
+++ b/toolkit/mozapps/notificationserver/NotificationComServer.cpp
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 <filesystem>
+#include <string>
+
+#include "mozilla/WinHeaderOnlyUtils.h"
+
+#include "NotificationFactory.h"
+
+using namespace std::filesystem;
+
+static path processDllPath = {};
+
+// Populate the path to this DLL.
+bool PopulateDllPath(HINSTANCE dllInstance) {
+ std::vector<wchar_t> path(MAX_PATH, 0);
+ DWORD charsWritten =
+ GetModuleFileNameW(dllInstance, path.data(), path.size());
+
+ // GetModuleFileNameW returns the count of characters written including null
+ // when truncated, excluding null otherwise. Therefore the count will always
+ // be less than the buffer size when not truncated.
+ while (charsWritten == path.size()) {
+ path.resize(path.size() * 2, 0);
+ charsWritten = GetModuleFileNameW(dllInstance, path.data(), path.size());
+ }
+
+ if (charsWritten == 0) {
+ return false;
+ }
+
+ processDllPath = path.data();
+ return true;
+}
+
+// Our activator's CLSID is generated once either during install or at runtime
+// by the application generating the notification so that notifications work
+// with parallel installs and portable/development builds. When a COM object is
+// requested we verify the CLSID's InprocServer registry entry matches this
+// DLL's path.
+bool CheckRuntimeClsid(REFCLSID rclsid) {
+ // MSIX Notification COM Server registration is isolated to the package and is
+ // identical across installs/channels.
+ if (mozilla::HasPackageIdentity()) {
+ // Keep synchronized with `python\mozbuild\mozbuild\repackaging\msix.py`.
+ constexpr CLSID MOZ_INOTIFICATIONACTIVATION_CLSID = {
+ 0x916f9b5d,
+ 0xb5b2,
+ 0x4d36,
+ {0xb0, 0x47, 0x03, 0xc7, 0xa5, 0x2f, 0x81, 0xc8}};
+
+ return IsEqualCLSID(rclsid, MOZ_INOTIFICATIONACTIVATION_CLSID);
+ }
+
+ std::wstring clsid_str;
+ {
+ wchar_t* raw_clsid_str;
+ if (SUCCEEDED(StringFromCLSID(rclsid, &raw_clsid_str))) {
+ clsid_str += raw_clsid_str;
+ CoTaskMemFree(raw_clsid_str);
+ } else {
+ return false;
+ }
+ }
+
+ std::wstring key = L"CLSID\\";
+ key += clsid_str;
+ key += L"\\InprocServer32";
+
+ DWORD bufferLen = 0;
+ LSTATUS status = RegGetValueW(HKEY_CLASSES_ROOT, key.c_str(), L"",
+ RRF_RT_REG_SZ, nullptr, nullptr, &bufferLen);
+ if (status != ERROR_SUCCESS) {
+ return false;
+ }
+
+ std::vector<wchar_t> clsidDllPathBuffer(bufferLen / sizeof(wchar_t));
+ // Sanity assignment in case the buffer length found was not an integer
+ // multiple of `sizeof(wchar_t)`.
+ bufferLen = clsidDllPathBuffer.size() * sizeof(wchar_t);
+
+ status = RegGetValueW(HKEY_CLASSES_ROOT, key.c_str(), L"", RRF_RT_REG_SZ,
+ nullptr, clsidDllPathBuffer.data(), &bufferLen);
+ if (status != ERROR_SUCCESS) {
+ return false;
+ }
+
+ path clsidDllPath = clsidDllPathBuffer.data();
+ return equivalent(processDllPath, clsidDllPath);
+}
+
+extern "C" {
+HRESULT STDMETHODCALLTYPE DllGetClassObject(REFCLSID rclsid, REFIID riid,
+ LPVOID* ppv) {
+ if (!ppv) {
+ return E_INVALIDARG;
+ }
+ *ppv = nullptr;
+
+ if (!CheckRuntimeClsid(rclsid)) {
+ return CLASS_E_CLASSNOTAVAILABLE;
+ }
+
+ using namespace Microsoft::WRL;
+ ComPtr<NotificationFactory> factory =
+ Make<NotificationFactory, const GUID&, const path&>(
+ rclsid, processDllPath.parent_path());
+
+ switch (factory->QueryInterface(riid, ppv)) {
+ case S_OK:
+ return S_OK;
+ case E_NOINTERFACE:
+ return CLASS_E_CLASSNOTAVAILABLE;
+ default:
+ return E_UNEXPECTED;
+ }
+}
+
+BOOL STDMETHODCALLTYPE DllMain(HINSTANCE hinstDLL, DWORD fdwReason,
+ LPVOID lpReserved) {
+ if (fdwReason == DLL_PROCESS_ATTACH) {
+ if (!PopulateDllPath(hinstDLL)) {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+}
diff --git a/toolkit/mozapps/notificationserver/NotificationFactory.cpp b/toolkit/mozapps/notificationserver/NotificationFactory.cpp
new file mode 100644
index 0000000000..a1043f17b9
--- /dev/null
+++ b/toolkit/mozapps/notificationserver/NotificationFactory.cpp
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 "NotificationFactory.h"
+
+HRESULT STDMETHODCALLTYPE NotificationFactory::CreateInstance(
+ IUnknown* pUnkOuter, REFIID riid, void** ppvObject) {
+ if (pUnkOuter != nullptr) {
+ return CLASS_E_NOAGGREGATION;
+ }
+
+ if (!ppvObject) {
+ return E_INVALIDARG;
+ }
+ *ppvObject = nullptr;
+
+ using namespace Microsoft::WRL;
+ ComPtr<NotificationCallback> callback =
+ Make<NotificationCallback, const GUID&, const path&>(notificationGuid,
+ installDir);
+
+ switch (callback->QueryInterface(riid, ppvObject)) {
+ case S_OK:
+ return S_OK;
+ case E_NOINTERFACE:
+ return E_NOINTERFACE;
+ default:
+ return E_UNEXPECTED;
+ }
+}
diff --git a/toolkit/mozapps/notificationserver/NotificationFactory.h b/toolkit/mozapps/notificationserver/NotificationFactory.h
new file mode 100644
index 0000000000..0936b1ffeb
--- /dev/null
+++ b/toolkit/mozapps/notificationserver/NotificationFactory.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 NotificationFactory_h__
+#define NotificationFactory_h__
+
+#include <filesystem>
+
+#include "NotificationCallback.h"
+
+using namespace std::filesystem;
+using namespace Microsoft::WRL;
+
+class NotificationFactory final : public ClassFactory<> {
+ public:
+ HRESULT STDMETHODCALLTYPE CreateInstance(IUnknown* pUnkOuter, REFIID riid,
+ void** ppvObject) final;
+
+ explicit NotificationFactory(const GUID& runtimeGuid,
+ const path& dllInstallDir)
+ : notificationGuid(runtimeGuid), installDir(dllInstallDir) {}
+
+ private:
+ const GUID notificationGuid = {};
+ const path installDir = {};
+};
+
+#endif
diff --git a/toolkit/mozapps/notificationserver/moz.build b/toolkit/mozapps/notificationserver/moz.build
new file mode 100644
index 0000000000..16e8d50f9f
--- /dev/null
+++ b/toolkit/mozapps/notificationserver/moz.build
@@ -0,0 +1,33 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Toolkit", "Notifications and Alerts")
+
+SharedLibrary("notificationserver")
+
+UNIFIED_SOURCES = [
+ "/mfbt/Poison.cpp", # Necessary for global poison definitions.
+ "NotificationCallback.cpp",
+ "NotificationComServer.cpp",
+ "NotificationFactory.cpp",
+]
+
+DEFFILE = "notificationserver.def"
+
+DEFINES["MOZ_APP_NAME"] = '"%s"' % CONFIG["MOZ_APP_NAME"]
+DEFINES["MOZ_APP_DISPLAYNAME"] = '"%s"' % CONFIG["MOZ_APP_DISPLAYNAME"]
+
+DEFINES["IMPL_MFBT"] = True
+
+OS_LIBS += [
+ "advapi32",
+ "kernel32",
+ "user32",
+]
+
+LIBRARY_DEFINES["MOZ_NO_MOZALLOC"] = True
+DisableStlWrapping()
diff --git a/toolkit/mozapps/notificationserver/notificationserver.def b/toolkit/mozapps/notificationserver/notificationserver.def
new file mode 100644
index 0000000000..694d5609e3
--- /dev/null
+++ b/toolkit/mozapps/notificationserver/notificationserver.def
@@ -0,0 +1,6 @@
+;+# 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/.
+
+LIBRARY notificationserver.dll
+EXPORTS DllGetClassObject PRIVATE