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/UniquePtr.h"
17 #include "mozilla/Unused.h"
18 #include "mozilla/WinHeaderOnlyUtils.h"
19 #include "nsWindowsHelpers.h"
20 #include "readstrings.h"
21 #include "updatererrors.h"
22 #include "WindowsDefaultBrowser.h"
25 #include "DefaultBrowser.h"
28 #include "SetDefaultBrowser.h"
30 #include "wintoastlib.h"
32 #define SEVEN_DAYS_IN_SECONDS (7 * 24 * 60 * 60)
34 // If the notification hasn't been activated or dismissed within 12 hours,
35 // stop waiting for it.
36 #define NOTIFICATION_WAIT_TIMEOUT_MS (12 * 60 * 60 * 1000)
37 // If the mutex hasn't been released within a few minutes, something is wrong
38 // and we should give up on it
39 #define MUTEX_TIMEOUT_MS (10 * 60 * 1000)
41 namespace mozilla::default_agent
{
43 bool FirefoxInstallIsEnglish();
45 static bool SetInitialNotificationShown(bool wasShown
) {
46 return !RegistrySetValueBool(IsPrefixed::Unprefixed
,
47 L
"InitialNotificationShown", wasShown
)
51 static bool GetInitialNotificationShown() {
52 return RegistryGetValueBool(IsPrefixed::Unprefixed
,
53 L
"InitialNotificationShown")
54 .unwrapOr(mozilla::Some(false))
58 static bool ResetInitialNotificationShown() {
59 return RegistryDeleteValue(IsPrefixed::Unprefixed
,
60 L
"InitialNotificationShown")
64 static bool SetFollowupNotificationShown(bool wasShown
) {
65 return !RegistrySetValueBool(IsPrefixed::Unprefixed
,
66 L
"FollowupNotificationShown", wasShown
)
70 static bool GetFollowupNotificationShown() {
71 return RegistryGetValueBool(IsPrefixed::Unprefixed
,
72 L
"FollowupNotificationShown")
73 .unwrapOr(mozilla::Some(false))
77 static bool SetFollowupNotificationSuppressed(bool value
) {
78 return !RegistrySetValueBool(IsPrefixed::Unprefixed
,
79 L
"FollowupNotificationSuppressed", value
)
83 static bool GetFollowupNotificationSuppressed() {
84 return RegistryGetValueBool(IsPrefixed::Unprefixed
,
85 L
"FollowupNotificationSuppressed")
86 .unwrapOr(mozilla::Some(false))
90 // Returns 0 if no value is set.
91 static ULONGLONG
GetFollowupNotificationRequestTime() {
92 return RegistryGetValueQword(IsPrefixed::Unprefixed
, L
"FollowupRequestTime")
93 .unwrapOr(mozilla::Some(0))
97 // Returns false if no value is set.
98 static bool GetPrefSetDefaultBrowserUserChoice() {
99 return RegistryGetValueBool(IsPrefixed::Prefixed
,
100 L
"SetDefaultBrowserUserChoice")
101 .unwrapOr(mozilla::Some(false))
105 struct ToastStrings
{
106 mozilla::UniquePtr
<wchar_t[]> text1
;
107 mozilla::UniquePtr
<wchar_t[]> text2
;
108 mozilla::UniquePtr
<wchar_t[]> action1
;
109 mozilla::UniquePtr
<wchar_t[]> action2
;
110 mozilla::UniquePtr
<wchar_t[]> relImagePath
;
114 // Toast notification button text is hard to localize because it tends to
115 // overflow. Thus, we have 3 different toast notifications:
116 // - The initial notification, which includes a button with text like
117 // "Ask me later". Since we cannot easily localize this, we will display
118 // it only in English.
119 // - The followup notification, to be shown if the user clicked "Ask me
120 // later". Since we only have that button in English, we only need this
121 // notification in English.
122 // - The localized notification, which has much shorter button text to
123 // (hopefully) prevent overflow: just "Yes" and "No". Since we no longer
124 // have an "Ask me later" button, a followup localized notification is not
126 ToastStrings initialToast
;
127 ToastStrings followupToast
;
128 ToastStrings localizedToast
;
130 // Returned pointer points within this struct and should not be freed.
131 const ToastStrings
* GetToastStrings(NotificationType whichToast
,
132 bool englishStrings
) const {
133 if (!englishStrings
) {
134 return &localizedToast
;
136 if (whichToast
== NotificationType::Initial
) {
137 return &initialToast
;
139 return &followupToast
;
143 // Gets all strings out of the relevant INI files.
144 // Returns true on success, false on failure
145 static bool GetStrings(Strings
& strings
) {
146 mozilla::UniquePtr
<wchar_t[]> installPath
;
147 bool success
= GetInstallDirectory(installPath
);
149 LOG_ERROR_MESSAGE(L
"Failed to get install directory when getting strings");
152 const wchar_t* iniFormat
= L
"%s\\defaultagent.ini";
153 int bufferSize
= _scwprintf(iniFormat
, installPath
.get());
154 ++bufferSize
; // Extra character for terminating null
155 mozilla::UniquePtr
<wchar_t[]> iniPath
=
156 mozilla::MakeUnique
<wchar_t[]>(bufferSize
);
157 _snwprintf_s(iniPath
.get(), bufferSize
, _TRUNCATE
, iniFormat
,
160 IniReader
stringsReader(iniPath
.get());
161 stringsReader
.AddKey("DefaultBrowserNotificationTitle",
162 &strings
.initialToast
.text1
);
163 stringsReader
.AddKey("DefaultBrowserNotificationTitle",
164 &strings
.followupToast
.text1
);
165 stringsReader
.AddKey("DefaultBrowserNotificationText",
166 &strings
.initialToast
.text2
);
167 stringsReader
.AddKey("DefaultBrowserNotificationText",
168 &strings
.followupToast
.text2
);
169 stringsReader
.AddKey("DefaultBrowserNotificationMakeFirefoxDefault",
170 &strings
.initialToast
.action1
);
171 stringsReader
.AddKey("DefaultBrowserNotificationMakeFirefoxDefault",
172 &strings
.followupToast
.action1
);
173 stringsReader
.AddKey("DefaultBrowserNotificationDontShowAgain",
174 &strings
.initialToast
.action2
);
175 stringsReader
.AddKey("DefaultBrowserNotificationDontShowAgain",
176 &strings
.followupToast
.action2
);
177 int result
= stringsReader
.Read();
179 LOG_ERROR_MESSAGE(L
"Unable to read English strings: %d", result
);
183 const wchar_t* localizedIniFormat
= L
"%s\\defaultagent_localized.ini";
184 bufferSize
= _scwprintf(localizedIniFormat
, installPath
.get());
185 ++bufferSize
; // Extra character for terminating null
186 mozilla::UniquePtr
<wchar_t[]> localizedIniPath
=
187 mozilla::MakeUnique
<wchar_t[]>(bufferSize
);
188 _snwprintf_s(localizedIniPath
.get(), bufferSize
, _TRUNCATE
,
189 localizedIniFormat
, installPath
.get());
191 IniReader
localizedReader(localizedIniPath
.get());
192 localizedReader
.AddKey("DefaultBrowserNotificationHeaderText",
193 &strings
.localizedToast
.text1
);
194 localizedReader
.AddKey("DefaultBrowserNotificationBodyText",
195 &strings
.localizedToast
.text2
);
196 localizedReader
.AddKey("DefaultBrowserNotificationYesButtonText",
197 &strings
.localizedToast
.action1
);
198 localizedReader
.AddKey("DefaultBrowserNotificationNoButtonText",
199 &strings
.localizedToast
.action2
);
200 result
= localizedReader
.Read();
202 LOG_ERROR_MESSAGE(L
"Unable to read localized strings: %d", result
);
206 // IniReader is only capable of reading from one section at a time, so we need
207 // to make another one to read the other section.
208 IniReader
nonlocalizedReader(iniPath
.get(), "Nonlocalized");
209 nonlocalizedReader
.AddKey("InitialToastRelativeImagePath",
210 &strings
.initialToast
.relImagePath
);
211 nonlocalizedReader
.AddKey("FollowupToastRelativeImagePath",
212 &strings
.followupToast
.relImagePath
);
213 nonlocalizedReader
.AddKey("LocalizedToastRelativeImagePath",
214 &strings
.localizedToast
.relImagePath
);
215 result
= nonlocalizedReader
.Read();
217 LOG_ERROR_MESSAGE(L
"Unable to read non-localized strings: %d", result
);
224 static mozilla::WindowsError
LaunchFirefoxToHandleDefaultBrowserAgent() {
225 // Could also be `MOZ_APP_NAME.exe`, but there's no generality to be gained:
226 // the WDBA is Firefox-only.
227 FilePathResult firefoxPathResult
= GetRelativeBinaryPath(L
"firefox.exe");
228 if (firefoxPathResult
.isErr()) {
229 return firefoxPathResult
.unwrapErr();
231 std::wstring firefoxPath
= firefoxPathResult
.unwrap();
233 const wchar_t* firefoxArgs
[] = {firefoxPath
.c_str(),
234 L
"-to-handle-default-browser-agent"};
235 mozilla::UniquePtr
<wchar_t[]> firefoxCmdLine(mozilla::MakeCommandLine(
236 mozilla::ArrayLength(firefoxArgs
), const_cast<wchar_t**>(firefoxArgs
)));
238 PROCESS_INFORMATION pi
;
239 STARTUPINFOW si
= {sizeof(si
)};
240 if (!::CreateProcessW(firefoxPath
.c_str(), firefoxCmdLine
.get(), nullptr,
242 DETACHED_PROCESS
| NORMAL_PRIORITY_CLASS
, nullptr,
243 nullptr, &si
, &pi
)) {
244 HRESULT hr
= HRESULT_FROM_WIN32(GetLastError());
246 return mozilla::WindowsError::FromHResult(hr
);
249 CloseHandle(pi
.hThread
);
250 CloseHandle(pi
.hProcess
);
252 return mozilla::WindowsError::CreateSuccess();
256 * Set the default browser.
258 * First check if we can directly write UserChoice, if so attempt that.
259 * If we can't write UserChoice, or if the attempt fails, fall back to
260 * showing the Default Apps page of Settings.
262 * @param aAumi The AUMI of the installation to set as default.
264 static void SetDefaultBrowserFromNotification(const wchar_t* aumi
) {
265 nsresult rv
= NS_ERROR_FAILURE
;
266 if (GetPrefSetDefaultBrowserUserChoice()) {
267 rv
= SetDefaultBrowserUserChoice(aumi
);
270 if (NS_SUCCEEDED(rv
)) {
271 mozilla::Unused
<< LaunchFirefoxToHandleDefaultBrowserAgent();
273 LOG_ERROR_MESSAGE(L
"Failed to SetDefaultBrowserUserChoice: %#X",
275 LaunchModernSettingsDialogDefaultApps();
279 // This encapsulates the data that needs to be protected by a mutex because it
280 // will be shared by the main thread and the handler thread.
281 // To ensure the data is only written once, handlerDataHasBeenSet should be
282 // initialized to false, then set to true when the handler writes data into the
285 NotificationActivities activitiesPerformed
;
286 bool handlerDataHasBeenSet
;
289 // The value that ToastHandler writes into should be a global. We can't control
290 // when ToastHandler is called, and if this value isn't a global, ToastHandler
291 // may be called and attempt to access this after it has been deconstructed.
292 // Since this value is accessed by the handler thread and the main thread, it
293 // is protected by a mutex (gHandlerMutex).
294 // Since ShowNotification deconstructs the mutex, it might seem like once
295 // ShowNotification exits, we can just rely on the inability to wait on an
296 // invalid mutex to protect the deconstructed data, but it's possible that
297 // we could deconstruct the mutex while the handler is holding it and is
298 // already accessing the protected data.
299 static HandlerData gHandlerReturnData
;
300 static HANDLE gHandlerMutex
= INVALID_HANDLE_VALUE
;
302 class ToastHandler
: public WinToastLib::IWinToastHandler
{
304 NotificationType mWhichNotification
;
306 const std::wstring mAumiStr
;
309 ToastHandler(NotificationType whichNotification
, HANDLE event
,
311 : mWhichNotification(whichNotification
), mEvent(event
), mAumiStr(aumi
) {}
313 void FinishHandler(NotificationActivities
& returnData
) const {
314 SetReturnData(returnData
);
316 BOOL success
= SetEvent(mEvent
);
318 LOG_ERROR_MESSAGE(L
"Event could not be set: %#X", GetLastError());
322 void SetReturnData(NotificationActivities
& toSet
) const {
323 DWORD result
= WaitForSingleObject(gHandlerMutex
, MUTEX_TIMEOUT_MS
);
324 if (result
== WAIT_TIMEOUT
) {
325 LOG_ERROR_MESSAGE(L
"Unable to obtain mutex ownership");
327 } else if (result
== WAIT_FAILED
) {
328 LOG_ERROR_MESSAGE(L
"Failed to wait on mutex: %#X", GetLastError());
330 } else if (result
== WAIT_ABANDONED
) {
331 LOG_ERROR_MESSAGE(L
"Found abandoned mutex");
332 ReleaseMutex(gHandlerMutex
);
336 // Only set this data once
337 if (!gHandlerReturnData
.handlerDataHasBeenSet
) {
338 gHandlerReturnData
.activitiesPerformed
= toSet
;
339 gHandlerReturnData
.handlerDataHasBeenSet
= true;
342 BOOL success
= ReleaseMutex(gHandlerMutex
);
344 LOG_ERROR_MESSAGE(L
"Unable to release mutex ownership: %#X",
349 void toastActivated() const override
{
350 NotificationActivities activitiesPerformed
;
351 activitiesPerformed
.type
= mWhichNotification
;
352 activitiesPerformed
.shown
= NotificationShown::Shown
;
353 activitiesPerformed
.action
= NotificationAction::ToastClicked
;
355 // Notification strings are written to indicate the default browser is
356 // restored to Firefox when the notification body is clicked to prevent
357 // ambiguity when buttons aren't pressed.
358 SetDefaultBrowserFromNotification(mAumiStr
.c_str());
360 FinishHandler(activitiesPerformed
);
363 void toastActivated(int actionIndex
) const override
{
364 NotificationActivities activitiesPerformed
;
365 activitiesPerformed
.type
= mWhichNotification
;
366 activitiesPerformed
.shown
= NotificationShown::Shown
;
367 // Override this below
368 activitiesPerformed
.action
= NotificationAction::NoAction
;
370 if (actionIndex
== 0) {
371 // "Make Firefox the default" button, on both the initial and followup
372 // notifications. "Yes" button on the localized notification.
373 activitiesPerformed
.action
= NotificationAction::MakeFirefoxDefaultButton
;
375 SetDefaultBrowserFromNotification(mAumiStr
.c_str());
376 } else if (actionIndex
== 1) {
377 // Do nothing. As long as we don't call
378 // SetFollowupNotificationRequestTime, there will be no followup
380 activitiesPerformed
.action
= NotificationAction::DismissedByButton
;
383 FinishHandler(activitiesPerformed
);
386 void toastDismissed(WinToastDismissalReason state
) const override
{
387 NotificationActivities activitiesPerformed
;
388 activitiesPerformed
.type
= mWhichNotification
;
389 activitiesPerformed
.shown
= NotificationShown::Shown
;
390 // Override this below
391 activitiesPerformed
.action
= NotificationAction::NoAction
;
393 if (state
== WinToastDismissalReason::TimedOut
) {
394 activitiesPerformed
.action
= NotificationAction::DismissedByTimeout
;
395 } else if (state
== WinToastDismissalReason::ApplicationHidden
) {
396 activitiesPerformed
.action
=
397 NotificationAction::DismissedByApplicationHidden
;
398 } else if (state
== WinToastDismissalReason::UserCanceled
) {
399 activitiesPerformed
.action
= NotificationAction::DismissedToActionCenter
;
402 FinishHandler(activitiesPerformed
);
405 void toastFailed() const override
{
406 NotificationActivities activitiesPerformed
;
407 activitiesPerformed
.type
= mWhichNotification
;
408 activitiesPerformed
.shown
= NotificationShown::Error
;
409 activitiesPerformed
.action
= NotificationAction::NoAction
;
411 LOG_ERROR_MESSAGE(L
"Toast notification failed to display");
412 FinishHandler(activitiesPerformed
);
416 // This function blocks until the shown notification is activated or dismissed.
417 static NotificationActivities
ShowNotification(
418 NotificationType whichNotification
, const wchar_t* aumi
) {
419 // Initially set the value that will be returned to error. If the notification
420 // is shown successfully, we'll update it.
421 NotificationActivities activitiesPerformed
= {whichNotification
,
422 NotificationShown::Error
,
423 NotificationAction::NoAction
};
424 using namespace WinToastLib
;
426 if (!WinToast::isCompatible()) {
427 LOG_ERROR_MESSAGE(L
"System is not compatible with WinToast");
428 return activitiesPerformed
;
431 WinToast::instance()->setAppName(L
"" MOZ_APP_DISPLAYNAME
);
432 std::wstring aumiStr
= aumi
;
433 WinToast::instance()->setAppUserModelId(aumiStr
);
434 WinToast::instance()->setShortcutPolicy(
435 WinToastLib::WinToast::SHORTCUT_POLICY_REQUIRE_NO_CREATE
);
436 WinToast::WinToastError error
;
437 if (!WinToast::instance()->initialize(&error
)) {
438 LOG_ERROR_MESSAGE(WinToast::strerror(error
).c_str());
439 return activitiesPerformed
;
442 bool isEnglishInstall
= FirefoxInstallIsEnglish();
445 if (!GetStrings(strings
)) {
446 return activitiesPerformed
;
448 const ToastStrings
* toastStrings
=
449 strings
.GetToastStrings(whichNotification
, isEnglishInstall
);
451 // This event object will let the handler notify us when it has handled the
453 nsAutoHandle
event(CreateEventW(nullptr, TRUE
, FALSE
, nullptr));
454 if (event
.get() == nullptr) {
455 LOG_ERROR_MESSAGE(L
"Unable to create event object: %#X", GetLastError());
456 return activitiesPerformed
;
459 bool success
= false;
460 if (whichNotification
== NotificationType::Initial
) {
461 success
= SetInitialNotificationShown(true);
463 success
= SetFollowupNotificationShown(true);
466 // Return early in this case to prevent the notification from being shown
468 LOG_ERROR_MESSAGE(L
"Unable to set notification as displayed");
469 return activitiesPerformed
;
472 // We need the absolute image path, not the relative path.
473 mozilla::UniquePtr
<wchar_t[]> installPath
;
474 success
= GetInstallDirectory(installPath
);
476 LOG_ERROR_MESSAGE(L
"Failed to get install directory for the image path");
477 return activitiesPerformed
;
479 const wchar_t* absPathFormat
= L
"%s\\%s";
480 int bufferSize
= _scwprintf(absPathFormat
, installPath
.get(),
481 toastStrings
->relImagePath
.get());
482 ++bufferSize
; // Extra character for terminating null
483 mozilla::UniquePtr
<wchar_t[]> absImagePath
=
484 mozilla::MakeUnique
<wchar_t[]>(bufferSize
);
485 _snwprintf_s(absImagePath
.get(), bufferSize
, _TRUNCATE
, absPathFormat
,
486 installPath
.get(), toastStrings
->relImagePath
.get());
488 // This is used to protect gHandlerReturnData.
489 gHandlerMutex
= CreateMutexW(nullptr, TRUE
, nullptr);
490 if (gHandlerMutex
== nullptr) {
491 LOG_ERROR_MESSAGE(L
"Unable to create mutex: %#X", GetLastError());
492 return activitiesPerformed
;
494 // Automatically close this mutex when this function exits.
495 nsAutoHandle
autoMutex(gHandlerMutex
);
496 // No need to initialize gHandlerReturnData.activitiesPerformed, since it will
497 // be set by the handler. But we do need to initialize
498 // gHandlerReturnData.handlerDataHasBeenSet so the handler knows that no data
500 gHandlerReturnData
.handlerDataHasBeenSet
= false;
501 success
= ReleaseMutex(gHandlerMutex
);
503 LOG_ERROR_MESSAGE(L
"Unable to release mutex ownership: %#X",
507 // Finally ready to assemble the notification and dispatch it.
508 WinToastTemplate toastTemplate
=
509 WinToastTemplate(WinToastTemplate::ImageAndText02
);
510 toastTemplate
.setTextField(toastStrings
->text1
.get(),
511 WinToastTemplate::FirstLine
);
512 toastTemplate
.setTextField(toastStrings
->text2
.get(),
513 WinToastTemplate::SecondLine
);
514 toastTemplate
.addAction(toastStrings
->action1
.get());
515 toastTemplate
.addAction(toastStrings
->action2
.get());
516 toastTemplate
.setImagePath(absImagePath
.get());
517 toastTemplate
.setScenario(WinToastTemplate::Scenario::Reminder
);
518 ToastHandler
* handler
=
519 new ToastHandler(whichNotification
, event
.get(), aumi
);
520 INT64 id
= WinToast::instance()->showToast(toastTemplate
, handler
, &error
);
522 LOG_ERROR_MESSAGE(WinToast::strerror(error
).c_str());
523 return activitiesPerformed
;
526 DWORD result
= WaitForSingleObject(event
.get(), NOTIFICATION_WAIT_TIMEOUT_MS
);
527 // Don't return after these errors. Attempt to hide the notification.
528 if (result
== WAIT_FAILED
) {
529 LOG_ERROR_MESSAGE(L
"Unable to wait on event object: %#X", GetLastError());
530 } else if (result
== WAIT_TIMEOUT
) {
531 LOG_ERROR_MESSAGE(L
"Timed out waiting for event object");
533 result
= WaitForSingleObject(gHandlerMutex
, MUTEX_TIMEOUT_MS
);
534 if (result
== WAIT_TIMEOUT
) {
535 LOG_ERROR_MESSAGE(L
"Unable to obtain mutex ownership");
536 // activitiesPerformed is already set to error. No change needed.
537 } else if (result
== WAIT_FAILED
) {
538 LOG_ERROR_MESSAGE(L
"Failed to wait on mutex: %#X", GetLastError());
539 // activitiesPerformed is already set to error. No change needed.
540 } else if (result
== WAIT_ABANDONED
) {
541 LOG_ERROR_MESSAGE(L
"Found abandoned mutex");
542 ReleaseMutex(gHandlerMutex
);
543 // activitiesPerformed is already set to error. No change needed.
545 // Mutex is being held. It is safe to access gHandlerReturnData.
546 // If gHandlerReturnData.handlerDataHasBeenSet is false, the handler never
547 // ran. Use the error value activitiesPerformed already contains.
548 if (gHandlerReturnData
.handlerDataHasBeenSet
) {
549 activitiesPerformed
= gHandlerReturnData
.activitiesPerformed
;
552 success
= ReleaseMutex(gHandlerMutex
);
554 LOG_ERROR_MESSAGE(L
"Unable to release mutex ownership: %#X",
560 if (!WinToast::instance()->hideToast(id
)) {
561 LOG_ERROR_MESSAGE(L
"Failed to hide notification");
563 return activitiesPerformed
;
566 // Previously this function checked that the Firefox build was using English.
567 // This was checked because of the peculiar way we were localizing toast
568 // notifications where we used a completely different set of strings in English.
570 // We've since unified the notification flows but need to clean up unused code
571 // and config files - Bug 1826375.
572 bool FirefoxInstallIsEnglish() { return false; }
574 // If a notification is shown, this function will block until the notification
575 // is activated or dismissed.
576 // aumi is the App User Model ID.
577 NotificationActivities
MaybeShowNotification(
578 const DefaultBrowserInfo
& browserInfo
, const wchar_t* aumi
, bool force
) {
579 // Default to not showing a notification. Any other value will be returned
580 // directly from ShowNotification.
581 NotificationActivities activitiesPerformed
= {NotificationType::Initial
,
582 NotificationShown::NotShown
,
583 NotificationAction::NoAction
};
585 // Reset notification state machine, user setting default browser to Firefox
586 // is a strong signal that they intend to have it as the default browser.
587 if (browserInfo
.currentDefaultBrowser
== Browser::Firefox
) {
588 ResetInitialNotificationShown();
591 bool initialNotificationShown
= GetInitialNotificationShown();
592 if (!initialNotificationShown
|| force
) {
593 if ((browserInfo
.currentDefaultBrowser
== Browser::EdgeWithBlink
&&
594 browserInfo
.previousDefaultBrowser
== Browser::Firefox
) ||
596 return ShowNotification(NotificationType::Initial
, aumi
);
598 return activitiesPerformed
;
600 activitiesPerformed
.type
= NotificationType::Followup
;
602 ULONGLONG followupNotificationRequestTime
=
603 GetFollowupNotificationRequestTime();
604 bool followupNotificationRequested
= followupNotificationRequestTime
!= 0;
605 bool followupNotificationShown
= GetFollowupNotificationShown();
606 if (followupNotificationRequested
&& !followupNotificationShown
&&
607 !GetFollowupNotificationSuppressed()) {
608 ULONGLONG secondsSinceRequestTime
=
609 SecondsPassedSince(followupNotificationRequestTime
);
611 if (secondsSinceRequestTime
>= SEVEN_DAYS_IN_SECONDS
) {
612 // If we go to show the followup notification and the user has already
613 // changed the default browser, permanently suppress the followup since
614 // it's no longer relevant.
615 if (browserInfo
.currentDefaultBrowser
== Browser::EdgeWithBlink
) {
616 return ShowNotification(NotificationType::Followup
, aumi
);
618 SetFollowupNotificationSuppressed(true);
622 return activitiesPerformed
;
625 std::string
GetStringForNotificationType(NotificationType type
) {
627 case NotificationType::Initial
:
628 return std::string("initial");
629 case NotificationType::Followup
:
630 return std::string("followup");
634 std::string
GetStringForNotificationShown(NotificationShown shown
) {
636 case NotificationShown::NotShown
:
637 return std::string("not-shown");
638 case NotificationShown::Shown
:
639 return std::string("shown");
640 case NotificationShown::Error
:
641 return std::string("error");
645 std::string
GetStringForNotificationAction(NotificationAction action
) {
647 case NotificationAction::DismissedByTimeout
:
648 return std::string("dismissed-by-timeout");
649 case NotificationAction::DismissedToActionCenter
:
650 return std::string("dismissed-to-action-center");
651 case NotificationAction::DismissedByButton
:
652 return std::string("dismissed-by-button");
653 case NotificationAction::DismissedByApplicationHidden
:
654 return std::string("dismissed-by-application-hidden");
655 case NotificationAction::RemindMeLater
:
656 return std::string("remind-me-later");
657 case NotificationAction::MakeFirefoxDefaultButton
:
658 return std::string("make-firefox-default-button");
659 case NotificationAction::ToastClicked
:
660 return std::string("toast-clicked");
661 case NotificationAction::NoAction
:
662 return std::string("no-action");
666 void EnsureValidNotificationAction(std::string
& actionString
) {
667 if (actionString
!= "dismissed-by-timeout" &&
668 actionString
!= "dismissed-to-action-center" &&
669 actionString
!= "dismissed-by-button" &&
670 actionString
!= "dismissed-by-application-hidden" &&
671 actionString
!= "remind-me-later" &&
672 actionString
!= "make-firefox-default-button" &&
673 actionString
!= "toast-clicked" && actionString
!= "no-action") {
674 actionString
= "no-action";
678 } // namespace mozilla::default_agent