Bug 1867190 - Add prefs for PHC probablities r=glandium
[gecko.git] / toolkit / mozapps / defaultagent / Notification.cpp
blob961e57c9b3e53accaee493f7595f473e85e101b6
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/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"
24 #include "nsString.h"
25 #include "nsTArray.h"
26 #include "nsWindowsHelpers.h"
27 #include "readstrings.h"
28 #include "updatererrors.h"
29 #include "WindowsDefaultBrowser.h"
31 #include "common.h"
32 #include "DefaultBrowser.h"
33 #include "EventLog.h"
34 #include "Registry.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)
57 .isErr();
60 static bool GetInitialNotificationShown() {
61 return RegistryGetValueBool(IsPrefixed::Unprefixed,
62 L"InitialNotificationShown")
63 .unwrapOr(mozilla::Some(false))
64 .valueOr(false);
67 static bool ResetInitialNotificationShown() {
68 return RegistryDeleteValue(IsPrefixed::Unprefixed,
69 L"InitialNotificationShown")
70 .isOk();
73 static bool SetFollowupNotificationShown(bool wasShown) {
74 return !RegistrySetValueBool(IsPrefixed::Unprefixed,
75 L"FollowupNotificationShown", wasShown)
76 .isErr();
79 static bool GetFollowupNotificationShown() {
80 return RegistryGetValueBool(IsPrefixed::Unprefixed,
81 L"FollowupNotificationShown")
82 .unwrapOr(mozilla::Some(false))
83 .valueOr(false);
86 static bool SetFollowupNotificationSuppressed(bool value) {
87 return !RegistrySetValueBool(IsPrefixed::Unprefixed,
88 L"FollowupNotificationSuppressed", value)
89 .isErr();
92 static bool GetFollowupNotificationSuppressed() {
93 return RegistryGetValueBool(IsPrefixed::Unprefixed,
94 L"FollowupNotificationSuppressed")
95 .unwrapOr(mozilla::Some(false))
96 .valueOr(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))
103 .valueOr(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))
111 .valueOr(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;
122 struct Strings {
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
134 // needed.
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);
157 if (!success) {
158 LOG_ERROR_MESSAGE(L"Failed to get install directory when getting strings");
159 return false;
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, {},
167 daHeaderText, daRv);
168 ENSURE_SUCCESS(daRv, false);
169 l10n->FormatValueSync("default-browser-notification-body-text"_ns, {},
170 daBodyText, daRv);
171 ENSURE_SUCCESS(daRv, false);
172 l10n->FormatValueSync("default-browser-notification-yes-button-text"_ns, {},
173 daYesButton, daRv);
174 ENSURE_SUCCESS(daRv, false);
175 l10n->FormatValueSync("default-browser-notification-no-button-text"_ns, {},
176 daNoButton, daRv);
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,
203 installPath.get());
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();
213 if (result != OK) {
214 LOG_ERROR_MESSAGE(L"Unable to read non-localized strings: %d", result);
215 return false;
218 return true;
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.
240 auto result =
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();
264 } else {
265 LOG_ERROR_MESSAGE(L"Failed to SetDefaultBrowserUserChoice: %#X",
266 GetLastError());
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
275 // structure.
276 struct HandlerData {
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 {
295 private:
296 NotificationType mWhichNotification;
297 HANDLE mEvent;
298 const std::wstring mAumiStr;
300 public:
301 ToastHandler(NotificationType whichNotification, HANDLE event,
302 const wchar_t* aumi)
303 : mWhichNotification(whichNotification), mEvent(event), mAumiStr(aumi) {}
305 void FinishHandler(NotificationActivities& returnData) const {
306 SetReturnData(returnData);
308 BOOL success = SetEvent(mEvent);
309 if (!success) {
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");
318 return;
319 } else if (result == WAIT_FAILED) {
320 LOG_ERROR_MESSAGE(L"Failed to wait on mutex: %#X", GetLastError());
321 return;
322 } else if (result == WAIT_ABANDONED) {
323 LOG_ERROR_MESSAGE(L"Found abandoned mutex");
324 ReleaseMutex(gHandlerMutex);
325 return;
328 // Only set this data once
329 if (!gHandlerReturnData.handlerDataHasBeenSet) {
330 gHandlerReturnData.activitiesPerformed = toSet;
331 gHandlerReturnData.handlerDataHasBeenSet = true;
334 BOOL success = ReleaseMutex(gHandlerMutex);
335 if (!success) {
336 LOG_ERROR_MESSAGE(L"Unable to release mutex ownership: %#X",
337 GetLastError());
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
371 // notification.
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();
419 Strings strings;
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");
431 return;
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());
441 return;
444 // This event object will let the handler notify us when it has handled the
445 // notification.
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());
449 return;
452 bool success = false;
453 if (whichNotification == NotificationType::Initial) {
454 success = SetInitialNotificationShown(true);
455 } else {
456 success = SetFollowupNotificationShown(true);
458 if (!success) {
459 // Return early in this case to prevent the notification from being shown
460 // on every run.
461 LOG_ERROR_MESSAGE(L"Unable to set notification as displayed");
462 return;
465 // We need the absolute image path, not the relative path.
466 mozilla::UniquePtr<wchar_t[]> installPath;
467 success = GetInstallDirectory(installPath);
468 if (!success) {
469 LOG_ERROR_MESSAGE(L"Failed to get install directory for the image path");
470 return;
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());
485 return;
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);
495 if (!success) {
496 LOG_ERROR_MESSAGE(L"Unable to release mutex ownership: %#X",
497 GetLastError());
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);
514 if (id < 0) {
515 LOG_ERROR_MESSAGE(WinToast::strerror(error).c_str());
516 return;
519 DWORD result =
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");
526 } else {
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.
538 } else {
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);
547 if (!success) {
548 LOG_ERROR_MESSAGE(L"Unable to release mutex ownership: %#X",
549 GetLastError());
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) ||
590 force) {
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);
612 } else {
613 SetFollowupNotificationSuppressed(true);
617 return activitiesPerformed;
620 std::string GetStringForNotificationType(NotificationType type) {
621 switch (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) {
630 switch (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;
647 } else {
648 // Catch all.
649 return NotificationShown::Error;
653 std::string GetStringForNotificationAction(NotificationAction action) {
654 switch (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;
691 } else {
692 // Catch all.
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