diff options
Diffstat (limited to 'toolkit/mozapps/defaultagent')
-rw-r--r-- | toolkit/mozapps/defaultagent/BackgroundTask_defaultagent.sys.mjs | 45 | ||||
-rw-r--r-- | toolkit/mozapps/defaultagent/DefaultAgent.cpp | 59 | ||||
-rw-r--r-- | toolkit/mozapps/defaultagent/DefaultBrowser.cpp | 11 | ||||
-rw-r--r-- | toolkit/mozapps/defaultagent/DefaultBrowser.h | 4 | ||||
-rw-r--r-- | toolkit/mozapps/defaultagent/Notification.cpp | 602 | ||||
-rw-r--r-- | toolkit/mozapps/defaultagent/Notification.h | 7 | ||||
-rw-r--r-- | toolkit/mozapps/defaultagent/ScheduledTask.cpp | 3 | ||||
-rw-r--r-- | toolkit/mozapps/defaultagent/SetDefaultBrowser.cpp | 393 | ||||
-rw-r--r-- | toolkit/mozapps/defaultagent/defaultagent.ini | 9 | ||||
-rw-r--r-- | toolkit/mozapps/defaultagent/moz.build | 20 | ||||
-rw-r--r-- | toolkit/mozapps/defaultagent/nsIDefaultAgent.idl | 14 | ||||
-rw-r--r-- | toolkit/mozapps/defaultagent/proxy/main.cpp | 2 | ||||
-rw-r--r-- | toolkit/mozapps/defaultagent/proxy/moz.build | 1 |
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(¤tTimeMinute, &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 += [ |