Bug 1852412 - Convert set user choice error types to nsresult. r=nrishel
[gecko.git] / toolkit / mozapps / defaultagent / Notification.cpp
blob8764bb148e8c3c6971214e7a38405716992631ca
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"
9 #include <shlwapi.h>
10 #include <wchar.h>
11 #include <windows.h>
12 #include <winnt.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"
24 #include "common.h"
25 #include "DefaultBrowser.h"
26 #include "EventLog.h"
27 #include "Registry.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)
48 .isErr();
51 static bool GetInitialNotificationShown() {
52 return RegistryGetValueBool(IsPrefixed::Unprefixed,
53 L"InitialNotificationShown")
54 .unwrapOr(mozilla::Some(false))
55 .valueOr(false);
58 static bool ResetInitialNotificationShown() {
59 return RegistryDeleteValue(IsPrefixed::Unprefixed,
60 L"InitialNotificationShown")
61 .isOk();
64 static bool SetFollowupNotificationShown(bool wasShown) {
65 return !RegistrySetValueBool(IsPrefixed::Unprefixed,
66 L"FollowupNotificationShown", wasShown)
67 .isErr();
70 static bool GetFollowupNotificationShown() {
71 return RegistryGetValueBool(IsPrefixed::Unprefixed,
72 L"FollowupNotificationShown")
73 .unwrapOr(mozilla::Some(false))
74 .valueOr(false);
77 static bool SetFollowupNotificationSuppressed(bool value) {
78 return !RegistrySetValueBool(IsPrefixed::Unprefixed,
79 L"FollowupNotificationSuppressed", value)
80 .isErr();
83 static bool GetFollowupNotificationSuppressed() {
84 return RegistryGetValueBool(IsPrefixed::Unprefixed,
85 L"FollowupNotificationSuppressed")
86 .unwrapOr(mozilla::Some(false))
87 .valueOr(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))
94 .valueOr(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))
102 .valueOr(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;
113 struct Strings {
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
125 // needed.
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);
148 if (!success) {
149 LOG_ERROR_MESSAGE(L"Failed to get install directory when getting strings");
150 return false;
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,
158 installPath.get());
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();
178 if (result != OK) {
179 LOG_ERROR_MESSAGE(L"Unable to read English strings: %d", result);
180 return false;
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();
201 if (result != OK) {
202 LOG_ERROR_MESSAGE(L"Unable to read localized strings: %d", result);
203 return false;
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();
216 if (result != OK) {
217 LOG_ERROR_MESSAGE(L"Unable to read non-localized strings: %d", result);
218 return false;
221 return true;
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,
241 nullptr, false,
242 DETACHED_PROCESS | NORMAL_PRIORITY_CLASS, nullptr,
243 nullptr, &si, &pi)) {
244 HRESULT hr = HRESULT_FROM_WIN32(GetLastError());
245 LOG_ERROR(hr);
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();
272 } else {
273 LOG_ERROR_MESSAGE(L"Failed to SetDefaultBrowserUserChoice: %#X",
274 GetLastError());
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
283 // structure.
284 struct HandlerData {
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 {
303 private:
304 NotificationType mWhichNotification;
305 HANDLE mEvent;
306 const std::wstring mAumiStr;
308 public:
309 ToastHandler(NotificationType whichNotification, HANDLE event,
310 const wchar_t* aumi)
311 : mWhichNotification(whichNotification), mEvent(event), mAumiStr(aumi) {}
313 void FinishHandler(NotificationActivities& returnData) const {
314 SetReturnData(returnData);
316 BOOL success = SetEvent(mEvent);
317 if (!success) {
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");
326 return;
327 } else if (result == WAIT_FAILED) {
328 LOG_ERROR_MESSAGE(L"Failed to wait on mutex: %#X", GetLastError());
329 return;
330 } else if (result == WAIT_ABANDONED) {
331 LOG_ERROR_MESSAGE(L"Found abandoned mutex");
332 ReleaseMutex(gHandlerMutex);
333 return;
336 // Only set this data once
337 if (!gHandlerReturnData.handlerDataHasBeenSet) {
338 gHandlerReturnData.activitiesPerformed = toSet;
339 gHandlerReturnData.handlerDataHasBeenSet = true;
342 BOOL success = ReleaseMutex(gHandlerMutex);
343 if (!success) {
344 LOG_ERROR_MESSAGE(L"Unable to release mutex ownership: %#X",
345 GetLastError());
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
379 // notification.
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();
444 Strings strings;
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
452 // notification.
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);
462 } else {
463 success = SetFollowupNotificationShown(true);
465 if (!success) {
466 // Return early in this case to prevent the notification from being shown
467 // on every run.
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);
475 if (!success) {
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
499 // has been set yet.
500 gHandlerReturnData.handlerDataHasBeenSet = false;
501 success = ReleaseMutex(gHandlerMutex);
502 if (!success) {
503 LOG_ERROR_MESSAGE(L"Unable to release mutex ownership: %#X",
504 GetLastError());
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);
521 if (id < 0) {
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");
532 } else {
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.
544 } else {
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);
553 if (!success) {
554 LOG_ERROR_MESSAGE(L"Unable to release mutex ownership: %#X",
555 GetLastError());
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) ||
595 force) {
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);
617 } else {
618 SetFollowupNotificationSuppressed(true);
622 return activitiesPerformed;
625 std::string GetStringForNotificationType(NotificationType type) {
626 switch (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) {
635 switch (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) {
646 switch (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