summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/defaultagent/Notification.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/mozapps/defaultagent/Notification.cpp')
-rw-r--r--toolkit/mozapps/defaultagent/Notification.cpp690
1 files changed, 690 insertions, 0 deletions
diff --git a/toolkit/mozapps/defaultagent/Notification.cpp b/toolkit/mozapps/defaultagent/Notification.cpp
new file mode 100644
index 0000000000..1ccb670e40
--- /dev/null
+++ b/toolkit/mozapps/defaultagent/Notification.cpp
@@ -0,0 +1,690 @@
+/* -*- 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/. */
+
+#include "Notification.h"
+
+#include <shlwapi.h>
+#include <wchar.h>
+#include <windows.h>
+#include <winnt.h>
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/CmdLineAndEnvUtils.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/WindowsVersion.h"
+#include "mozilla/WinHeaderOnlyUtils.h"
+#include "nsWindowsHelpers.h"
+#include "readstrings.h"
+#include "updatererrors.h"
+#include "WindowsDefaultBrowser.h"
+
+#include "common.h"
+#include "DefaultBrowser.h"
+#include "EventLog.h"
+#include "Registry.h"
+#include "SetDefaultBrowser.h"
+
+#include "wintoastlib.h"
+
+#define SEVEN_DAYS_IN_SECONDS (7 * 24 * 60 * 60)
+
+// If the notification hasn't been activated or dismissed within 12 hours,
+// stop waiting for it.
+#define NOTIFICATION_WAIT_TIMEOUT_MS (12 * 60 * 60 * 1000)
+// If the mutex hasn't been released within a few minutes, something is wrong
+// and we should give up on it
+#define MUTEX_TIMEOUT_MS (10 * 60 * 1000)
+
+bool FirefoxInstallIsEnglish();
+
+static bool SetInitialNotificationShown(bool wasShown) {
+ return !RegistrySetValueBool(IsPrefixed::Unprefixed,
+ L"InitialNotificationShown", wasShown)
+ .isErr();
+}
+
+static bool GetInitialNotificationShown() {
+ return RegistryGetValueBool(IsPrefixed::Unprefixed,
+ L"InitialNotificationShown")
+ .unwrapOr(mozilla::Some(false))
+ .valueOr(false);
+}
+
+static bool ResetInitialNotificationShown() {
+ return RegistryDeleteValue(IsPrefixed::Unprefixed,
+ L"InitialNotificationShown")
+ .isOk();
+}
+
+static bool SetFollowupNotificationShown(bool wasShown) {
+ return !RegistrySetValueBool(IsPrefixed::Unprefixed,
+ L"FollowupNotificationShown", wasShown)
+ .isErr();
+}
+
+static bool GetFollowupNotificationShown() {
+ return RegistryGetValueBool(IsPrefixed::Unprefixed,
+ L"FollowupNotificationShown")
+ .unwrapOr(mozilla::Some(false))
+ .valueOr(false);
+}
+
+static bool SetFollowupNotificationSuppressed(bool value) {
+ return !RegistrySetValueBool(IsPrefixed::Unprefixed,
+ L"FollowupNotificationSuppressed", value)
+ .isErr();
+}
+
+static bool GetFollowupNotificationSuppressed() {
+ return RegistryGetValueBool(IsPrefixed::Unprefixed,
+ L"FollowupNotificationSuppressed")
+ .unwrapOr(mozilla::Some(false))
+ .valueOr(false);
+}
+
+// Returns 0 if no value is set.
+static ULONGLONG GetFollowupNotificationRequestTime() {
+ return RegistryGetValueQword(IsPrefixed::Unprefixed, L"FollowupRequestTime")
+ .unwrapOr(mozilla::Some(0))
+ .valueOr(0);
+}
+
+// Returns false if no value is set.
+static bool GetPrefSetDefaultBrowserUserChoice() {
+ return RegistryGetValueBool(IsPrefixed::Prefixed,
+ L"SetDefaultBrowserUserChoice")
+ .unwrapOr(mozilla::Some(false))
+ .valueOr(false);
+}
+
+struct ToastStrings {
+ mozilla::UniquePtr<wchar_t[]> text1;
+ mozilla::UniquePtr<wchar_t[]> text2;
+ mozilla::UniquePtr<wchar_t[]> action1;
+ mozilla::UniquePtr<wchar_t[]> action2;
+ mozilla::UniquePtr<wchar_t[]> relImagePath;
+};
+
+struct Strings {
+ // Toast notification button text is hard to localize because it tends to
+ // overflow. Thus, we have 3 different toast notifications:
+ // - The initial notification, which includes a button with text like
+ // "Ask me later". Since we cannot easily localize this, we will display
+ // it only in English.
+ // - The followup notification, to be shown if the user clicked "Ask me
+ // later". Since we only have that button in English, we only need this
+ // notification in English.
+ // - The localized notification, which has much shorter button text to
+ // (hopefully) prevent overflow: just "Yes" and "No". Since we no longer
+ // have an "Ask me later" button, a followup localized notification is not
+ // needed.
+ ToastStrings initialToast;
+ ToastStrings followupToast;
+ ToastStrings localizedToast;
+
+ // Returned pointer points within this struct and should not be freed.
+ const ToastStrings* GetToastStrings(NotificationType whichToast,
+ bool englishStrings) const {
+ if (!englishStrings) {
+ return &localizedToast;
+ }
+ if (whichToast == NotificationType::Initial) {
+ return &initialToast;
+ }
+ return &followupToast;
+ }
+};
+
+// Gets all strings out of the relevant INI files.
+// Returns true on success, false on failure
+static bool GetStrings(Strings& strings) {
+ mozilla::UniquePtr<wchar_t[]> installPath;
+ bool success = GetInstallDirectory(installPath);
+ if (!success) {
+ LOG_ERROR_MESSAGE(L"Failed to get install directory when getting strings");
+ return false;
+ }
+ const wchar_t* iniFormat = L"%s\\defaultagent.ini";
+ int bufferSize = _scwprintf(iniFormat, installPath.get());
+ ++bufferSize; // Extra character for terminating null
+ mozilla::UniquePtr<wchar_t[]> iniPath =
+ mozilla::MakeUnique<wchar_t[]>(bufferSize);
+ _snwprintf_s(iniPath.get(), bufferSize, _TRUNCATE, iniFormat,
+ installPath.get());
+
+ IniReader stringsReader(iniPath.get());
+ stringsReader.AddKey("DefaultBrowserNotificationTitle",
+ &strings.initialToast.text1);
+ stringsReader.AddKey("DefaultBrowserNotificationTitle",
+ &strings.followupToast.text1);
+ stringsReader.AddKey("DefaultBrowserNotificationText",
+ &strings.initialToast.text2);
+ stringsReader.AddKey("DefaultBrowserNotificationText",
+ &strings.followupToast.text2);
+ stringsReader.AddKey("DefaultBrowserNotificationMakeFirefoxDefault",
+ &strings.initialToast.action1);
+ stringsReader.AddKey("DefaultBrowserNotificationMakeFirefoxDefault",
+ &strings.followupToast.action1);
+ stringsReader.AddKey("DefaultBrowserNotificationDontShowAgain",
+ &strings.initialToast.action2);
+ stringsReader.AddKey("DefaultBrowserNotificationDontShowAgain",
+ &strings.followupToast.action2);
+ int result = stringsReader.Read();
+ if (result != OK) {
+ LOG_ERROR_MESSAGE(L"Unable to read English strings: %d", result);
+ return false;
+ }
+
+ const wchar_t* localizedIniFormat = L"%s\\defaultagent_localized.ini";
+ bufferSize = _scwprintf(localizedIniFormat, installPath.get());
+ ++bufferSize; // Extra character for terminating null
+ mozilla::UniquePtr<wchar_t[]> localizedIniPath =
+ mozilla::MakeUnique<wchar_t[]>(bufferSize);
+ _snwprintf_s(localizedIniPath.get(), bufferSize, _TRUNCATE,
+ localizedIniFormat, installPath.get());
+
+ IniReader localizedReader(localizedIniPath.get());
+ localizedReader.AddKey("DefaultBrowserNotificationHeaderText",
+ &strings.localizedToast.text1);
+ localizedReader.AddKey("DefaultBrowserNotificationBodyText",
+ &strings.localizedToast.text2);
+ localizedReader.AddKey("DefaultBrowserNotificationYesButtonText",
+ &strings.localizedToast.action1);
+ localizedReader.AddKey("DefaultBrowserNotificationNoButtonText",
+ &strings.localizedToast.action2);
+ result = localizedReader.Read();
+ if (result != OK) {
+ LOG_ERROR_MESSAGE(L"Unable to read localized strings: %d", result);
+ return false;
+ }
+
+ // IniReader is only capable of reading from one section at a time, so we need
+ // to make another one to read the other section.
+ IniReader nonlocalizedReader(iniPath.get(), "Nonlocalized");
+ nonlocalizedReader.AddKey("InitialToastRelativeImagePath",
+ &strings.initialToast.relImagePath);
+ nonlocalizedReader.AddKey("FollowupToastRelativeImagePath",
+ &strings.followupToast.relImagePath);
+ nonlocalizedReader.AddKey("LocalizedToastRelativeImagePath",
+ &strings.localizedToast.relImagePath);
+ result = nonlocalizedReader.Read();
+ if (result != OK) {
+ LOG_ERROR_MESSAGE(L"Unable to read non-localized strings: %d", result);
+ return false;
+ }
+
+ return true;
+}
+
+static mozilla::WindowsError LaunchFirefoxToHandleDefaultBrowserAgent() {
+ // Could also be `MOZ_APP_NAME.exe`, but there's no generality to be gained:
+ // the WDBA is Firefox-only.
+ FilePathResult firefoxPathResult = GetRelativeBinaryPath(L"firefox.exe");
+ if (firefoxPathResult.isErr()) {
+ return firefoxPathResult.unwrapErr();
+ }
+ std::wstring firefoxPath = firefoxPathResult.unwrap();
+
+ const wchar_t* firefoxArgs[] = {firefoxPath.c_str(),
+ L"-to-handle-default-browser-agent"};
+ mozilla::UniquePtr<wchar_t[]> firefoxCmdLine(mozilla::MakeCommandLine(
+ mozilla::ArrayLength(firefoxArgs), const_cast<wchar_t**>(firefoxArgs)));
+
+ PROCESS_INFORMATION pi;
+ STARTUPINFOW si = {sizeof(si)};
+ if (!::CreateProcessW(firefoxPath.c_str(), firefoxCmdLine.get(), nullptr,
+ nullptr, false,
+ DETACHED_PROCESS | NORMAL_PRIORITY_CLASS, nullptr,
+ nullptr, &si, &pi)) {
+ HRESULT hr = HRESULT_FROM_WIN32(GetLastError());
+ LOG_ERROR(hr);
+ return mozilla::WindowsError::FromHResult(hr);
+ }
+
+ CloseHandle(pi.hThread);
+ CloseHandle(pi.hProcess);
+
+ return mozilla::WindowsError::CreateSuccess();
+}
+
+/*
+ * Set the default browser.
+ *
+ * First check if we can directly write UserChoice, if so attempt that.
+ * If we can't write UserChoice, or if the attempt fails, fall back to
+ * showing the Default Apps page of Settings.
+ *
+ * @param aAumi The AUMI of the installation to set as default.
+ *
+ * @return Success (SUCCEEDED(hr)) if all associations were set with
+ * UserChoice and checked successfully.
+ * Other return codes indicate a failure which causes us to
+ * fall back to Settings, see return codes of
+ * SetDefaultBrowserUserChoice().
+ */
+static HRESULT SetDefaultBrowserFromNotification(const wchar_t* aumi) {
+ // TODO maybe fall back to protocol dialog on Windows 11 (bug 1719832)?
+
+ HRESULT hr = E_FAIL;
+ if (GetPrefSetDefaultBrowserUserChoice()) {
+ hr = SetDefaultBrowserUserChoice(aumi);
+ }
+
+ if (!FAILED(hr)) {
+ mozilla::Unused << LaunchFirefoxToHandleDefaultBrowserAgent();
+ } else {
+ LOG_ERROR_MESSAGE(L"Failed to SetDefaultBrowserUserChoice: %#X",
+ GetLastError());
+ LaunchModernSettingsDialogDefaultApps();
+ }
+ return hr;
+}
+
+// This encapsulates the data that needs to be protected by a mutex because it
+// will be shared by the main thread and the handler thread.
+// To ensure the data is only written once, handlerDataHasBeenSet should be
+// initialized to false, then set to true when the handler writes data into the
+// structure.
+struct HandlerData {
+ NotificationActivities activitiesPerformed;
+ bool handlerDataHasBeenSet;
+};
+
+// The value that ToastHandler writes into should be a global. We can't control
+// when ToastHandler is called, and if this value isn't a global, ToastHandler
+// may be called and attempt to access this after it has been deconstructed.
+// Since this value is accessed by the handler thread and the main thread, it
+// is protected by a mutex (gHandlerMutex).
+// Since ShowNotification deconstructs the mutex, it might seem like once
+// ShowNotification exits, we can just rely on the inability to wait on an
+// invalid mutex to protect the deconstructed data, but it's possible that
+// we could deconstruct the mutex while the handler is holding it and is
+// already accessing the protected data.
+static HandlerData gHandlerReturnData;
+static HANDLE gHandlerMutex = INVALID_HANDLE_VALUE;
+
+class ToastHandler : public WinToastLib::IWinToastHandler {
+ private:
+ NotificationType mWhichNotification;
+ HANDLE mEvent;
+ const std::wstring mAumiStr;
+
+ public:
+ ToastHandler(NotificationType whichNotification, HANDLE event,
+ const wchar_t* aumi)
+ : mWhichNotification(whichNotification), mEvent(event), mAumiStr(aumi) {}
+
+ void FinishHandler(NotificationActivities& returnData) const {
+ SetReturnData(returnData);
+
+ BOOL success = SetEvent(mEvent);
+ if (!success) {
+ LOG_ERROR_MESSAGE(L"Event could not be set: %#X", GetLastError());
+ }
+ }
+
+ void SetReturnData(NotificationActivities& toSet) const {
+ DWORD result = WaitForSingleObject(gHandlerMutex, MUTEX_TIMEOUT_MS);
+ if (result == WAIT_TIMEOUT) {
+ LOG_ERROR_MESSAGE(L"Unable to obtain mutex ownership");
+ return;
+ } else if (result == WAIT_FAILED) {
+ LOG_ERROR_MESSAGE(L"Failed to wait on mutex: %#X", GetLastError());
+ return;
+ } else if (result == WAIT_ABANDONED) {
+ LOG_ERROR_MESSAGE(L"Found abandoned mutex");
+ ReleaseMutex(gHandlerMutex);
+ return;
+ }
+
+ // Only set this data once
+ if (!gHandlerReturnData.handlerDataHasBeenSet) {
+ gHandlerReturnData.activitiesPerformed = toSet;
+ gHandlerReturnData.handlerDataHasBeenSet = true;
+ }
+
+ BOOL success = ReleaseMutex(gHandlerMutex);
+ if (!success) {
+ LOG_ERROR_MESSAGE(L"Unable to release mutex ownership: %#X",
+ GetLastError());
+ }
+ }
+
+ void toastActivated() const override {
+ NotificationActivities activitiesPerformed;
+ activitiesPerformed.type = mWhichNotification;
+ activitiesPerformed.shown = NotificationShown::Shown;
+ activitiesPerformed.action = NotificationAction::ToastClicked;
+
+ // Notification strings are written to indicate the default browser is
+ // restored to Firefox when the notification body is clicked to prevent
+ // ambiguity when buttons aren't pressed.
+ SetDefaultBrowserFromNotification(mAumiStr.c_str());
+
+ FinishHandler(activitiesPerformed);
+ }
+
+ void toastActivated(int actionIndex) const override {
+ NotificationActivities activitiesPerformed;
+ activitiesPerformed.type = mWhichNotification;
+ activitiesPerformed.shown = NotificationShown::Shown;
+ // Override this below
+ activitiesPerformed.action = NotificationAction::NoAction;
+
+ if (actionIndex == 0) {
+ // "Make Firefox the default" button, on both the initial and followup
+ // notifications. "Yes" button on the localized notification.
+ activitiesPerformed.action = NotificationAction::MakeFirefoxDefaultButton;
+
+ SetDefaultBrowserFromNotification(mAumiStr.c_str());
+ } else if (actionIndex == 1) {
+ // Do nothing. As long as we don't call
+ // SetFollowupNotificationRequestTime, there will be no followup
+ // notification.
+ activitiesPerformed.action = NotificationAction::DismissedByButton;
+ }
+
+ FinishHandler(activitiesPerformed);
+ }
+
+ void toastDismissed(WinToastDismissalReason state) const override {
+ NotificationActivities activitiesPerformed;
+ activitiesPerformed.type = mWhichNotification;
+ activitiesPerformed.shown = NotificationShown::Shown;
+ // Override this below
+ activitiesPerformed.action = NotificationAction::NoAction;
+
+ if (state == WinToastDismissalReason::TimedOut) {
+ activitiesPerformed.action = NotificationAction::DismissedByTimeout;
+ } else if (state == WinToastDismissalReason::ApplicationHidden) {
+ activitiesPerformed.action =
+ NotificationAction::DismissedByApplicationHidden;
+ } else if (state == WinToastDismissalReason::UserCanceled) {
+ activitiesPerformed.action = NotificationAction::DismissedToActionCenter;
+ }
+
+ FinishHandler(activitiesPerformed);
+ }
+
+ void toastFailed() const override {
+ NotificationActivities activitiesPerformed;
+ activitiesPerformed.type = mWhichNotification;
+ activitiesPerformed.shown = NotificationShown::Error;
+ activitiesPerformed.action = NotificationAction::NoAction;
+
+ LOG_ERROR_MESSAGE(L"Toast notification failed to display");
+ FinishHandler(activitiesPerformed);
+ }
+};
+
+// This function blocks until the shown notification is activated or dismissed.
+static NotificationActivities ShowNotification(
+ NotificationType whichNotification, const wchar_t* aumi) {
+ // Initially set the value that will be returned to error. If the notification
+ // is shown successfully, we'll update it.
+ NotificationActivities activitiesPerformed = {whichNotification,
+ NotificationShown::Error,
+ NotificationAction::NoAction};
+ using namespace WinToastLib;
+
+ if (!WinToast::isCompatible()) {
+ LOG_ERROR_MESSAGE(L"System is not compatible with WinToast");
+ return activitiesPerformed;
+ }
+
+ WinToast::instance()->setAppName(L"" MOZ_APP_DISPLAYNAME);
+ std::wstring aumiStr = aumi;
+ WinToast::instance()->setAppUserModelId(aumiStr);
+ WinToast::instance()->setShortcutPolicy(
+ WinToastLib::WinToast::SHORTCUT_POLICY_REQUIRE_NO_CREATE);
+ WinToast::WinToastError error;
+ if (!WinToast::instance()->initialize(&error)) {
+ LOG_ERROR_MESSAGE(WinToast::strerror(error).c_str());
+ return activitiesPerformed;
+ }
+
+ bool isEnglishInstall = FirefoxInstallIsEnglish();
+
+ Strings strings;
+ if (!GetStrings(strings)) {
+ return activitiesPerformed;
+ }
+ const ToastStrings* toastStrings =
+ strings.GetToastStrings(whichNotification, isEnglishInstall);
+
+ // This event object will let the handler notify us when it has handled the
+ // notification.
+ nsAutoHandle event(CreateEventW(nullptr, TRUE, FALSE, nullptr));
+ if (event.get() == nullptr) {
+ LOG_ERROR_MESSAGE(L"Unable to create event object: %#X", GetLastError());
+ return activitiesPerformed;
+ }
+
+ bool success = false;
+ if (whichNotification == NotificationType::Initial) {
+ success = SetInitialNotificationShown(true);
+ } else {
+ success = SetFollowupNotificationShown(true);
+ }
+ if (!success) {
+ // Return early in this case to prevent the notification from being shown
+ // on every run.
+ LOG_ERROR_MESSAGE(L"Unable to set notification as displayed");
+ return activitiesPerformed;
+ }
+
+ // We need the absolute image path, not the relative path.
+ mozilla::UniquePtr<wchar_t[]> installPath;
+ success = GetInstallDirectory(installPath);
+ if (!success) {
+ LOG_ERROR_MESSAGE(L"Failed to get install directory for the image path");
+ return activitiesPerformed;
+ }
+ const wchar_t* absPathFormat = L"%s\\%s";
+ int bufferSize = _scwprintf(absPathFormat, installPath.get(),
+ toastStrings->relImagePath.get());
+ ++bufferSize; // Extra character for terminating null
+ mozilla::UniquePtr<wchar_t[]> absImagePath =
+ mozilla::MakeUnique<wchar_t[]>(bufferSize);
+ _snwprintf_s(absImagePath.get(), bufferSize, _TRUNCATE, absPathFormat,
+ installPath.get(), toastStrings->relImagePath.get());
+
+ // This is used to protect gHandlerReturnData.
+ gHandlerMutex = CreateMutexW(nullptr, TRUE, nullptr);
+ if (gHandlerMutex == nullptr) {
+ LOG_ERROR_MESSAGE(L"Unable to create mutex: %#X", GetLastError());
+ return activitiesPerformed;
+ }
+ // Automatically close this mutex when this function exits.
+ nsAutoHandle autoMutex(gHandlerMutex);
+ // No need to initialize gHandlerReturnData.activitiesPerformed, since it will
+ // be set by the handler. But we do need to initialize
+ // gHandlerReturnData.handlerDataHasBeenSet so the handler knows that no data
+ // has been set yet.
+ gHandlerReturnData.handlerDataHasBeenSet = false;
+ success = ReleaseMutex(gHandlerMutex);
+ if (!success) {
+ LOG_ERROR_MESSAGE(L"Unable to release mutex ownership: %#X",
+ GetLastError());
+ }
+
+ // Finally ready to assemble the notification and dispatch it.
+ WinToastTemplate toastTemplate =
+ WinToastTemplate(WinToastTemplate::ImageAndText02);
+ toastTemplate.setTextField(toastStrings->text1.get(),
+ WinToastTemplate::FirstLine);
+ toastTemplate.setTextField(toastStrings->text2.get(),
+ WinToastTemplate::SecondLine);
+ toastTemplate.addAction(toastStrings->action1.get());
+ toastTemplate.addAction(toastStrings->action2.get());
+ toastTemplate.setImagePath(absImagePath.get());
+ toastTemplate.setScenario(WinToastTemplate::Scenario::Reminder);
+ ToastHandler* handler =
+ new ToastHandler(whichNotification, event.get(), aumi);
+ INT64 id = WinToast::instance()->showToast(toastTemplate, handler, &error);
+ if (id < 0) {
+ LOG_ERROR_MESSAGE(WinToast::strerror(error).c_str());
+ return activitiesPerformed;
+ }
+
+ DWORD result = WaitForSingleObject(event.get(), NOTIFICATION_WAIT_TIMEOUT_MS);
+ // Don't return after these errors. Attempt to hide the notification.
+ if (result == WAIT_FAILED) {
+ LOG_ERROR_MESSAGE(L"Unable to wait on event object: %#X", GetLastError());
+ } else if (result == WAIT_TIMEOUT) {
+ LOG_ERROR_MESSAGE(L"Timed out waiting for event object");
+ } else {
+ result = WaitForSingleObject(gHandlerMutex, MUTEX_TIMEOUT_MS);
+ if (result == WAIT_TIMEOUT) {
+ LOG_ERROR_MESSAGE(L"Unable to obtain mutex ownership");
+ // activitiesPerformed is already set to error. No change needed.
+ } else if (result == WAIT_FAILED) {
+ LOG_ERROR_MESSAGE(L"Failed to wait on mutex: %#X", GetLastError());
+ // activitiesPerformed is already set to error. No change needed.
+ } else if (result == WAIT_ABANDONED) {
+ LOG_ERROR_MESSAGE(L"Found abandoned mutex");
+ ReleaseMutex(gHandlerMutex);
+ // activitiesPerformed is already set to error. No change needed.
+ } else {
+ // Mutex is being held. It is safe to access gHandlerReturnData.
+ // If gHandlerReturnData.handlerDataHasBeenSet is false, the handler never
+ // ran. Use the error value activitiesPerformed already contains.
+ if (gHandlerReturnData.handlerDataHasBeenSet) {
+ activitiesPerformed = gHandlerReturnData.activitiesPerformed;
+ }
+
+ success = ReleaseMutex(gHandlerMutex);
+ if (!success) {
+ LOG_ERROR_MESSAGE(L"Unable to release mutex ownership: %#X",
+ GetLastError());
+ }
+ }
+ }
+
+ if (!WinToast::instance()->hideToast(id)) {
+ LOG_ERROR_MESSAGE(L"Failed to hide notification");
+ }
+ return activitiesPerformed;
+}
+
+// Previously this function checked that the Firefox build was using English.
+// This was checked because of the peculiar way we were localizing toast
+// notifications where we used a completely different set of strings in English.
+//
+// We've since unified the notification flows but need to clean up unused code
+// and config files - Bug 1826375.
+bool FirefoxInstallIsEnglish() { return false; }
+
+// If a notification is shown, this function will block until the notification
+// is activated or dismissed.
+// aumi is the App User Model ID.
+NotificationActivities MaybeShowNotification(
+ const DefaultBrowserInfo& browserInfo, const wchar_t* aumi, bool force) {
+ // Default to not showing a notification. Any other value will be returned
+ // directly from ShowNotification.
+ NotificationActivities activitiesPerformed = {NotificationType::Initial,
+ NotificationShown::NotShown,
+ NotificationAction::NoAction};
+
+ if (!mozilla::IsWin10OrLater()) {
+ // Notifications aren't shown in versions prior to Windows 10 because the
+ // notification API we want isn't available.
+ return activitiesPerformed;
+ }
+
+ // Reset notification state machine, user setting default browser to Firefox
+ // is a strong signal that they intend to have it as the default browser.
+ if (browserInfo.currentDefaultBrowser == Browser::Firefox) {
+ ResetInitialNotificationShown();
+ }
+
+ bool initialNotificationShown = GetInitialNotificationShown();
+ if (!initialNotificationShown || force) {
+ if ((browserInfo.currentDefaultBrowser == Browser::EdgeWithBlink &&
+ browserInfo.previousDefaultBrowser == Browser::Firefox) ||
+ force) {
+ return ShowNotification(NotificationType::Initial, aumi);
+ }
+ return activitiesPerformed;
+ }
+ activitiesPerformed.type = NotificationType::Followup;
+
+ ULONGLONG followupNotificationRequestTime =
+ GetFollowupNotificationRequestTime();
+ bool followupNotificationRequested = followupNotificationRequestTime != 0;
+ bool followupNotificationShown = GetFollowupNotificationShown();
+ if (followupNotificationRequested && !followupNotificationShown &&
+ !GetFollowupNotificationSuppressed()) {
+ ULONGLONG secondsSinceRequestTime =
+ SecondsPassedSince(followupNotificationRequestTime);
+
+ if (secondsSinceRequestTime >= SEVEN_DAYS_IN_SECONDS) {
+ // If we go to show the followup notification and the user has already
+ // changed the default browser, permanently suppress the followup since
+ // it's no longer relevant.
+ if (browserInfo.currentDefaultBrowser == Browser::EdgeWithBlink) {
+ return ShowNotification(NotificationType::Followup, aumi);
+ } else {
+ SetFollowupNotificationSuppressed(true);
+ }
+ }
+ }
+ return activitiesPerformed;
+}
+
+std::string GetStringForNotificationType(NotificationType type) {
+ switch (type) {
+ case NotificationType::Initial:
+ return std::string("initial");
+ case NotificationType::Followup:
+ return std::string("followup");
+ }
+}
+
+std::string GetStringForNotificationShown(NotificationShown shown) {
+ switch (shown) {
+ case NotificationShown::NotShown:
+ return std::string("not-shown");
+ case NotificationShown::Shown:
+ return std::string("shown");
+ case NotificationShown::Error:
+ return std::string("error");
+ }
+}
+
+std::string GetStringForNotificationAction(NotificationAction action) {
+ switch (action) {
+ case NotificationAction::DismissedByTimeout:
+ return std::string("dismissed-by-timeout");
+ case NotificationAction::DismissedToActionCenter:
+ return std::string("dismissed-to-action-center");
+ case NotificationAction::DismissedByButton:
+ return std::string("dismissed-by-button");
+ case NotificationAction::DismissedByApplicationHidden:
+ return std::string("dismissed-by-application-hidden");
+ case NotificationAction::RemindMeLater:
+ return std::string("remind-me-later");
+ case NotificationAction::MakeFirefoxDefaultButton:
+ return std::string("make-firefox-default-button");
+ case NotificationAction::ToastClicked:
+ return std::string("toast-clicked");
+ case NotificationAction::NoAction:
+ return std::string("no-action");
+ }
+}
+
+void EnsureValidNotificationAction(std::string& actionString) {
+ if (actionString != "dismissed-by-timeout" &&
+ actionString != "dismissed-to-action-center" &&
+ actionString != "dismissed-by-button" &&
+ actionString != "dismissed-by-application-hidden" &&
+ actionString != "remind-me-later" &&
+ actionString != "make-firefox-default-button" &&
+ actionString != "toast-clicked" && actionString != "no-action") {
+ actionString = "no-action";
+ }
+}