diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /toolkit/mozapps/defaultagent/Notification.cpp | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/mozapps/defaultagent/Notification.cpp')
-rw-r--r-- | toolkit/mozapps/defaultagent/Notification.cpp | 709 |
1 files changed, 709 insertions, 0 deletions
diff --git a/toolkit/mozapps/defaultagent/Notification.cpp b/toolkit/mozapps/defaultagent/Notification.cpp new file mode 100644 index 0000000000..961e57c9b3 --- /dev/null +++ b/toolkit/mozapps/defaultagent/Notification.cpp @@ -0,0 +1,709 @@ +/* -*- 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/ErrorResult.h" +#include "mozilla/mscom/EnsureMTA.h" +#include "mozilla/intl/FileSource.h" +#include "mozilla/intl/Localization.h" +#include "mozilla/ShellHeaderOnlyUtils.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Unused.h" +#include "mozilla/WinHeaderOnlyUtils.h" +#include "nsString.h" +#include "nsTArray.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" + +using mozilla::intl::Localization; + +#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) + +namespace mozilla::default_agent { + +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; + } + nsTArray<nsCString> resIds = {"branding/brand.ftl"_ns, + "browser/backgroundtasks/defaultagent.ftl"_ns}; + RefPtr<Localization> l10n = Localization::Create(resIds, true); + nsAutoCString daHeaderText, daBodyText, daYesButton, daNoButton; + mozilla::ErrorResult daRv; + l10n->FormatValueSync("default-browser-notification-header-text"_ns, {}, + daHeaderText, daRv); + ENSURE_SUCCESS(daRv, false); + l10n->FormatValueSync("default-browser-notification-body-text"_ns, {}, + daBodyText, daRv); + ENSURE_SUCCESS(daRv, false); + l10n->FormatValueSync("default-browser-notification-yes-button-text"_ns, {}, + daYesButton, daRv); + ENSURE_SUCCESS(daRv, false); + l10n->FormatValueSync("default-browser-notification-no-button-text"_ns, {}, + daNoButton, daRv); + ENSURE_SUCCESS(daRv, false); + + NS_ConvertUTF8toUTF16 daHeaderTextW(daHeaderText), daBodyTextW(daBodyText), + daYesButtonW(daYesButton), daNoButtonW(daNoButton); + strings.localizedToast.text1 = + mozilla::MakeUnique<wchar_t[]>(daHeaderTextW.Length() + 1); + wcsncpy(strings.localizedToast.text1.get(), daHeaderTextW.get(), + daHeaderTextW.Length() + 1); + strings.localizedToast.text2 = + mozilla::MakeUnique<wchar_t[]>(daBodyTextW.Length() + 1); + wcsncpy(strings.localizedToast.text2.get(), daBodyTextW.get(), + daBodyTextW.Length() + 1); + strings.localizedToast.action1 = + mozilla::MakeUnique<wchar_t[]>(daYesButtonW.Length() + 1); + wcsncpy(strings.localizedToast.action1.get(), daYesButtonW.get(), + daYesButtonW.Length() + 1); + strings.localizedToast.action2 = + mozilla::MakeUnique<wchar_t[]>(daNoButtonW.Length() + 1); + wcsncpy(strings.localizedToast.action2.get(), daNoButtonW.get(), + daNoButtonW.Length() + 1); + 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 nonlocalizedReader(iniPath.get(), "Nonlocalized"); + nonlocalizedReader.AddKey("InitialToastRelativeImagePath", + &strings.initialToast.relImagePath); + nonlocalizedReader.AddKey("FollowupToastRelativeImagePath", + &strings.followupToast.relImagePath); + nonlocalizedReader.AddKey("LocalizedToastRelativeImagePath", + &strings.localizedToast.relImagePath); + int 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(); + + _bstr_t cmd = firefoxPath.c_str(); + // Omit argv[0] because ShellExecute doesn't need it. + _variant_t args(L"-to-handle-default-browser-agent"); + _variant_t operation(L"open"); + _variant_t directory; + _variant_t showCmd(SW_SHOWNORMAL); + + // To prevent inheriting environment variables from the background task, we + // run Firefox via Explorer instead of our own process. This mimics the + // implementation of the Windows Launcher Process. + auto result = + ShellExecuteByExplorer(cmd, args, operation, directory, showCmd); + NS_ENSURE_TRUE(result.isOk(), result.unwrapErr()); + + 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. + */ +static void SetDefaultBrowserFromNotification(const wchar_t* aumi) { + nsresult rv = NS_ERROR_FAILURE; + if (GetPrefSetDefaultBrowserUserChoice()) { + rv = SetDefaultBrowserUserChoice(aumi); + } + + if (NS_SUCCEEDED(rv)) { + mozilla::Unused << LaunchFirefoxToHandleDefaultBrowserAgent(); + } else { + LOG_ERROR_MESSAGE(L"Failed to SetDefaultBrowserUserChoice: %#X", + GetLastError()); + LaunchModernSettingsDialogDefaultApps(); + } +} + +// 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}; + + bool isEnglishInstall = FirefoxInstallIsEnglish(); + + Strings strings; + if (!GetStrings(strings)) { + return activitiesPerformed; + } + const ToastStrings* toastStrings = + strings.GetToastStrings(whichNotification, isEnglishInstall); + + mozilla::mscom::EnsureMTA([&] { + using namespace WinToastLib; + + if (!WinToast::isCompatible()) { + LOG_ERROR_MESSAGE(L"System is not compatible with WinToast"); + return; + } + 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; + } + + // 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; + } + + 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; + } + + // 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; + } + 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; + } + // 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; + } + + 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}; + + // 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"); + } +} + +NotificationShown GetNotificationShownFromString(const nsAString& shown) { + if (shown == u"not-shown"_ns) { + return NotificationShown::NotShown; + } else if (shown == u"shown"_ns) { + return NotificationShown::Shown; + } else if (shown == u"error"_ns) { + return NotificationShown::Error; + } else { + // Catch all. + return NotificationShown::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"); + } +} + +NotificationAction GetNotificationActionFromString(const nsAString& action) { + if (action == u"dismissed-by-timeout"_ns) { + return NotificationAction::DismissedByTimeout; + } else if (action == u"dismissed-to-action-center"_ns) { + return NotificationAction::DismissedToActionCenter; + } else if (action == u"dismissed-by-button"_ns) { + return NotificationAction::DismissedByButton; + } else if (action == u"dismissed-by-application-hidden"_ns) { + return NotificationAction::DismissedByApplicationHidden; + } else if (action == u"remind-me-later"_ns) { + return NotificationAction::RemindMeLater; + } else if (action == u"make-firefox-default-button"_ns) { + return NotificationAction::MakeFirefoxDefaultButton; + } else if (action == u"toast-clicked"_ns) { + return NotificationAction::ToastClicked; + } else if (action == u"no-action"_ns) { + return NotificationAction::NoAction; + } else { + // Catch all. + return NotificationAction::NoAction; + } +} + +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"; + } +} + +} // namespace mozilla::default_agent |