1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "Notification.h"
14 #include "mozilla/ArrayUtils.h"
15 #include "mozilla/CmdLineAndEnvUtils.h"
16 #include "mozilla/ErrorResult.h"
17 #include "mozilla/mscom/EnsureMTA.h"
18 #include "mozilla/intl/FileSource.h"
19 #include "mozilla/intl/Localization.h"
20 #include "mozilla/ShellHeaderOnlyUtils.h"
21 #include "mozilla/UniquePtr.h"
22 #include "mozilla/Unused.h"
23 #include "mozilla/WinHeaderOnlyUtils.h"
26 #include "nsWindowsHelpers.h"
27 #include "readstrings.h"
28 #include "updatererrors.h"
29 #include "WindowsDefaultBrowser.h"
32 #include "DefaultBrowser.h"
35 #include "SetDefaultBrowser.h"
37 #include "wintoastlib.h"
39 using mozilla::intl::Localization
;
41 #define SEVEN_DAYS_IN_SECONDS (7 * 24 * 60 * 60)
43 // If the notification hasn't been activated or dismissed within 12 hours,
44 // stop waiting for it.
45 #define NOTIFICATION_WAIT_TIMEOUT_MS (12 * 60 * 60 * 1000)
46 // If the mutex hasn't been released within a few minutes, something is wrong
47 // and we should give up on it
48 #define MUTEX_TIMEOUT_MS (10 * 60 * 1000)
50 namespace mozilla::default_agent
{
52 bool FirefoxInstallIsEnglish();
54 static bool SetInitialNotificationShown(bool wasShown
) {
55 return !RegistrySetValueBool(IsPrefixed::Unprefixed
,
56 L
"InitialNotificationShown", wasShown
)
60 static bool GetInitialNotificationShown() {
61 return RegistryGetValueBool(IsPrefixed::Unprefixed
,
62 L
"InitialNotificationShown")
63 .unwrapOr(mozilla::Some(false))
67 static bool ResetInitialNotificationShown() {
68 return RegistryDeleteValue(IsPrefixed::Unprefixed
,
69 L
"InitialNotificationShown")
73 static bool SetFollowupNotificationShown(bool wasShown
) {
74 return !RegistrySetValueBool(IsPrefixed::Unprefixed
,
75 L
"FollowupNotificationShown", wasShown
)
79 static bool GetFollowupNotificationShown() {
80 return RegistryGetValueBool(IsPrefixed::Unprefixed
,
81 L
"FollowupNotificationShown")
82 .unwrapOr(mozilla::Some(false))
86 static bool SetFollowupNotificationSuppressed(bool value
) {
87 return !RegistrySetValueBool(IsPrefixed::Unprefixed
,
88 L
"FollowupNotificationSuppressed", value
)
92 static bool GetFollowupNotificationSuppressed() {
93 return RegistryGetValueBool(IsPrefixed::Unprefixed
,
94 L
"FollowupNotificationSuppressed")
95 .unwrapOr(mozilla::Some(false))
99 // Returns 0 if no value is set.
100 static ULONGLONG
GetFollowupNotificationRequestTime() {
101 return RegistryGetValueQword(IsPrefixed::Unprefixed
, L
"FollowupRequestTime")
102 .unwrapOr(mozilla::Some(0))
106 // Returns false if no value is set.
107 static bool GetPrefSetDefaultBrowserUserChoice() {
108 return RegistryGetValueBool(IsPrefixed::Prefixed
,
109 L
"SetDefaultBrowserUserChoice")
110 .unwrapOr(mozilla::Some(false))
114 struct ToastStrings
{
115 mozilla::UniquePtr
<wchar_t[]> text1
;
116 mozilla::UniquePtr
<wchar_t[]> text2
;
117 mozilla::UniquePtr
<wchar_t[]> action1
;
118 mozilla::UniquePtr
<wchar_t[]> action2
;
119 mozilla::UniquePtr
<wchar_t[]> relImagePath
;
123 // Toast notification button text is hard to localize because it tends to
124 // overflow. Thus, we have 3 different toast notifications:
125 // - The initial notification, which includes a button with text like
126 // "Ask me later". Since we cannot easily localize this, we will display
127 // it only in English.
128 // - The followup notification, to be shown if the user clicked "Ask me
129 // later". Since we only have that button in English, we only need this
130 // notification in English.
131 // - The localized notification, which has much shorter button text to
132 // (hopefully) prevent overflow: just "Yes" and "No". Since we no longer
133 // have an "Ask me later" button, a followup localized notification is not
135 ToastStrings initialToast
;
136 ToastStrings followupToast
;
137 ToastStrings localizedToast
;
139 // Returned pointer points within this struct and should not be freed.
140 const ToastStrings
* GetToastStrings(NotificationType whichToast
,
141 bool englishStrings
) const {
142 if (!englishStrings
) {
143 return &localizedToast
;
145 if (whichToast
== NotificationType::Initial
) {
146 return &initialToast
;
148 return &followupToast
;
152 // Gets all strings out of the relevant INI files.
153 // Returns true on success, false on failure
154 static bool GetStrings(Strings
& strings
) {
155 mozilla::UniquePtr
<wchar_t[]> installPath
;
156 bool success
= GetInstallDirectory(installPath
);
158 LOG_ERROR_MESSAGE(L
"Failed to get install directory when getting strings");
161 nsTArray
<nsCString
> resIds
= {"branding/brand.ftl"_ns
,
162 "browser/backgroundtasks/defaultagent.ftl"_ns
};
163 RefPtr
<Localization
> l10n
= Localization::Create(resIds
, true);
164 nsAutoCString daHeaderText
, daBodyText
, daYesButton
, daNoButton
;
165 mozilla::ErrorResult daRv
;
166 l10n
->FormatValueSync("default-browser-notification-header-text"_ns
, {},
168 ENSURE_SUCCESS(daRv
, false);
169 l10n
->FormatValueSync("default-browser-notification-body-text"_ns
, {},
171 ENSURE_SUCCESS(daRv
, false);
172 l10n
->FormatValueSync("default-browser-notification-yes-button-text"_ns
, {},
174 ENSURE_SUCCESS(daRv
, false);
175 l10n
->FormatValueSync("default-browser-notification-no-button-text"_ns
, {},
177 ENSURE_SUCCESS(daRv
, false);
179 NS_ConvertUTF8toUTF16
daHeaderTextW(daHeaderText
), daBodyTextW(daBodyText
),
180 daYesButtonW(daYesButton
), daNoButtonW(daNoButton
);
181 strings
.localizedToast
.text1
=
182 mozilla::MakeUnique
<wchar_t[]>(daHeaderTextW
.Length() + 1);
183 wcsncpy(strings
.localizedToast
.text1
.get(), daHeaderTextW
.get(),
184 daHeaderTextW
.Length() + 1);
185 strings
.localizedToast
.text2
=
186 mozilla::MakeUnique
<wchar_t[]>(daBodyTextW
.Length() + 1);
187 wcsncpy(strings
.localizedToast
.text2
.get(), daBodyTextW
.get(),
188 daBodyTextW
.Length() + 1);
189 strings
.localizedToast
.action1
=
190 mozilla::MakeUnique
<wchar_t[]>(daYesButtonW
.Length() + 1);
191 wcsncpy(strings
.localizedToast
.action1
.get(), daYesButtonW
.get(),
192 daYesButtonW
.Length() + 1);
193 strings
.localizedToast
.action2
=
194 mozilla::MakeUnique
<wchar_t[]>(daNoButtonW
.Length() + 1);
195 wcsncpy(strings
.localizedToast
.action2
.get(), daNoButtonW
.get(),
196 daNoButtonW
.Length() + 1);
197 const wchar_t* iniFormat
= L
"%s\\defaultagent.ini";
198 int bufferSize
= _scwprintf(iniFormat
, installPath
.get());
199 ++bufferSize
; // Extra character for terminating null
200 mozilla::UniquePtr
<wchar_t[]> iniPath
=
201 mozilla::MakeUnique
<wchar_t[]>(bufferSize
);
202 _snwprintf_s(iniPath
.get(), bufferSize
, _TRUNCATE
, iniFormat
,
205 IniReader
nonlocalizedReader(iniPath
.get(), "Nonlocalized");
206 nonlocalizedReader
.AddKey("InitialToastRelativeImagePath",
207 &strings
.initialToast
.relImagePath
);
208 nonlocalizedReader
.AddKey("FollowupToastRelativeImagePath",
209 &strings
.followupToast
.relImagePath
);
210 nonlocalizedReader
.AddKey("LocalizedToastRelativeImagePath",
211 &strings
.localizedToast
.relImagePath
);
212 int result
= nonlocalizedReader
.Read();
214 LOG_ERROR_MESSAGE(L
"Unable to read non-localized strings: %d", result
);
221 static mozilla::WindowsError
LaunchFirefoxToHandleDefaultBrowserAgent() {
222 // Could also be `MOZ_APP_NAME.exe`, but there's no generality to be gained:
223 // the WDBA is Firefox-only.
224 FilePathResult firefoxPathResult
= GetRelativeBinaryPath(L
"firefox.exe");
225 if (firefoxPathResult
.isErr()) {
226 return firefoxPathResult
.unwrapErr();
228 std::wstring firefoxPath
= firefoxPathResult
.unwrap();
230 _bstr_t cmd
= firefoxPath
.c_str();
231 // Omit argv[0] because ShellExecute doesn't need it.
232 _variant_t
args(L
"-to-handle-default-browser-agent");
233 _variant_t
operation(L
"open");
234 _variant_t directory
;
235 _variant_t
showCmd(SW_SHOWNORMAL
);
237 // To prevent inheriting environment variables from the background task, we
238 // run Firefox via Explorer instead of our own process. This mimics the
239 // implementation of the Windows Launcher Process.
241 ShellExecuteByExplorer(cmd
, args
, operation
, directory
, showCmd
);
242 NS_ENSURE_TRUE(result
.isOk(), result
.unwrapErr());
244 return mozilla::WindowsError::CreateSuccess();
248 * Set the default browser.
250 * First check if we can directly write UserChoice, if so attempt that.
251 * If we can't write UserChoice, or if the attempt fails, fall back to
252 * showing the Default Apps page of Settings.
254 * @param aAumi The AUMI of the installation to set as default.
256 static void SetDefaultBrowserFromNotification(const wchar_t* aumi
) {
257 nsresult rv
= NS_ERROR_FAILURE
;
258 if (GetPrefSetDefaultBrowserUserChoice()) {
259 rv
= SetDefaultBrowserUserChoice(aumi
);
262 if (NS_SUCCEEDED(rv
)) {
263 mozilla::Unused
<< LaunchFirefoxToHandleDefaultBrowserAgent();
265 LOG_ERROR_MESSAGE(L
"Failed to SetDefaultBrowserUserChoice: %#X",
267 LaunchModernSettingsDialogDefaultApps();
271 // This encapsulates the data that needs to be protected by a mutex because it
272 // will be shared by the main thread and the handler thread.
273 // To ensure the data is only written once, handlerDataHasBeenSet should be
274 // initialized to false, then set to true when the handler writes data into the
277 NotificationActivities activitiesPerformed
;
278 bool handlerDataHasBeenSet
;
281 // The value that ToastHandler writes into should be a global. We can't control
282 // when ToastHandler is called, and if this value isn't a global, ToastHandler
283 // may be called and attempt to access this after it has been deconstructed.
284 // Since this value is accessed by the handler thread and the main thread, it
285 // is protected by a mutex (gHandlerMutex).
286 // Since ShowNotification deconstructs the mutex, it might seem like once
287 // ShowNotification exits, we can just rely on the inability to wait on an
288 // invalid mutex to protect the deconstructed data, but it's possible that
289 // we could deconstruct the mutex while the handler is holding it and is
290 // already accessing the protected data.
291 static HandlerData gHandlerReturnData
;
292 static HANDLE gHandlerMutex
= INVALID_HANDLE_VALUE
;
294 class ToastHandler
: public WinToastLib::IWinToastHandler
{
296 NotificationType mWhichNotification
;
298 const std::wstring mAumiStr
;
301 ToastHandler(NotificationType whichNotification
, HANDLE event
,
303 : mWhichNotification(whichNotification
), mEvent(event
), mAumiStr(aumi
) {}
305 void FinishHandler(NotificationActivities
& returnData
) const {
306 SetReturnData(returnData
);
308 BOOL success
= SetEvent(mEvent
);
310 LOG_ERROR_MESSAGE(L
"Event could not be set: %#X", GetLastError());
314 void SetReturnData(NotificationActivities
& toSet
) const {
315 DWORD result
= WaitForSingleObject(gHandlerMutex
, MUTEX_TIMEOUT_MS
);
316 if (result
== WAIT_TIMEOUT
) {
317 LOG_ERROR_MESSAGE(L
"Unable to obtain mutex ownership");
319 } else if (result
== WAIT_FAILED
) {
320 LOG_ERROR_MESSAGE(L
"Failed to wait on mutex: %#X", GetLastError());
322 } else if (result
== WAIT_ABANDONED
) {
323 LOG_ERROR_MESSAGE(L
"Found abandoned mutex");
324 ReleaseMutex(gHandlerMutex
);
328 // Only set this data once
329 if (!gHandlerReturnData
.handlerDataHasBeenSet
) {
330 gHandlerReturnData
.activitiesPerformed
= toSet
;
331 gHandlerReturnData
.handlerDataHasBeenSet
= true;
334 BOOL success
= ReleaseMutex(gHandlerMutex
);
336 LOG_ERROR_MESSAGE(L
"Unable to release mutex ownership: %#X",
341 void toastActivated() const override
{
342 NotificationActivities activitiesPerformed
;
343 activitiesPerformed
.type
= mWhichNotification
;
344 activitiesPerformed
.shown
= NotificationShown::Shown
;
345 activitiesPerformed
.action
= NotificationAction::ToastClicked
;
347 // Notification strings are written to indicate the default browser is
348 // restored to Firefox when the notification body is clicked to prevent
349 // ambiguity when buttons aren't pressed.
350 SetDefaultBrowserFromNotification(mAumiStr
.c_str());
352 FinishHandler(activitiesPerformed
);
355 void toastActivated(int actionIndex
) const override
{
356 NotificationActivities activitiesPerformed
;
357 activitiesPerformed
.type
= mWhichNotification
;
358 activitiesPerformed
.shown
= NotificationShown::Shown
;
359 // Override this below
360 activitiesPerformed
.action
= NotificationAction::NoAction
;
362 if (actionIndex
== 0) {
363 // "Make Firefox the default" button, on both the initial and followup
364 // notifications. "Yes" button on the localized notification.
365 activitiesPerformed
.action
= NotificationAction::MakeFirefoxDefaultButton
;
367 SetDefaultBrowserFromNotification(mAumiStr
.c_str());
368 } else if (actionIndex
== 1) {
369 // Do nothing. As long as we don't call
370 // SetFollowupNotificationRequestTime, there will be no followup
372 activitiesPerformed
.action
= NotificationAction::DismissedByButton
;
375 FinishHandler(activitiesPerformed
);
378 void toastDismissed(WinToastDismissalReason state
) const override
{
379 NotificationActivities activitiesPerformed
;
380 activitiesPerformed
.type
= mWhichNotification
;
381 activitiesPerformed
.shown
= NotificationShown::Shown
;
382 // Override this below
383 activitiesPerformed
.action
= NotificationAction::NoAction
;
385 if (state
== WinToastDismissalReason::TimedOut
) {
386 activitiesPerformed
.action
= NotificationAction::DismissedByTimeout
;
387 } else if (state
== WinToastDismissalReason::ApplicationHidden
) {
388 activitiesPerformed
.action
=
389 NotificationAction::DismissedByApplicationHidden
;
390 } else if (state
== WinToastDismissalReason::UserCanceled
) {
391 activitiesPerformed
.action
= NotificationAction::DismissedToActionCenter
;
394 FinishHandler(activitiesPerformed
);
397 void toastFailed() const override
{
398 NotificationActivities activitiesPerformed
;
399 activitiesPerformed
.type
= mWhichNotification
;
400 activitiesPerformed
.shown
= NotificationShown::Error
;
401 activitiesPerformed
.action
= NotificationAction::NoAction
;
403 LOG_ERROR_MESSAGE(L
"Toast notification failed to display");
404 FinishHandler(activitiesPerformed
);
408 // This function blocks until the shown notification is activated or dismissed.
409 static NotificationActivities
ShowNotification(
410 NotificationType whichNotification
, const wchar_t* aumi
) {
411 // Initially set the value that will be returned to error. If the notification
412 // is shown successfully, we'll update it.
413 NotificationActivities activitiesPerformed
= {whichNotification
,
414 NotificationShown::Error
,
415 NotificationAction::NoAction
};
417 bool isEnglishInstall
= FirefoxInstallIsEnglish();
420 if (!GetStrings(strings
)) {
421 return activitiesPerformed
;
423 const ToastStrings
* toastStrings
=
424 strings
.GetToastStrings(whichNotification
, isEnglishInstall
);
426 mozilla::mscom::EnsureMTA([&] {
427 using namespace WinToastLib
;
429 if (!WinToast::isCompatible()) {
430 LOG_ERROR_MESSAGE(L
"System is not compatible with WinToast");
433 WinToast::instance()->setAppName(L
"" MOZ_APP_DISPLAYNAME
);
434 std::wstring aumiStr
= aumi
;
435 WinToast::instance()->setAppUserModelId(aumiStr
);
436 WinToast::instance()->setShortcutPolicy(
437 WinToastLib::WinToast::SHORTCUT_POLICY_REQUIRE_NO_CREATE
);
438 WinToast::WinToastError error
;
439 if (!WinToast::instance()->initialize(&error
)) {
440 LOG_ERROR_MESSAGE(WinToast::strerror(error
).c_str());
444 // This event object will let the handler notify us when it has handled the
446 nsAutoHandle
event(CreateEventW(nullptr, TRUE
, FALSE
, nullptr));
447 if (event
.get() == nullptr) {
448 LOG_ERROR_MESSAGE(L
"Unable to create event object: %#X", GetLastError());
452 bool success
= false;
453 if (whichNotification
== NotificationType::Initial
) {
454 success
= SetInitialNotificationShown(true);
456 success
= SetFollowupNotificationShown(true);
459 // Return early in this case to prevent the notification from being shown
461 LOG_ERROR_MESSAGE(L
"Unable to set notification as displayed");
465 // We need the absolute image path, not the relative path.
466 mozilla::UniquePtr
<wchar_t[]> installPath
;
467 success
= GetInstallDirectory(installPath
);
469 LOG_ERROR_MESSAGE(L
"Failed to get install directory for the image path");
472 const wchar_t* absPathFormat
= L
"%s\\%s";
473 int bufferSize
= _scwprintf(absPathFormat
, installPath
.get(),
474 toastStrings
->relImagePath
.get());
475 ++bufferSize
; // Extra character for terminating null
476 mozilla::UniquePtr
<wchar_t[]> absImagePath
=
477 mozilla::MakeUnique
<wchar_t[]>(bufferSize
);
478 _snwprintf_s(absImagePath
.get(), bufferSize
, _TRUNCATE
, absPathFormat
,
479 installPath
.get(), toastStrings
->relImagePath
.get());
481 // This is used to protect gHandlerReturnData.
482 gHandlerMutex
= CreateMutexW(nullptr, TRUE
, nullptr);
483 if (gHandlerMutex
== nullptr) {
484 LOG_ERROR_MESSAGE(L
"Unable to create mutex: %#X", GetLastError());
487 // Automatically close this mutex when this function exits.
488 nsAutoHandle
autoMutex(gHandlerMutex
);
489 // No need to initialize gHandlerReturnData.activitiesPerformed, since it
490 // will be set by the handler. But we do need to initialize
491 // gHandlerReturnData.handlerDataHasBeenSet so the handler knows that no
492 // data has been set yet.
493 gHandlerReturnData
.handlerDataHasBeenSet
= false;
494 success
= ReleaseMutex(gHandlerMutex
);
496 LOG_ERROR_MESSAGE(L
"Unable to release mutex ownership: %#X",
500 // Finally ready to assemble the notification and dispatch it.
501 WinToastTemplate toastTemplate
=
502 WinToastTemplate(WinToastTemplate::ImageAndText02
);
503 toastTemplate
.setTextField(toastStrings
->text1
.get(),
504 WinToastTemplate::FirstLine
);
505 toastTemplate
.setTextField(toastStrings
->text2
.get(),
506 WinToastTemplate::SecondLine
);
507 toastTemplate
.addAction(toastStrings
->action1
.get());
508 toastTemplate
.addAction(toastStrings
->action2
.get());
509 toastTemplate
.setImagePath(absImagePath
.get());
510 toastTemplate
.setScenario(WinToastTemplate::Scenario::Reminder
);
511 ToastHandler
* handler
=
512 new ToastHandler(whichNotification
, event
.get(), aumi
);
513 INT64 id
= WinToast::instance()->showToast(toastTemplate
, handler
, &error
);
515 LOG_ERROR_MESSAGE(WinToast::strerror(error
).c_str());
520 WaitForSingleObject(event
.get(), NOTIFICATION_WAIT_TIMEOUT_MS
);
521 // Don't return after these errors. Attempt to hide the notification.
522 if (result
== WAIT_FAILED
) {
523 LOG_ERROR_MESSAGE(L
"Unable to wait on event object: %#X", GetLastError());
524 } else if (result
== WAIT_TIMEOUT
) {
525 LOG_ERROR_MESSAGE(L
"Timed out waiting for event object");
527 result
= WaitForSingleObject(gHandlerMutex
, MUTEX_TIMEOUT_MS
);
528 if (result
== WAIT_TIMEOUT
) {
529 LOG_ERROR_MESSAGE(L
"Unable to obtain mutex ownership");
530 // activitiesPerformed is already set to error. No change needed.
531 } else if (result
== WAIT_FAILED
) {
532 LOG_ERROR_MESSAGE(L
"Failed to wait on mutex: %#X", GetLastError());
533 // activitiesPerformed is already set to error. No change needed.
534 } else if (result
== WAIT_ABANDONED
) {
535 LOG_ERROR_MESSAGE(L
"Found abandoned mutex");
536 ReleaseMutex(gHandlerMutex
);
537 // activitiesPerformed is already set to error. No change needed.
539 // Mutex is being held. It is safe to access gHandlerReturnData.
540 // If gHandlerReturnData.handlerDataHasBeenSet is false, the handler
541 // never ran. Use the error value activitiesPerformed already contains.
542 if (gHandlerReturnData
.handlerDataHasBeenSet
) {
543 activitiesPerformed
= gHandlerReturnData
.activitiesPerformed
;
546 success
= ReleaseMutex(gHandlerMutex
);
548 LOG_ERROR_MESSAGE(L
"Unable to release mutex ownership: %#X",
554 if (!WinToast::instance()->hideToast(id
)) {
555 LOG_ERROR_MESSAGE(L
"Failed to hide notification");
558 return activitiesPerformed
;
561 // Previously this function checked that the Firefox build was using English.
562 // This was checked because of the peculiar way we were localizing toast
563 // notifications where we used a completely different set of strings in English.
565 // We've since unified the notification flows but need to clean up unused code
566 // and config files - Bug 1826375.
567 bool FirefoxInstallIsEnglish() { return false; }
569 // If a notification is shown, this function will block until the notification
570 // is activated or dismissed.
571 // aumi is the App User Model ID.
572 NotificationActivities
MaybeShowNotification(
573 const DefaultBrowserInfo
& browserInfo
, const wchar_t* aumi
, bool force
) {
574 // Default to not showing a notification. Any other value will be returned
575 // directly from ShowNotification.
576 NotificationActivities activitiesPerformed
= {NotificationType::Initial
,
577 NotificationShown::NotShown
,
578 NotificationAction::NoAction
};
580 // Reset notification state machine, user setting default browser to Firefox
581 // is a strong signal that they intend to have it as the default browser.
582 if (browserInfo
.currentDefaultBrowser
== Browser::Firefox
) {
583 ResetInitialNotificationShown();
586 bool initialNotificationShown
= GetInitialNotificationShown();
587 if (!initialNotificationShown
|| force
) {
588 if ((browserInfo
.currentDefaultBrowser
== Browser::EdgeWithBlink
&&
589 browserInfo
.previousDefaultBrowser
== Browser::Firefox
) ||
591 return ShowNotification(NotificationType::Initial
, aumi
);
593 return activitiesPerformed
;
595 activitiesPerformed
.type
= NotificationType::Followup
;
597 ULONGLONG followupNotificationRequestTime
=
598 GetFollowupNotificationRequestTime();
599 bool followupNotificationRequested
= followupNotificationRequestTime
!= 0;
600 bool followupNotificationShown
= GetFollowupNotificationShown();
601 if (followupNotificationRequested
&& !followupNotificationShown
&&
602 !GetFollowupNotificationSuppressed()) {
603 ULONGLONG secondsSinceRequestTime
=
604 SecondsPassedSince(followupNotificationRequestTime
);
606 if (secondsSinceRequestTime
>= SEVEN_DAYS_IN_SECONDS
) {
607 // If we go to show the followup notification and the user has already
608 // changed the default browser, permanently suppress the followup since
609 // it's no longer relevant.
610 if (browserInfo
.currentDefaultBrowser
== Browser::EdgeWithBlink
) {
611 return ShowNotification(NotificationType::Followup
, aumi
);
613 SetFollowupNotificationSuppressed(true);
617 return activitiesPerformed
;
620 std::string
GetStringForNotificationType(NotificationType type
) {
622 case NotificationType::Initial
:
623 return std::string("initial");
624 case NotificationType::Followup
:
625 return std::string("followup");
629 std::string
GetStringForNotificationShown(NotificationShown shown
) {
631 case NotificationShown::NotShown
:
632 return std::string("not-shown");
633 case NotificationShown::Shown
:
634 return std::string("shown");
635 case NotificationShown::Error
:
636 return std::string("error");
640 NotificationShown
GetNotificationShownFromString(const nsAString
& shown
) {
641 if (shown
== u
"not-shown"_ns
) {
642 return NotificationShown::NotShown
;
643 } else if (shown
== u
"shown"_ns
) {
644 return NotificationShown::Shown
;
645 } else if (shown
== u
"error"_ns
) {
646 return NotificationShown::Error
;
649 return NotificationShown::Error
;
653 std::string
GetStringForNotificationAction(NotificationAction action
) {
655 case NotificationAction::DismissedByTimeout
:
656 return std::string("dismissed-by-timeout");
657 case NotificationAction::DismissedToActionCenter
:
658 return std::string("dismissed-to-action-center");
659 case NotificationAction::DismissedByButton
:
660 return std::string("dismissed-by-button");
661 case NotificationAction::DismissedByApplicationHidden
:
662 return std::string("dismissed-by-application-hidden");
663 case NotificationAction::RemindMeLater
:
664 return std::string("remind-me-later");
665 case NotificationAction::MakeFirefoxDefaultButton
:
666 return std::string("make-firefox-default-button");
667 case NotificationAction::ToastClicked
:
668 return std::string("toast-clicked");
669 case NotificationAction::NoAction
:
670 return std::string("no-action");
674 NotificationAction
GetNotificationActionFromString(const nsAString
& action
) {
675 if (action
== u
"dismissed-by-timeout"_ns
) {
676 return NotificationAction::DismissedByTimeout
;
677 } else if (action
== u
"dismissed-to-action-center"_ns
) {
678 return NotificationAction::DismissedToActionCenter
;
679 } else if (action
== u
"dismissed-by-button"_ns
) {
680 return NotificationAction::DismissedByButton
;
681 } else if (action
== u
"dismissed-by-application-hidden"_ns
) {
682 return NotificationAction::DismissedByApplicationHidden
;
683 } else if (action
== u
"remind-me-later"_ns
) {
684 return NotificationAction::RemindMeLater
;
685 } else if (action
== u
"make-firefox-default-button"_ns
) {
686 return NotificationAction::MakeFirefoxDefaultButton
;
687 } else if (action
== u
"toast-clicked"_ns
) {
688 return NotificationAction::ToastClicked
;
689 } else if (action
== u
"no-action"_ns
) {
690 return NotificationAction::NoAction
;
693 return NotificationAction::NoAction
;
697 void EnsureValidNotificationAction(std::string
& actionString
) {
698 if (actionString
!= "dismissed-by-timeout" &&
699 actionString
!= "dismissed-to-action-center" &&
700 actionString
!= "dismissed-by-button" &&
701 actionString
!= "dismissed-by-application-hidden" &&
702 actionString
!= "remind-me-later" &&
703 actionString
!= "make-firefox-default-button" &&
704 actionString
!= "toast-clicked" && actionString
!= "no-action") {
705 actionString
= "no-action";
709 } // namespace mozilla::default_agent