summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/defaultagent
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/mozapps/defaultagent')
-rw-r--r--toolkit/mozapps/defaultagent/BackgroundTask_defaultagent.sys.mjs45
-rw-r--r--toolkit/mozapps/defaultagent/DefaultAgent.cpp59
-rw-r--r--toolkit/mozapps/defaultagent/DefaultBrowser.cpp11
-rw-r--r--toolkit/mozapps/defaultagent/DefaultBrowser.h4
-rw-r--r--toolkit/mozapps/defaultagent/Notification.cpp602
-rw-r--r--toolkit/mozapps/defaultagent/Notification.h7
-rw-r--r--toolkit/mozapps/defaultagent/ScheduledTask.cpp3
-rw-r--r--toolkit/mozapps/defaultagent/SetDefaultBrowser.cpp393
-rw-r--r--toolkit/mozapps/defaultagent/defaultagent.ini9
-rw-r--r--toolkit/mozapps/defaultagent/moz.build20
-rw-r--r--toolkit/mozapps/defaultagent/nsIDefaultAgent.idl14
-rw-r--r--toolkit/mozapps/defaultagent/proxy/main.cpp2
-rw-r--r--toolkit/mozapps/defaultagent/proxy/moz.build1
13 files changed, 414 insertions, 756 deletions
diff --git a/toolkit/mozapps/defaultagent/BackgroundTask_defaultagent.sys.mjs b/toolkit/mozapps/defaultagent/BackgroundTask_defaultagent.sys.mjs
index c727a55997..691f76c319 100644
--- a/toolkit/mozapps/defaultagent/BackgroundTask_defaultagent.sys.mjs
+++ b/toolkit/mozapps/defaultagent/BackgroundTask_defaultagent.sys.mjs
@@ -17,8 +17,6 @@ const EXIT_CODE = {
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
setTimeout: "resource://gre/modules/Timer.sys.mjs",
- BackgroundTasksUtils: "resource://gre/modules/BackgroundTasksUtils.sys.mjs",
- NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
// eslint-disable-next-line mozilla/no-browser-refs-in-toolkit
ShellService: "resource:///modules/ShellService.sys.mjs",
});
@@ -37,11 +35,20 @@ ChromeUtils.defineLazyGetter(lazy, "log", () => {
return new ConsoleAPI(consoleOptions);
});
-// Should be slightly longer than NOTIFICATION_WAIT_TIMEOUT_MS in
-// Notification.cpp (divided by 1000 to convert millseconds to seconds) to not
-// cause race between timeouts. Currently 12 hours + 5 additional minutes.
-export const backgroundTaskTimeoutSec = 12 * 60 * 60 + 60 * 5;
-const kNotificationTimeoutMs = 12 * 60 * 60 * 1000;
+// Should be slightly longer than kNotificationTimeoutMs and kGleanSendWait below
+// (divided by 1000 to convert millseconds to seconds) to not cause race
+// between timeouts.
+//
+// Additionally, should be less than the Windows Scheduled Task timeout
+// execTimeLimitBStr in ScheduledTask.cpp.
+//
+// Current bounds are 11 hours 55 minutes 10 seconds and 12 hours 5 minutes.
+export const backgroundTaskTimeoutSec = 12 * 60 * 60;
+
+// 11 hours 55 minutes in milliseconds.
+const kNotificationTimeoutMs = 11 * 60 * 60 * 1000 + 55 * 60 * 1000;
+// 10 seconds in milliseconds.
+const kGleanSendWait = 10000;
const kNotificationShown = Object.freeze({
notShown: "not-shown",
@@ -148,25 +155,11 @@ export async function runBackgroundTask(commandLine) {
lazy.log.info(`Running do-task with AUMID "${aumid}"`);
- let cppFallback = false;
try {
- await lazy.BackgroundTasksUtils.enableNimbus(commandLine);
- cppFallback =
- lazy.NimbusFeatures.defaultAgent.getVariable("cppFallback");
- } catch (e) {
- lazy.log.error(`Error enabling nimbus: ${e}`);
- }
-
- try {
- if (!cppFallback) {
- lazy.log.info("Running JS do-task.");
- await runWithRegistryLocked(async () => {
- await doTask(defaultAgent, force);
- });
- } else {
- lazy.log.info("Running C++ do-task.");
- defaultAgent.doTask(aumid, force);
- }
+ lazy.log.info("Running JS do-task.");
+ await runWithRegistryLocked(async () => {
+ await doTask(defaultAgent, force);
+ });
} catch (e) {
if (e.message) {
lazy.log.error(e.message);
@@ -181,7 +174,7 @@ export async function runBackgroundTask(commandLine) {
// Bug 1857333: We wait for arbitrary time for Glean to submit telemetry.
lazy.log.info("Pinged glean, waiting for submission.");
- await new Promise(resolve => lazy.setTimeout(resolve, 5000));
+ await new Promise(resolve => lazy.setTimeout(resolve, kGleanSendWait));
return EXIT_CODE.SUCCESS;
}
diff --git a/toolkit/mozapps/defaultagent/DefaultAgent.cpp b/toolkit/mozapps/defaultagent/DefaultAgent.cpp
index 2ebb5e466e..3bff6e3243 100644
--- a/toolkit/mozapps/defaultagent/DefaultAgent.cpp
+++ b/toolkit/mozapps/defaultagent/DefaultAgent.cpp
@@ -7,11 +7,8 @@
#include <windows.h>
#include <shlwapi.h>
#include <objbase.h>
-#include <string.h>
-#include <vector>
#include "nsAutoRef.h"
-#include "nsDebug.h"
#include "nsProxyRelease.h"
#include "nsWindowsHelpers.h"
#include "nsString.h"
@@ -304,62 +301,6 @@ DefaultAgent::Uninstall(const nsAString& aUniqueToken) {
}
NS_IMETHODIMP
-DefaultAgent::DoTask(const nsAString& aUniqueToken, const bool aForce) {
- // Acquire() has a short timeout. Since this runs in the background, we
- // could use a longer timeout in this situation. However, if another
- // installation's agent is already running, it will update CurrentDefault,
- // possibly send a ping, and possibly show a notification.
- // Once all that has happened, there is no real reason to do it again. We
- // only send one ping per day, so we aren't going to do that again. And
- // the only time we ever show a second notification is 7 days after the
- // first one, so we aren't going to do that again either.
- // If the other process didn't take those actions, there is no reason that
- // this process would take them.
- // If the other process fails, this one will most likely fail for the same
- // reason.
- // So we'll just bail if we can't get the mutex quickly.
- RegistryMutex regMutex;
- if (!regMutex.Acquire()) {
- return NS_ERROR_NOT_AVAILABLE;
- }
-
- // Check that Firefox ran recently, if not then stop here.
- // Also stop if no timestamp was found, which most likely indicates
- // that Firefox was not yet run.
- bool ranRecently = false;
- if (!aForce && (!CheckIfAppRanRecently(&ranRecently) || !ranRecently)) {
- return NS_ERROR_FAILURE;
- }
-
- DefaultBrowserResult defaultBrowserResult = GetDefaultBrowserInfo();
- DefaultBrowserInfo browserInfo{};
- if (defaultBrowserResult.isOk()) {
- browserInfo = defaultBrowserResult.unwrap();
- } else {
- browserInfo.currentDefaultBrowser = Browser::Error;
- browserInfo.previousDefaultBrowser = Browser::Error;
- }
-
- DefaultPdfResult defaultPdfResult = GetDefaultPdfInfo();
- DefaultPdfInfo pdfInfo{};
- if (defaultPdfResult.isOk()) {
- pdfInfo = defaultPdfResult.unwrap();
- } else {
- pdfInfo.currentDefaultPdf = PDFHandler::Error;
- }
-
- NotificationActivities activitiesPerformed;
- // We block while waiting for the notification which prevents STA thread
- // callbacks from running as the event loop won't run. Moving notification
- // handling to an MTA thread prevents this conflict.
- activitiesPerformed = MaybeShowNotification(
- browserInfo, PromiseFlatString(aUniqueToken).get(), aForce);
-
- HRESULT hr = SendDefaultAgentPing(browserInfo, pdfInfo, activitiesPerformed);
- return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE;
-}
-
-NS_IMETHODIMP
DefaultAgent::AppRanRecently(bool* aRanRecently) {
bool ranRecently = false;
*aRanRecently = CheckIfAppRanRecently(&ranRecently) && ranRecently;
diff --git a/toolkit/mozapps/defaultagent/DefaultBrowser.cpp b/toolkit/mozapps/defaultagent/DefaultBrowser.cpp
index 87d3f62632..d9543665b7 100644
--- a/toolkit/mozapps/defaultagent/DefaultBrowser.cpp
+++ b/toolkit/mozapps/defaultagent/DefaultBrowser.cpp
@@ -184,17 +184,6 @@ BrowserResult TryGetReplacePreviousDefaultBrowser(Browser currentDefault) {
return GetBrowserFromString(previousDefault);
}
-DefaultBrowserResult GetDefaultBrowserInfo() {
- DefaultBrowserInfo browserInfo;
-
- MOZ_TRY_VAR(browserInfo.currentDefaultBrowser, TryGetDefaultBrowser());
- MOZ_TRY_VAR(
- browserInfo.previousDefaultBrowser,
- TryGetReplacePreviousDefaultBrowser(browserInfo.currentDefaultBrowser));
-
- return browserInfo;
-}
-
// We used to prefix this key with the installation directory, but that causes
// problems with our new "only one ping per day across installs" restriction.
// To make sure all installations use consistent data, the value's name is
diff --git a/toolkit/mozapps/defaultagent/DefaultBrowser.h b/toolkit/mozapps/defaultagent/DefaultBrowser.h
index f1b940959f..081f3b9ccf 100644
--- a/toolkit/mozapps/defaultagent/DefaultBrowser.h
+++ b/toolkit/mozapps/defaultagent/DefaultBrowser.h
@@ -10,7 +10,6 @@
#include <string>
#include "mozilla/DefineEnum.h"
-#include "mozilla/WinHeaderOnlyUtils.h"
namespace mozilla::default_agent {
@@ -24,9 +23,6 @@ struct DefaultBrowserInfo {
Browser previousDefaultBrowser;
};
-using DefaultBrowserResult = mozilla::WindowsErrorResult<DefaultBrowserInfo>;
-
-DefaultBrowserResult GetDefaultBrowserInfo();
Browser GetDefaultBrowser();
Browser GetReplacePreviousDefaultBrowser(Browser currentBrowser);
diff --git a/toolkit/mozapps/defaultagent/Notification.cpp b/toolkit/mozapps/defaultagent/Notification.cpp
index 961e57c9b3..39236eac15 100644
--- a/toolkit/mozapps/defaultagent/Notification.cpp
+++ b/toolkit/mozapps/defaultagent/Notification.cpp
@@ -7,616 +7,22 @@
#include "Notification.h"
#include <shlwapi.h>
-#include <wchar.h>
#include <windows.h>
#include <winnt.h>
-#include "mozilla/ArrayUtils.h"
-#include "mozilla/CmdLineAndEnvUtils.h"
-#include "mozilla/ErrorResult.h"
-#include "mozilla/mscom/EnsureMTA.h"
-#include "mozilla/intl/FileSource.h"
-#include "mozilla/intl/Localization.h"
-#include "mozilla/ShellHeaderOnlyUtils.h"
-#include "mozilla/UniquePtr.h"
-#include "mozilla/Unused.h"
-#include "mozilla/WinHeaderOnlyUtils.h"
-#include "nsString.h"
-#include "nsTArray.h"
-#include "nsWindowsHelpers.h"
-#include "readstrings.h"
-#include "updatererrors.h"
-#include "WindowsDefaultBrowser.h"
-
-#include "common.h"
-#include "DefaultBrowser.h"
-#include "EventLog.h"
-#include "Registry.h"
-#include "SetDefaultBrowser.h"
-
-#include "wintoastlib.h"
-
-using mozilla::intl::Localization;
+#include "nsLiteralString.h"
#define SEVEN_DAYS_IN_SECONDS (7 * 24 * 60 * 60)
-// If the notification hasn't been activated or dismissed within 12 hours,
-// stop waiting for it.
-#define NOTIFICATION_WAIT_TIMEOUT_MS (12 * 60 * 60 * 1000)
+// If the notification hasn't been activated or dismissed within 11 hours 55
+// minutes, stop waiting for it.
+#define NOTIFICATION_WAIT_TIMEOUT_MS (11 * 60 * 60 * 1000 + 55 * 60 * 1000)
// If the mutex hasn't been released within a few minutes, something is wrong
// and we should give up on it
#define MUTEX_TIMEOUT_MS (10 * 60 * 1000)
namespace mozilla::default_agent {
-bool FirefoxInstallIsEnglish();
-
-static bool SetInitialNotificationShown(bool wasShown) {
- return !RegistrySetValueBool(IsPrefixed::Unprefixed,
- L"InitialNotificationShown", wasShown)
- .isErr();
-}
-
-static bool GetInitialNotificationShown() {
- return RegistryGetValueBool(IsPrefixed::Unprefixed,
- L"InitialNotificationShown")
- .unwrapOr(mozilla::Some(false))
- .valueOr(false);
-}
-
-static bool ResetInitialNotificationShown() {
- return RegistryDeleteValue(IsPrefixed::Unprefixed,
- L"InitialNotificationShown")
- .isOk();
-}
-
-static bool SetFollowupNotificationShown(bool wasShown) {
- return !RegistrySetValueBool(IsPrefixed::Unprefixed,
- L"FollowupNotificationShown", wasShown)
- .isErr();
-}
-
-static bool GetFollowupNotificationShown() {
- return RegistryGetValueBool(IsPrefixed::Unprefixed,
- L"FollowupNotificationShown")
- .unwrapOr(mozilla::Some(false))
- .valueOr(false);
-}
-
-static bool SetFollowupNotificationSuppressed(bool value) {
- return !RegistrySetValueBool(IsPrefixed::Unprefixed,
- L"FollowupNotificationSuppressed", value)
- .isErr();
-}
-
-static bool GetFollowupNotificationSuppressed() {
- return RegistryGetValueBool(IsPrefixed::Unprefixed,
- L"FollowupNotificationSuppressed")
- .unwrapOr(mozilla::Some(false))
- .valueOr(false);
-}
-
-// Returns 0 if no value is set.
-static ULONGLONG GetFollowupNotificationRequestTime() {
- return RegistryGetValueQword(IsPrefixed::Unprefixed, L"FollowupRequestTime")
- .unwrapOr(mozilla::Some(0))
- .valueOr(0);
-}
-
-// Returns false if no value is set.
-static bool GetPrefSetDefaultBrowserUserChoice() {
- return RegistryGetValueBool(IsPrefixed::Prefixed,
- L"SetDefaultBrowserUserChoice")
- .unwrapOr(mozilla::Some(false))
- .valueOr(false);
-}
-
-struct ToastStrings {
- mozilla::UniquePtr<wchar_t[]> text1;
- mozilla::UniquePtr<wchar_t[]> text2;
- mozilla::UniquePtr<wchar_t[]> action1;
- mozilla::UniquePtr<wchar_t[]> action2;
- mozilla::UniquePtr<wchar_t[]> relImagePath;
-};
-
-struct Strings {
- // Toast notification button text is hard to localize because it tends to
- // overflow. Thus, we have 3 different toast notifications:
- // - The initial notification, which includes a button with text like
- // "Ask me later". Since we cannot easily localize this, we will display
- // it only in English.
- // - The followup notification, to be shown if the user clicked "Ask me
- // later". Since we only have that button in English, we only need this
- // notification in English.
- // - The localized notification, which has much shorter button text to
- // (hopefully) prevent overflow: just "Yes" and "No". Since we no longer
- // have an "Ask me later" button, a followup localized notification is not
- // needed.
- ToastStrings initialToast;
- ToastStrings followupToast;
- ToastStrings localizedToast;
-
- // Returned pointer points within this struct and should not be freed.
- const ToastStrings* GetToastStrings(NotificationType whichToast,
- bool englishStrings) const {
- if (!englishStrings) {
- return &localizedToast;
- }
- if (whichToast == NotificationType::Initial) {
- return &initialToast;
- }
- return &followupToast;
- }
-};
-
-// Gets all strings out of the relevant INI files.
-// Returns true on success, false on failure
-static bool GetStrings(Strings& strings) {
- mozilla::UniquePtr<wchar_t[]> installPath;
- bool success = GetInstallDirectory(installPath);
- if (!success) {
- LOG_ERROR_MESSAGE(L"Failed to get install directory when getting strings");
- return false;
- }
- nsTArray<nsCString> resIds = {"branding/brand.ftl"_ns,
- "browser/backgroundtasks/defaultagent.ftl"_ns};
- RefPtr<Localization> l10n = Localization::Create(resIds, true);
- nsAutoCString daHeaderText, daBodyText, daYesButton, daNoButton;
- mozilla::ErrorResult daRv;
- l10n->FormatValueSync("default-browser-notification-header-text"_ns, {},
- daHeaderText, daRv);
- ENSURE_SUCCESS(daRv, false);
- l10n->FormatValueSync("default-browser-notification-body-text"_ns, {},
- daBodyText, daRv);
- ENSURE_SUCCESS(daRv, false);
- l10n->FormatValueSync("default-browser-notification-yes-button-text"_ns, {},
- daYesButton, daRv);
- ENSURE_SUCCESS(daRv, false);
- l10n->FormatValueSync("default-browser-notification-no-button-text"_ns, {},
- daNoButton, daRv);
- ENSURE_SUCCESS(daRv, false);
-
- NS_ConvertUTF8toUTF16 daHeaderTextW(daHeaderText), daBodyTextW(daBodyText),
- daYesButtonW(daYesButton), daNoButtonW(daNoButton);
- strings.localizedToast.text1 =
- mozilla::MakeUnique<wchar_t[]>(daHeaderTextW.Length() + 1);
- wcsncpy(strings.localizedToast.text1.get(), daHeaderTextW.get(),
- daHeaderTextW.Length() + 1);
- strings.localizedToast.text2 =
- mozilla::MakeUnique<wchar_t[]>(daBodyTextW.Length() + 1);
- wcsncpy(strings.localizedToast.text2.get(), daBodyTextW.get(),
- daBodyTextW.Length() + 1);
- strings.localizedToast.action1 =
- mozilla::MakeUnique<wchar_t[]>(daYesButtonW.Length() + 1);
- wcsncpy(strings.localizedToast.action1.get(), daYesButtonW.get(),
- daYesButtonW.Length() + 1);
- strings.localizedToast.action2 =
- mozilla::MakeUnique<wchar_t[]>(daNoButtonW.Length() + 1);
- wcsncpy(strings.localizedToast.action2.get(), daNoButtonW.get(),
- daNoButtonW.Length() + 1);
- const wchar_t* iniFormat = L"%s\\defaultagent.ini";
- int bufferSize = _scwprintf(iniFormat, installPath.get());
- ++bufferSize; // Extra character for terminating null
- mozilla::UniquePtr<wchar_t[]> iniPath =
- mozilla::MakeUnique<wchar_t[]>(bufferSize);
- _snwprintf_s(iniPath.get(), bufferSize, _TRUNCATE, iniFormat,
- installPath.get());
-
- IniReader nonlocalizedReader(iniPath.get(), "Nonlocalized");
- nonlocalizedReader.AddKey("InitialToastRelativeImagePath",
- &strings.initialToast.relImagePath);
- nonlocalizedReader.AddKey("FollowupToastRelativeImagePath",
- &strings.followupToast.relImagePath);
- nonlocalizedReader.AddKey("LocalizedToastRelativeImagePath",
- &strings.localizedToast.relImagePath);
- int result = nonlocalizedReader.Read();
- if (result != OK) {
- LOG_ERROR_MESSAGE(L"Unable to read non-localized strings: %d", result);
- return false;
- }
-
- return true;
-}
-
-static mozilla::WindowsError LaunchFirefoxToHandleDefaultBrowserAgent() {
- // Could also be `MOZ_APP_NAME.exe`, but there's no generality to be gained:
- // the WDBA is Firefox-only.
- FilePathResult firefoxPathResult = GetRelativeBinaryPath(L"firefox.exe");
- if (firefoxPathResult.isErr()) {
- return firefoxPathResult.unwrapErr();
- }
- std::wstring firefoxPath = firefoxPathResult.unwrap();
-
- _bstr_t cmd = firefoxPath.c_str();
- // Omit argv[0] because ShellExecute doesn't need it.
- _variant_t args(L"-to-handle-default-browser-agent");
- _variant_t operation(L"open");
- _variant_t directory;
- _variant_t showCmd(SW_SHOWNORMAL);
-
- // To prevent inheriting environment variables from the background task, we
- // run Firefox via Explorer instead of our own process. This mimics the
- // implementation of the Windows Launcher Process.
- auto result =
- ShellExecuteByExplorer(cmd, args, operation, directory, showCmd);
- NS_ENSURE_TRUE(result.isOk(), result.unwrapErr());
-
- return mozilla::WindowsError::CreateSuccess();
-}
-
-/*
- * Set the default browser.
- *
- * First check if we can directly write UserChoice, if so attempt that.
- * If we can't write UserChoice, or if the attempt fails, fall back to
- * showing the Default Apps page of Settings.
- *
- * @param aAumi The AUMI of the installation to set as default.
- */
-static void SetDefaultBrowserFromNotification(const wchar_t* aumi) {
- nsresult rv = NS_ERROR_FAILURE;
- if (GetPrefSetDefaultBrowserUserChoice()) {
- rv = SetDefaultBrowserUserChoice(aumi);
- }
-
- if (NS_SUCCEEDED(rv)) {
- mozilla::Unused << LaunchFirefoxToHandleDefaultBrowserAgent();
- } else {
- LOG_ERROR_MESSAGE(L"Failed to SetDefaultBrowserUserChoice: %#X",
- GetLastError());
- LaunchModernSettingsDialogDefaultApps();
- }
-}
-
-// This encapsulates the data that needs to be protected by a mutex because it
-// will be shared by the main thread and the handler thread.
-// To ensure the data is only written once, handlerDataHasBeenSet should be
-// initialized to false, then set to true when the handler writes data into the
-// structure.
-struct HandlerData {
- NotificationActivities activitiesPerformed;
- bool handlerDataHasBeenSet;
-};
-
-// The value that ToastHandler writes into should be a global. We can't control
-// when ToastHandler is called, and if this value isn't a global, ToastHandler
-// may be called and attempt to access this after it has been deconstructed.
-// Since this value is accessed by the handler thread and the main thread, it
-// is protected by a mutex (gHandlerMutex).
-// Since ShowNotification deconstructs the mutex, it might seem like once
-// ShowNotification exits, we can just rely on the inability to wait on an
-// invalid mutex to protect the deconstructed data, but it's possible that
-// we could deconstruct the mutex while the handler is holding it and is
-// already accessing the protected data.
-static HandlerData gHandlerReturnData;
-static HANDLE gHandlerMutex = INVALID_HANDLE_VALUE;
-
-class ToastHandler : public WinToastLib::IWinToastHandler {
- private:
- NotificationType mWhichNotification;
- HANDLE mEvent;
- const std::wstring mAumiStr;
-
- public:
- ToastHandler(NotificationType whichNotification, HANDLE event,
- const wchar_t* aumi)
- : mWhichNotification(whichNotification), mEvent(event), mAumiStr(aumi) {}
-
- void FinishHandler(NotificationActivities& returnData) const {
- SetReturnData(returnData);
-
- BOOL success = SetEvent(mEvent);
- if (!success) {
- LOG_ERROR_MESSAGE(L"Event could not be set: %#X", GetLastError());
- }
- }
-
- void SetReturnData(NotificationActivities& toSet) const {
- DWORD result = WaitForSingleObject(gHandlerMutex, MUTEX_TIMEOUT_MS);
- if (result == WAIT_TIMEOUT) {
- LOG_ERROR_MESSAGE(L"Unable to obtain mutex ownership");
- return;
- } else if (result == WAIT_FAILED) {
- LOG_ERROR_MESSAGE(L"Failed to wait on mutex: %#X", GetLastError());
- return;
- } else if (result == WAIT_ABANDONED) {
- LOG_ERROR_MESSAGE(L"Found abandoned mutex");
- ReleaseMutex(gHandlerMutex);
- return;
- }
-
- // Only set this data once
- if (!gHandlerReturnData.handlerDataHasBeenSet) {
- gHandlerReturnData.activitiesPerformed = toSet;
- gHandlerReturnData.handlerDataHasBeenSet = true;
- }
-
- BOOL success = ReleaseMutex(gHandlerMutex);
- if (!success) {
- LOG_ERROR_MESSAGE(L"Unable to release mutex ownership: %#X",
- GetLastError());
- }
- }
-
- void toastActivated() const override {
- NotificationActivities activitiesPerformed;
- activitiesPerformed.type = mWhichNotification;
- activitiesPerformed.shown = NotificationShown::Shown;
- activitiesPerformed.action = NotificationAction::ToastClicked;
-
- // Notification strings are written to indicate the default browser is
- // restored to Firefox when the notification body is clicked to prevent
- // ambiguity when buttons aren't pressed.
- SetDefaultBrowserFromNotification(mAumiStr.c_str());
-
- FinishHandler(activitiesPerformed);
- }
-
- void toastActivated(int actionIndex) const override {
- NotificationActivities activitiesPerformed;
- activitiesPerformed.type = mWhichNotification;
- activitiesPerformed.shown = NotificationShown::Shown;
- // Override this below
- activitiesPerformed.action = NotificationAction::NoAction;
-
- if (actionIndex == 0) {
- // "Make Firefox the default" button, on both the initial and followup
- // notifications. "Yes" button on the localized notification.
- activitiesPerformed.action = NotificationAction::MakeFirefoxDefaultButton;
-
- SetDefaultBrowserFromNotification(mAumiStr.c_str());
- } else if (actionIndex == 1) {
- // Do nothing. As long as we don't call
- // SetFollowupNotificationRequestTime, there will be no followup
- // notification.
- activitiesPerformed.action = NotificationAction::DismissedByButton;
- }
-
- FinishHandler(activitiesPerformed);
- }
-
- void toastDismissed(WinToastDismissalReason state) const override {
- NotificationActivities activitiesPerformed;
- activitiesPerformed.type = mWhichNotification;
- activitiesPerformed.shown = NotificationShown::Shown;
- // Override this below
- activitiesPerformed.action = NotificationAction::NoAction;
-
- if (state == WinToastDismissalReason::TimedOut) {
- activitiesPerformed.action = NotificationAction::DismissedByTimeout;
- } else if (state == WinToastDismissalReason::ApplicationHidden) {
- activitiesPerformed.action =
- NotificationAction::DismissedByApplicationHidden;
- } else if (state == WinToastDismissalReason::UserCanceled) {
- activitiesPerformed.action = NotificationAction::DismissedToActionCenter;
- }
-
- FinishHandler(activitiesPerformed);
- }
-
- void toastFailed() const override {
- NotificationActivities activitiesPerformed;
- activitiesPerformed.type = mWhichNotification;
- activitiesPerformed.shown = NotificationShown::Error;
- activitiesPerformed.action = NotificationAction::NoAction;
-
- LOG_ERROR_MESSAGE(L"Toast notification failed to display");
- FinishHandler(activitiesPerformed);
- }
-};
-
-// This function blocks until the shown notification is activated or dismissed.
-static NotificationActivities ShowNotification(
- NotificationType whichNotification, const wchar_t* aumi) {
- // Initially set the value that will be returned to error. If the notification
- // is shown successfully, we'll update it.
- NotificationActivities activitiesPerformed = {whichNotification,
- NotificationShown::Error,
- NotificationAction::NoAction};
-
- bool isEnglishInstall = FirefoxInstallIsEnglish();
-
- Strings strings;
- if (!GetStrings(strings)) {
- return activitiesPerformed;
- }
- const ToastStrings* toastStrings =
- strings.GetToastStrings(whichNotification, isEnglishInstall);
-
- mozilla::mscom::EnsureMTA([&] {
- using namespace WinToastLib;
-
- if (!WinToast::isCompatible()) {
- LOG_ERROR_MESSAGE(L"System is not compatible with WinToast");
- return;
- }
- WinToast::instance()->setAppName(L"" MOZ_APP_DISPLAYNAME);
- std::wstring aumiStr = aumi;
- WinToast::instance()->setAppUserModelId(aumiStr);
- WinToast::instance()->setShortcutPolicy(
- WinToastLib::WinToast::SHORTCUT_POLICY_REQUIRE_NO_CREATE);
- WinToast::WinToastError error;
- if (!WinToast::instance()->initialize(&error)) {
- LOG_ERROR_MESSAGE(WinToast::strerror(error).c_str());
- return;
- }
-
- // This event object will let the handler notify us when it has handled the
- // notification.
- nsAutoHandle event(CreateEventW(nullptr, TRUE, FALSE, nullptr));
- if (event.get() == nullptr) {
- LOG_ERROR_MESSAGE(L"Unable to create event object: %#X", GetLastError());
- return;
- }
-
- bool success = false;
- if (whichNotification == NotificationType::Initial) {
- success = SetInitialNotificationShown(true);
- } else {
- success = SetFollowupNotificationShown(true);
- }
- if (!success) {
- // Return early in this case to prevent the notification from being shown
- // on every run.
- LOG_ERROR_MESSAGE(L"Unable to set notification as displayed");
- return;
- }
-
- // We need the absolute image path, not the relative path.
- mozilla::UniquePtr<wchar_t[]> installPath;
- success = GetInstallDirectory(installPath);
- if (!success) {
- LOG_ERROR_MESSAGE(L"Failed to get install directory for the image path");
- return;
- }
- const wchar_t* absPathFormat = L"%s\\%s";
- int bufferSize = _scwprintf(absPathFormat, installPath.get(),
- toastStrings->relImagePath.get());
- ++bufferSize; // Extra character for terminating null
- mozilla::UniquePtr<wchar_t[]> absImagePath =
- mozilla::MakeUnique<wchar_t[]>(bufferSize);
- _snwprintf_s(absImagePath.get(), bufferSize, _TRUNCATE, absPathFormat,
- installPath.get(), toastStrings->relImagePath.get());
-
- // This is used to protect gHandlerReturnData.
- gHandlerMutex = CreateMutexW(nullptr, TRUE, nullptr);
- if (gHandlerMutex == nullptr) {
- LOG_ERROR_MESSAGE(L"Unable to create mutex: %#X", GetLastError());
- return;
- }
- // Automatically close this mutex when this function exits.
- nsAutoHandle autoMutex(gHandlerMutex);
- // No need to initialize gHandlerReturnData.activitiesPerformed, since it
- // will be set by the handler. But we do need to initialize
- // gHandlerReturnData.handlerDataHasBeenSet so the handler knows that no
- // data has been set yet.
- gHandlerReturnData.handlerDataHasBeenSet = false;
- success = ReleaseMutex(gHandlerMutex);
- if (!success) {
- LOG_ERROR_MESSAGE(L"Unable to release mutex ownership: %#X",
- GetLastError());
- }
-
- // Finally ready to assemble the notification and dispatch it.
- WinToastTemplate toastTemplate =
- WinToastTemplate(WinToastTemplate::ImageAndText02);
- toastTemplate.setTextField(toastStrings->text1.get(),
- WinToastTemplate::FirstLine);
- toastTemplate.setTextField(toastStrings->text2.get(),
- WinToastTemplate::SecondLine);
- toastTemplate.addAction(toastStrings->action1.get());
- toastTemplate.addAction(toastStrings->action2.get());
- toastTemplate.setImagePath(absImagePath.get());
- toastTemplate.setScenario(WinToastTemplate::Scenario::Reminder);
- ToastHandler* handler =
- new ToastHandler(whichNotification, event.get(), aumi);
- INT64 id = WinToast::instance()->showToast(toastTemplate, handler, &error);
- if (id < 0) {
- LOG_ERROR_MESSAGE(WinToast::strerror(error).c_str());
- return;
- }
-
- DWORD result =
- WaitForSingleObject(event.get(), NOTIFICATION_WAIT_TIMEOUT_MS);
- // Don't return after these errors. Attempt to hide the notification.
- if (result == WAIT_FAILED) {
- LOG_ERROR_MESSAGE(L"Unable to wait on event object: %#X", GetLastError());
- } else if (result == WAIT_TIMEOUT) {
- LOG_ERROR_MESSAGE(L"Timed out waiting for event object");
- } else {
- result = WaitForSingleObject(gHandlerMutex, MUTEX_TIMEOUT_MS);
- if (result == WAIT_TIMEOUT) {
- LOG_ERROR_MESSAGE(L"Unable to obtain mutex ownership");
- // activitiesPerformed is already set to error. No change needed.
- } else if (result == WAIT_FAILED) {
- LOG_ERROR_MESSAGE(L"Failed to wait on mutex: %#X", GetLastError());
- // activitiesPerformed is already set to error. No change needed.
- } else if (result == WAIT_ABANDONED) {
- LOG_ERROR_MESSAGE(L"Found abandoned mutex");
- ReleaseMutex(gHandlerMutex);
- // activitiesPerformed is already set to error. No change needed.
- } else {
- // Mutex is being held. It is safe to access gHandlerReturnData.
- // If gHandlerReturnData.handlerDataHasBeenSet is false, the handler
- // never ran. Use the error value activitiesPerformed already contains.
- if (gHandlerReturnData.handlerDataHasBeenSet) {
- activitiesPerformed = gHandlerReturnData.activitiesPerformed;
- }
-
- success = ReleaseMutex(gHandlerMutex);
- if (!success) {
- LOG_ERROR_MESSAGE(L"Unable to release mutex ownership: %#X",
- GetLastError());
- }
- }
- }
-
- if (!WinToast::instance()->hideToast(id)) {
- LOG_ERROR_MESSAGE(L"Failed to hide notification");
- }
- });
- return activitiesPerformed;
-}
-
-// Previously this function checked that the Firefox build was using English.
-// This was checked because of the peculiar way we were localizing toast
-// notifications where we used a completely different set of strings in English.
-//
-// We've since unified the notification flows but need to clean up unused code
-// and config files - Bug 1826375.
-bool FirefoxInstallIsEnglish() { return false; }
-
-// If a notification is shown, this function will block until the notification
-// is activated or dismissed.
-// aumi is the App User Model ID.
-NotificationActivities MaybeShowNotification(
- const DefaultBrowserInfo& browserInfo, const wchar_t* aumi, bool force) {
- // Default to not showing a notification. Any other value will be returned
- // directly from ShowNotification.
- NotificationActivities activitiesPerformed = {NotificationType::Initial,
- NotificationShown::NotShown,
- NotificationAction::NoAction};
-
- // Reset notification state machine, user setting default browser to Firefox
- // is a strong signal that they intend to have it as the default browser.
- if (browserInfo.currentDefaultBrowser == Browser::Firefox) {
- ResetInitialNotificationShown();
- }
-
- bool initialNotificationShown = GetInitialNotificationShown();
- if (!initialNotificationShown || force) {
- if ((browserInfo.currentDefaultBrowser == Browser::EdgeWithBlink &&
- browserInfo.previousDefaultBrowser == Browser::Firefox) ||
- force) {
- return ShowNotification(NotificationType::Initial, aumi);
- }
- return activitiesPerformed;
- }
- activitiesPerformed.type = NotificationType::Followup;
-
- ULONGLONG followupNotificationRequestTime =
- GetFollowupNotificationRequestTime();
- bool followupNotificationRequested = followupNotificationRequestTime != 0;
- bool followupNotificationShown = GetFollowupNotificationShown();
- if (followupNotificationRequested && !followupNotificationShown &&
- !GetFollowupNotificationSuppressed()) {
- ULONGLONG secondsSinceRequestTime =
- SecondsPassedSince(followupNotificationRequestTime);
-
- if (secondsSinceRequestTime >= SEVEN_DAYS_IN_SECONDS) {
- // If we go to show the followup notification and the user has already
- // changed the default browser, permanently suppress the followup since
- // it's no longer relevant.
- if (browserInfo.currentDefaultBrowser == Browser::EdgeWithBlink) {
- return ShowNotification(NotificationType::Followup, aumi);
- } else {
- SetFollowupNotificationSuppressed(true);
- }
- }
- }
- return activitiesPerformed;
-}
-
std::string GetStringForNotificationType(NotificationType type) {
switch (type) {
case NotificationType::Initial:
diff --git a/toolkit/mozapps/defaultagent/Notification.h b/toolkit/mozapps/defaultagent/Notification.h
index 210c55f559..e4e007088d 100644
--- a/toolkit/mozapps/defaultagent/Notification.h
+++ b/toolkit/mozapps/defaultagent/Notification.h
@@ -7,7 +7,9 @@
#ifndef __DEFAULT_BROWSER_NOTIFICATION_H__
#define __DEFAULT_BROWSER_NOTIFICATION_H__
-#include "DefaultBrowser.h"
+#include <string>
+
+#include "nsStringFwd.h"
namespace mozilla::default_agent {
@@ -39,9 +41,6 @@ struct NotificationActivities {
NotificationAction action;
};
-NotificationActivities MaybeShowNotification(
- const DefaultBrowserInfo& browserInfo, const wchar_t* aumi, bool force);
-
// These take enum values and get strings suitable for telemetry
std::string GetStringForNotificationType(NotificationType type);
std::string GetStringForNotificationShown(NotificationShown shown);
diff --git a/toolkit/mozapps/defaultagent/ScheduledTask.cpp b/toolkit/mozapps/defaultagent/ScheduledTask.cpp
index a9cd647c03..c867e59b76 100644
--- a/toolkit/mozapps/defaultagent/ScheduledTask.cpp
+++ b/toolkit/mozapps/defaultagent/ScheduledTask.cpp
@@ -13,14 +13,11 @@
#include <comutil.h>
#include <taskschd.h>
-#include "readstrings.h"
-#include "updatererrors.h"
#include "EventLog.h"
#include "mozilla/RefPtr.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/WinHeaderOnlyUtils.h"
-#include "WindowsDefaultBrowser.h"
#include "DefaultBrowser.h"
diff --git a/toolkit/mozapps/defaultagent/SetDefaultBrowser.cpp b/toolkit/mozapps/defaultagent/SetDefaultBrowser.cpp
index 8bc0889e67..6d829ff045 100644
--- a/toolkit/mozapps/defaultagent/SetDefaultBrowser.cpp
+++ b/toolkit/mozapps/defaultagent/SetDefaultBrowser.cpp
@@ -6,17 +6,16 @@
#include <windows.h>
#include <appmodel.h>
#include <shlobj.h> // for SHChangeNotify and IApplicationAssociationRegistration
-#include <functional>
#include <timeapi.h>
#include "mozilla/ArrayUtils.h"
#include "mozilla/CmdLineAndEnvUtils.h"
#include "mozilla/RefPtr.h"
+#include "mozilla/Result.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/WindowsVersion.h"
#include "mozilla/WinHeaderOnlyUtils.h"
#include "WindowsUserChoice.h"
-#include "nsThreadUtils.h"
#include "EventLog.h"
#include "SetDefaultBrowser.h"
@@ -70,6 +69,77 @@ static bool AddMillisecondsToSystemTime(SYSTEMTIME& aSystemTime,
return true;
}
+/*
+ * Takes two times: the start time and the current time, and returns the number
+ * of seconds left before the current time hits the next minute from the start
+ * time. Used to check if we are within the same minute as the start time and
+ * how much time we have left to perform an operation in the same minute.
+ *
+ * Used with user choice hashes, which have to be written to the registry
+ * in the same minute as they are generated.
+ *
+ * Example 1:
+ * operationStartTime - 10m 20s 800ms
+ * currentTime - 10m 22s 0ms
+ * The next minute is 11, so the return value is 11m - 10m 22s 0ms, converted to
+ * milliseconds.
+ *
+ * Example 2:
+ * operationStartTime - 10m 59s 800ms
+ * currentTime - 11m 0s 0ms
+ * The next minute is 11, but the minute the operation started on was 10, so the
+ * time to the next minute is 0 (because the current time is already at the next
+ * minute).
+ *
+ * @param operationStartTime
+ * @param currentTime
+ *
+ * @returns the number of milliseconds left from the current time to the
+ * next minute from operationStartTime, or zero if the currentTime is already at
+ * the next minute or greater
+ */
+static WORD GetMillisecondsToNextMinute(SYSTEMTIME operationStartTime,
+ SYSTEMTIME currentTime) {
+ SYSTEMTIME operationStartTimeMinute = operationStartTime;
+ SYSTEMTIME currentTimeMinute = currentTime;
+
+ // Zero out the seconds and milliseconds so that we can confirm they are the
+ // same minutes
+ operationStartTimeMinute.wSecond = 0;
+ operationStartTimeMinute.wMilliseconds = 0;
+
+ currentTimeMinute.wSecond = 0;
+ currentTimeMinute.wMilliseconds = 0;
+
+ // Convert to a 64 bit value so we can compare them directly
+ FILETIME fileTime1;
+ FILETIME fileTime2;
+ if (!::SystemTimeToFileTime(&operationStartTimeMinute, &fileTime1) ||
+ !::SystemTimeToFileTime(&currentTimeMinute, &fileTime2)) {
+ // Error: report that there is 0 milliseconds till the next minute
+ return 0;
+ }
+
+ // The minutes for both times have to be the same, so confirm that they are,
+ // and if they aren't, return 0 milliseconds to indicate that we're already
+ // not on the minute that operationStartTime was on
+ if ((fileTime1.dwLowDateTime != fileTime2.dwLowDateTime) ||
+ (fileTime1.dwHighDateTime != fileTime2.dwHighDateTime)) {
+ return 0;
+ }
+
+ // The minutes are the same; determine the number of milliseconds left until
+ // the next minute
+ const WORD secondsToMilliseconds = 1000;
+ const WORD minutesToSeconds = 60;
+
+ // 1 minute converted to milliseconds - (the current second converted to
+ // milliseconds + the current milliseconds)
+ return (1 * minutesToSeconds * secondsToMilliseconds) -
+ ((currentTime.wSecond * secondsToMilliseconds) +
+ currentTime.wMilliseconds);
+}
+
// Compare two SYSTEMTIMEs as FILETIME after clearing everything
// below minutes.
static bool CheckEqualMinutes(SYSTEMTIME aSystemTime1,
@@ -149,6 +219,75 @@ static bool SetUserChoiceRegistry(const wchar_t* aExt, const wchar_t* aProgID,
return true;
}
+struct LaunchExeErr {};
+using LaunchExeResult =
+ mozilla::Result<std::tuple<DWORD, mozilla::UniquePtr<wchar_t[]>>,
+ LaunchExeErr>;
+
+static LaunchExeResult LaunchExecutable(const wchar_t* exePath, int aArgsLength,
+ const wchar_t* const* aArgs) {
+ const wchar_t* args[] = {exePath};
+ mozilla::UniquePtr<wchar_t[]> cmdLine(mozilla::MakeCommandLine(
+ mozilla::ArrayLength(args), const_cast<wchar_t**>(args), aArgsLength,
+ const_cast<wchar_t**>(aArgs)));
+
+ PROCESS_INFORMATION pi;
+ STARTUPINFOW si = {sizeof(si)};
+ si.dwFlags = STARTF_USESHOWWINDOW;
+ si.wShowWindow = SW_HIDE;
+
+ if (!::CreateProcessW(exePath, cmdLine.get(), nullptr, nullptr, FALSE, 0,
+ nullptr, nullptr, &si, &pi)) {
+ HRESULT hr = HRESULT_FROM_WIN32(GetLastError());
+ LOG_ERROR(hr);
+ return Err(LaunchExeErr());
+ }
+
+ nsAutoHandle process(pi.hProcess);
+ nsAutoHandle mainThread(pi.hThread);
+
+ DWORD exitCode;
+ if (::WaitForSingleObject(process.get(), INFINITE) == WAIT_OBJECT_0 &&
+ ::GetExitCodeProcess(process.get(), &exitCode)) {
+ return std::make_tuple(exitCode, std::move(cmdLine));
+ }
+
+ return Err(LaunchExeErr());
+}
+
+static bool LaunchPowershell(const wchar_t* command,
+ const wchar_t* powershellPath) {
+ const wchar_t* args[] = {
+ L"-NoProfile", // ensure nothing is monkeying with powershell
+ L"-c",
+ command,
+ };
+
+ return LaunchExecutable(powershellPath, mozilla::ArrayLength(args), args)
+ .map([](auto result) {
+ auto& [exitCode, regCmdLine] = result;
+ bool success = (exitCode == 0);
+ if (!success) {
+ LOG_ERROR_MESSAGE(L"%s returned failure exitCode %d",
+ regCmdLine.get(), exitCode);
+ }
+ return success;
+ })
+ .unwrapOr(false);
+}
+
+static bool FindPowershell(mozilla::UniquePtr<wchar_t[]>& powershellPath) {
+ auto exePath = mozilla::MakeUnique<wchar_t[]>(MAX_PATH + 1);
+ if (!ConstructSystem32Path(L"WindowsPowershell\\v1.0\\powershell.exe",
+ exePath.get(), MAX_PATH + 1)) {
+ LOG_ERROR_MESSAGE(L"Failed to construct path to powershell.exe");
+ return false;
+ }
+
+ powershellPath.swap(exePath);
+ return true;
+}
+
/*
* Set an association with a UserChoice key
*
@@ -165,7 +304,9 @@ static bool SetUserChoiceRegistry(const wchar_t* aExt, const wchar_t* aProgID,
static bool SetUserChoice(const wchar_t* aExt, const wchar_t* aSid,
const wchar_t* aProgID, bool inMsix) {
if (inMsix) {
- LOG_ERROR_MESSAGE(L"SetUserChoice does not work on MSIX builds.");
+ LOG_ERROR_MESSAGE(
+ L"SetUserChoice should not be called on MSIX builds. Call "
+ L"SetDefaultExtensionHandlersUserChoiceImplMsix instead.");
return false;
}
@@ -298,6 +439,247 @@ nsresult SetDefaultExtensionHandlersUserChoice(
return rv;
}
+/*
+ * Takes the list of file extension pairs and fills a list of program ids for
+ * each pair.
+ *
+ * @param aFileExtensions array of file association pairs to
+ * set as default, like `[ ".pdf", "FirefoxPDF" ]`.
+ */
+static nsresult GenerateProgramIDs(const nsTArray<nsString>& aFileExtensions,
+ nsTArray<nsString>& progIDs) {
+ for (size_t i = 0; i + 1 < aFileExtensions.Length(); i += 2) {
+ const wchar_t* fileExtension = aFileExtensions[i].get();
+
+ // Formatting the ProgID here prevents using this helper to target arbitrary
+ // ProgIDs.
+ mozilla::UniquePtr<wchar_t[]> progID;
+ nsresult rv = GetMsixProgId(fileExtension, progID);
+ if (NS_FAILED(rv)) {
+ LOG_ERROR_MESSAGE(L"Failed to retrieve MSIX progID for %s",
+ fileExtension);
+ return rv;
+ }
+
+ progIDs.AppendElement(nsString(progID.get()));
+ }
+
+ return NS_OK;
+}
+
+/*
+ * Takes the list of file extension pairs and a matching list of program ids for
+ * each of those pairs and verifies that the system is successfully to match
+ * each.
+ *
+ * @param aFileExtensions array of file association pairs to set as default,
+ * like `[ ".pdf", "FirefoxPDF" ]`.
+ * @param aProgIDs array of program ids. The order of this array matches the
+ * file extensions parameter.
+ */
+static nsresult VerifyUserDefaults(const nsTArray<nsString>& aFileExtensions,
+ const nsTArray<nsString>& aProgIDs) {
+ for (size_t i = 0; i + 1 < aFileExtensions.Length(); i += 2) {
+ const wchar_t* fileExtension = aFileExtensions[i].get();
+
+ if (!VerifyUserDefault(fileExtension, aProgIDs[i / 2].get())) {
+ return NS_ERROR_WDBA_REJECTED;
+ }
+ }
+
+ return NS_OK;
+}
+
+/*
+ * Queries the system for the minimum resolution (or tick time) in milliseconds
+ * that can be used with ::Sleep. If a Sleep is not triggered with a time
+ * divisible by the tick time, control can return to the thread before the time
+ * specified to Sleep.
+ *
+ * @param defaultValue what to return if the system query fails.
+ */
+static UINT GetSystemSleepIntervalInMilliseconds(UINT defaultValue) {
+ TIMECAPS timeCapabilities;
+ bool timeCapsFetchSuccessful =
+ (MMSYSERR_NOERROR ==
+ timeGetDevCaps(&timeCapabilities, sizeof(timeCapabilities)));
+ if (!timeCapsFetchSuccessful) {
+ return defaultValue;
+ }
+
+ return timeCapabilities.wPeriodMin > 0 ? timeCapabilities.wPeriodMin
+ : defaultValue;
+}
+
+/*
+ * MSIX implementation for SetDefaultExtensionHandlersUserChoice.
+ *
+ * Due to the fact that MSIX builds run in a virtual, walled off environment,
+ * calling into the Win32 registry APIs doesn't work to set registry keys.
+ * MSIX builds access a virtual registry.
+ *
+ * Sends a "script" via command line to Powershell to modify the registry
+ * in an executable that is outside of the walled garden MSIX FX package
+ * environment, which is the only way to do that so far. Only works
+ * on slightly older versions of Windows 11 (older than 23H2).
+ *
+ * This was originally done using calls to Reg.exe, but permissions can't
+ * be changed that way, requiring using Powershell to call into .Net functions
+ * directly. Launching Powershell is slow (on the order of a second on some
+ * systems) so this method jams the calls to do everything into one launch of
+ * Powershell, to make it as quick as possible.
+ *
+ */
+static nsresult SetDefaultExtensionHandlersUserChoiceImplMsix(
+ const wchar_t* aAumi, const wchar_t* const aSid,
+ const nsTArray<nsString>& aFileExtensions) {
+ mozilla::UniquePtr<wchar_t[]> exePath;
+ if (!FindPowershell(exePath)) {
+ LOG_ERROR_MESSAGE(L"Could not locate Powershell");
+ return NS_ERROR_FAILURE;
+ }
+
+ // The following is the start of the script that will be sent to Powershell.
+ // In includes a function; calls to the function get appended per file
+ // extension, done below.
+ nsString startScript(
+ uR"(
+# Force exceptions to stop execution
+$ErrorActionPreference = 'Stop'
+
+function Set-DefaultHandlerRegistry($Path, $ProgID, $Hash) {
+ $Path = "$Path\UserChoice"
+ $CurrentUser = [Microsoft.Win32.Registry]::CurrentUser
+
+ # DeleteSubKey throws if we don't have sufficient permissions to delete key,
+ # signaling failure to launching process.
+ #
+ # Note: DeleteSubKeyTree fails when DENY permissions are set on key, whereas
+ # DeleteSubKey succeeds.
+ $CurrentUser.DeleteSubKey($Path, $false)
+ $key = $CurrentUser.CreateSubKey($Path)
+
+ $StringType = [Microsoft.Win32.RegistryValueKind]::String
+ $key.SetValue('ProgID', $ProgID, $StringType)
+ $key.SetValue('Hash', $Hash, $StringType)
+}
+
+)"); // Newlines in the above powershell script at the end are important!!!
+
+ // NOTE!!!! CreateProcess / calling things on the command line has a character
+ // count limit. For CMD.exe, it's 8K. For CreateProcessW, it's technically
+ // 32K. I can't find documentation about Powershell, but think 8K is safe to
+ // assume as a good maximum. The above script is about 1000 characters, and we
+ // will append another 100 characters at most per extension, so we'd need to
+ // be setting about 70 handlers at once to worry about the theoretical limit.
+ // Which we won't do. So the length shouldn't be a problem.
+ if (aFileExtensions.Length() >= 70) {
+ LOG_ERROR_MESSAGE(
+ L"SetDefaultExtensionHandlersUserChoiceImplMsix can't cope with 70 or "
+ L"more file extensions at once. Please break it up into multiple calls "
+ L"with fewer extensions per call.");
+ return NS_ERROR_FAILURE;
+ }
+
+ // NOTE!!!!
+ // User choice hashes have to be generated and written to the registry in the
+ // same minute. So we do everything we can upfront before we get to the hash
+ // generation, to ensure that hash generation and the call to Powershell to
+ // write to the registry has as much time as possible to run.
+
+ // Program ID fetch / generation might be slow, so do that ahead of time.
+ nsTArray<nsString> progIDs;
+ nsresult rv = GenerateProgramIDs(aFileExtensions, progIDs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString scriptBuffer;
+
+ // Everyting in the loop below should succeed or fail reasonably fast (within
+ // 20 milliseconds or something well under a second) besides the Powershell
+ // call. The Powershell call itself will either fail or succeed and break out
+ // of the loop, so repeating 10 times should be fine and is mostly to allow
+ // for getting the timing right with the user choice hash generation happening
+ // in the same minute - meaning this should likely only happen twice through
+ // the loop at most.
+ for (int i = 0; i < 10; i++) {
+ // Pre-allocate the memory for the scriptBuffer upfront so that we don't
+ // have to keep allocating every time Append is called.
+ const int scriptBufferCapacity = 16 * 1024;
+ scriptBuffer = startScript;
+ scriptBuffer.SetCapacity(scriptBufferCapacity);
+
+ SYSTEMTIME hashTimestamp;
+ ::GetSystemTime(&hashTimestamp);
+
+ // Time critical stuff starts here:
+
+ for (size_t i = 0; i + 1 < aFileExtensions.Length(); i += 2) {
+ const wchar_t* fileExtension = aFileExtensions[i].get();
+
+ nsAutoString keyPath;
+ AppendAssociationKeyPath(fileExtension, keyPath);
+
+ auto hashWchar = GenerateUserChoiceHash(
+ fileExtension, aSid, progIDs[i / 2].get(), hashTimestamp);
+ if (!hashWchar) {
+ return NS_ERROR_FAILURE;
+ }
+ auto hash = nsDependentString(hashWchar.get());
+
+ // Append a line to the script buffer in the form:
+ // Set-DefaultHandlerRegistry $RegistryKeyPath $ProgID $UserChoiceHash
+ scriptBuffer += u"Set-DefaultHandlerRegistry "_ns + keyPath + u" "_ns +
+ progIDs[i / 2] + u" "_ns + hash + u"\n"_ns;
+ }
+
+ // The hash changes at the end of each minute, so check that the hash should
+ // be the same by the time we're done writing.
+ const ULONGLONG kWriteTimingThresholdMilliseconds = 2000;
+
+ // Generating the hash could have taken some time, so figure out what time
+ // we are at right now.
+ SYSTEMTIME writeEndTimestamp;
+ ::GetSystemTime(&writeEndTimestamp);
+
+ // Check if we have enough time to launch Powershell or if we should sleep
+ // to the next minute and try again
+ auto millisecondsLeftUntilNextMinute =
+ GetMillisecondsToNextMinute(hashTimestamp, writeEndTimestamp);
+ if (millisecondsLeftUntilNextMinute >= kWriteTimingThresholdMilliseconds) {
+ break;
+ }
+
+ LOG_ERROR_MESSAGE(
+ L"Hash is too close to next minute, sleeping until next minute to "
+ L"ensure that hash generation matches write to registry.");
+
+ UINT sleepUntilNextMinuteBufferMilliseconds =
+ GetSystemSleepIntervalInMilliseconds(
+ 50); // Default to 50ms if we can't figure out the right interval
+ // to sleep for
+ ::Sleep(millisecondsLeftUntilNextMinute +
+ (sleepUntilNextMinuteBufferMilliseconds * 2));
+
+ // Try again, if we have any attempts left
+ }
+
+ // Call Powershell to set the registry keys now - this is the really time
+ // consuming thing 250ms to launch on a VM in a reasonably fast case, possibly
+ // a lot slower on other systems
+ bool powershellSuccessful =
+ LaunchPowershell(scriptBuffer.get(), exePath.get());
+ if (!powershellSuccessful) {
+ // If powershell failed, it likely means that something got mucked with
+ // the registry and that Windows is popping up notifications to the user,
+ // so don't try again right now, so as to not overwhelm the user and annoy
+ // them.
+ return NS_ERROR_FAILURE;
+ }
+
+ // Validate now
+ return VerifyUserDefaults(aFileExtensions, progIDs);
+}
+
nsresult SetDefaultExtensionHandlersUserChoiceImpl(
const wchar_t* aAumi, const wchar_t* const aSid,
const nsTArray<nsString>& aFileExtensions) {
@@ -306,9 +688,8 @@ nsresult SetDefaultExtensionHandlersUserChoiceImpl(
GetCurrentPackageFullName(&pfnLen, nullptr) != APPMODEL_ERROR_NO_PACKAGE;
if (inMsix) {
- // MSIX packages can not meaningfully modify the registry keys related to
- // default handlers
- return NS_ERROR_FAILURE;
+ return SetDefaultExtensionHandlersUserChoiceImplMsix(aAumi, aSid,
+ aFileExtensions);
}
for (size_t i = 0; i + 1 < aFileExtensions.Length(); i += 2) {
diff --git a/toolkit/mozapps/defaultagent/defaultagent.ini b/toolkit/mozapps/defaultagent/defaultagent.ini
deleted file mode 100644
index 9300b20c46..0000000000
--- a/toolkit/mozapps/defaultagent/defaultagent.ini
+++ /dev/null
@@ -1,9 +0,0 @@
-; This Source Code Form is subject to the terms of the Mozilla Public
-; License, v. 2.0. If a copy of the MPL was not distributed with this
-; file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-; This file is in the UTF-8 encoding
-[Nonlocalized]
-InitialToastRelativeImagePath=browser/VisualElements/VisualElements_150.png
-FollowupToastRelativeImagePath=browser/VisualElements/VisualElements_150.png
-LocalizedToastRelativeImagePath=browser/VisualElements/VisualElements_150.png
diff --git a/toolkit/mozapps/defaultagent/moz.build b/toolkit/mozapps/defaultagent/moz.build
index 86b68c6371..f8ae506d9c 100644
--- a/toolkit/mozapps/defaultagent/moz.build
+++ b/toolkit/mozapps/defaultagent/moz.build
@@ -15,6 +15,7 @@ UNIFIED_SOURCES += [
"DefaultBrowser.cpp",
"DefaultPDF.cpp",
"EventLog.cpp",
+ "Notification.cpp",
"Policy.cpp",
"Registry.cpp",
"ScheduledTask.cpp",
@@ -25,21 +26,6 @@ UNIFIED_SOURCES += [
"WindowsMutex.cpp",
]
-SOURCES += [
- "/third_party/WinToast/wintoastlib.cpp",
- "/toolkit/mozapps/update/common/readstrings.cpp",
- "Notification.cpp",
-]
-
-# Suppress warnings from third-party code.
-SOURCES["/third_party/WinToast/wintoastlib.cpp"].flags += [
- "-Wno-implicit-fallthrough",
- "-Wno-nonportable-include-path", # Needed for wintoastlib.h including "Windows.h"
-]
-SOURCES["Notification.cpp"].flags += [
- "-Wno-nonportable-include-path", # Needed for wintoastlib.h including "Windows.h"
-]
-
EXPORTS.mozilla += [
"DefaultAgent.h",
"WindowsMutex.h",
@@ -52,9 +38,7 @@ USE_LIBS += [
LOCAL_INCLUDES += [
"/browser/components/shell/",
"/other-licenses/nsis/Contrib/CityHash/cityhash",
- "/third_party/WinToast",
"/toolkit/components/jsoncpp/include",
- "/toolkit/mozapps/update/common",
]
OS_LIBS += [
@@ -98,8 +82,6 @@ for var in ("MOZ_APP_BASENAME", "MOZ_APP_DISPLAYNAME", "MOZ_APP_VENDOR"):
DEFINES["UNICODE"] = True
DEFINES["_UNICODE"] = True
-FINAL_TARGET_FILES += ["defaultagent.ini"]
-
FINAL_LIBRARY = "xul"
if CONFIG["ENABLE_TESTS"]:
diff --git a/toolkit/mozapps/defaultagent/nsIDefaultAgent.idl b/toolkit/mozapps/defaultagent/nsIDefaultAgent.idl
index 7e78e1b30d..8472dea3af 100644
--- a/toolkit/mozapps/defaultagent/nsIDefaultAgent.idl
+++ b/toolkit/mozapps/defaultagent/nsIDefaultAgent.idl
@@ -52,20 +52,6 @@ interface nsIDefaultAgent : nsISupports
void uninstall(in AString aUniqueToken);
/**
- * Actually performs the default agent task, which currently means generating
- * and sending our telemetry ping and possibly showing a notification to the
- * user if their browser has switched from Firefox to Edge with Blink.
- *
- * @param {AString} aUniqueToken
- * A unique identifier for this installation; the same one provided when
- * the task was registered.
- * @param {boolean} aForce
- * For debugging, forces the task to run even if it has run in the last
- * 24 hours, and forces the notification to show.
- */
- void doTask(in AString aUniqueToken, in boolean aForce);
-
- /**
* Checks that the main app ran recently.
*
* @return {boolean} true if the app ran recently.
diff --git a/toolkit/mozapps/defaultagent/proxy/main.cpp b/toolkit/mozapps/defaultagent/proxy/main.cpp
index 53efdcb9ae..7ba63c2095 100644
--- a/toolkit/mozapps/defaultagent/proxy/main.cpp
+++ b/toolkit/mozapps/defaultagent/proxy/main.cpp
@@ -7,10 +7,8 @@
#include <windows.h>
#include <shlwapi.h>
#include <objbase.h>
-#include <string.h>
#include <filesystem>
-#include "../ScheduledTask.h"
#include "../ScheduledTaskRemove.h"
#include "mozilla/CmdLineAndEnvUtils.h"
diff --git a/toolkit/mozapps/defaultagent/proxy/moz.build b/toolkit/mozapps/defaultagent/proxy/moz.build
index 40d7655dfa..3ff6932ac2 100644
--- a/toolkit/mozapps/defaultagent/proxy/moz.build
+++ b/toolkit/mozapps/defaultagent/proxy/moz.build
@@ -17,7 +17,6 @@ UNIFIED_SOURCES += [
SOURCES += [
"/browser/components/shell/WindowsDefaultBrowser.cpp",
"/other-licenses/nsis/Contrib/CityHash/cityhash/city.cpp",
- "/toolkit/mozapps/update/common/readstrings.cpp",
]
LOCAL_INCLUDES += [