| author | Adam Gashlin <agashlin@mozilla.com> |
| Thu, 17 Jun 2021 18:06:35 +0000 | |
| changeset 583757 | 91f7b8dc86708e694233f5ddbd2c4213febf90fb |
| parent 583756 | 19dce9dc4c02caf462e271286ab70be7aa7c3eab |
| child 583758 | fe2b9abdb53bf6d24ea184ebcb77350f38ea8405 |
| push id | 145231 |
| push user | agashlin@mozilla.com |
| push date | Thu, 17 Jun 2021 20:30:21 +0000 |
| treeherder | autoland@fe2b9abdb53b [default view] [failures only] |
| perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
| reviewers | bytesized |
| bugs | 1703578 |
| milestone | 91.0a1 |
| first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
| last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -258,16 +258,22 @@ pref("browser.compactmode.show", false); // At startup, check if we're the default browser and prompt user if not. pref("browser.shell.checkDefaultBrowser", true); pref("browser.shell.shortcutFavicons",true); pref("browser.shell.mostRecentDateSetAsDefault", ""); pref("browser.shell.skipDefaultBrowserCheckOnFirstRun", true); pref("browser.shell.didSkipDefaultBrowserCheckOnFirstRun", false); pref("browser.shell.defaultBrowserCheckCount", 0); +#if defined(XP_WIN) +// Attempt to set the default browser on Windows 10 using the UserChoice registry keys, +// before falling back to launching the modern Settings dialog. +pref("browser.shell.setDefaultBrowserUserChoice", true); +#endif + // 0 = blank, 1 = home (browser.startup.homepage), 2 = last visited page, 3 = resume previous browser session // The behavior of option 3 is detailed at: http://wiki.mozilla.org/Session_Restore pref("browser.startup.page", 1); pref("browser.startup.homepage", "about:home"); #ifdef NIGHTLY_BUILD pref("browser.startup.homepage.abouthome_cache.enabled", true); #else
--- a/browser/components/shell/ShellService.jsm +++ b/browser/components/shell/ShellService.jsm @@ -14,16 +14,28 @@ const { XPCOMUtils } = ChromeUtils.impor "resource://gre/modules/XPCOMUtils.jsm" ); ChromeUtils.defineModuleGetter( this, "WindowsRegistry", "resource://gre/modules/WindowsRegistry.jsm" ); +XPCOMUtils.defineLazyModuleGetters(this, { + Subprocess: "resource://gre/modules/Subprocess.jsm", + setTimeout: "resource://gre/modules/Timer.jsm", +}); + +XPCOMUtils.defineLazyServiceGetter( + this, + "XreDirProvider", + "@mozilla.org/xre/directory-provider;1", + "nsIXREDirProvider" +); + /** * Internal functionality to save and restore the docShell.allow* properties. */ let ShellServiceInternal = { /** * Used to determine whether or not to offer "Set as desktop background" * functionality. Even if shell service is available it is not * guaranteed that it is able to set the background for every desktop @@ -107,16 +119,94 @@ let ShellServiceInternal = { this._checkedThisSession = true; } if (this.shellService) { return this.shellService.isDefaultBrowser(forAllTypes); } return false; }, + /* + * Set the default browser through the UserChoice registry keys on Windows. + * + * NOTE: This does NOT open the System Settings app for manual selection + * in case of failure. If that is desired, catch the exception and call + * setDefaultBrowser(). + * + * @return Promise, resolves when successful, rejects with Error on failure. + */ + async setAsDefaultUserChoice() { + if (AppConstants.platform != "win") { + throw new Error("Windows-only"); + } + + // We launch the WDBA to handle the registry writes, see + // SetDefaultBrowserUserChoice() in + // toolkit/mozapps/defaultagent/SetDefaultBrowser.cpp. + // This is external in case an overzealous antimalware product decides to + // quarrantine any program that writes UserChoice, though this has not + // occurred during extensive testing. + + if (!ShellService.checkAllProgIDsExist()) { + throw new Error("checkAllProgIDsExist() failed"); + } + + if (!ShellService.checkBrowserUserChoiceHashes()) { + throw new Error("checkBrowserUserChoiceHashes() failed"); + } + + const wdba = Services.dirsvc.get("XREExeF", Ci.nsIFile); + wdba.leafName = "default-browser-agent.exe"; + const aumi = XreDirProvider.getInstallHash(); + + const exeProcess = await Subprocess.call({ + command: wdba.path, + arguments: ["set-default-browser-user-choice", aumi], + }); + + // exit codes + const S_OK = 0; + const STILL_ACTIVE = 0x103; + + const exeWaitTimeoutMs = 2000; // 2 seconds + const exeWaitPromise = exeProcess.wait(); + const timeoutPromise = new Promise(function(resolve, reject) { + setTimeout(() => resolve({ exitCode: STILL_ACTIVE }), exeWaitTimeoutMs); + }); + const { exitCode } = await Promise.race([exeWaitPromise, timeoutPromise]); + + if (exitCode != S_OK) { + throw new Error(`WDBA nonzero exit code ${exitCode}`); + } + }, + + // override nsIShellService.setDefaultBrowser() on the ShellService proxy. + setDefaultBrowser(claimAllTypes, forAllUsers) { + // On Windows 10, our best chance is to set UserChoice, so try that first. + if ( + AppConstants.isPlatformAndVersionAtLeast("win", "10") && + Services.prefs.getBoolPref( + "browser.shell.setDefaultBrowserUserChoice", + true + ) + ) { + // nsWindowsShellService::SetDefaultBrowser() kicks off several + // operations, but doesn't wait for their result. So we don't need to + // await the result of setAsDefaultUserChoice() here, either, we just need + // to fall back in case it fails. + this.setAsDefaultUserChoice().catch(err => { + Cu.reportError(err); + this.shellService.setDefaultBrowser(claimAllTypes, forAllUsers); + }); + return; + } + + this.shellService.setDefaultBrowser(claimAllTypes, forAllUsers); + }, + setAsDefault() { let claimAllTypes = true; let setAsDefaultError = false; if (AppConstants.platform == "win") { try { // In Windows 8+, the UI for selecting default protocol is much // nicer than the UI for setting file type associations. So we // only show the protocol association screen on Windows 8+.
--- a/browser/components/shell/moz.build +++ b/browser/components/shell/moz.build @@ -46,36 +46,44 @@ elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "gt elif CONFIG["OS_ARCH"] == "WINNT": XPIDL_SOURCES += [ "nsIWindowsShellService.idl", ] SOURCES += [ "nsWindowsShellService.cpp", "WindowsDefaultBrowser.cpp", + "WindowsUserChoice.cpp", ] LOCAL_INCLUDES += [ "../../../other-licenses/nsis/Contrib/CityHash/cityhash", ] OS_LIBS += [ + "bcrypt", + "crypt32", "propsys", ] XPIDL_MODULE = "shellservice" if SOURCES: FINAL_LIBRARY = "browsercomps" EXTRA_JS_MODULES += [ "HeadlessShell.jsm", "ScreenshotChild.jsm", "ShellService.jsm", ] -for var in ("MOZ_APP_DISPLAYNAME", "MOZ_APP_NAME", "MOZ_APP_VERSION"): +for var in ( + "MOZ_APP_DISPLAYNAME", + "MOZ_APP_NAME", + "MOZ_APP_VERSION", + "MOZ_DEFAULT_BROWSER_AGENT", +): DEFINES[var] = '"%s"' % CONFIG[var] CXXFLAGS += CONFIG["TK_CFLAGS"] if CONFIG["MOZ_ENABLE_DBUS"]: CXXFLAGS += CONFIG["MOZ_DBUS_GLIB_CFLAGS"] with Files("**"): BUG_COMPONENT = ("Firefox", "Shell Integration")
--- a/browser/components/shell/nsIWindowsShellService.idl +++ b/browser/components/shell/nsIWindowsShellService.idl @@ -84,9 +84,28 @@ interface nsIWindowsShellService : nsISu * - "Taskbar", Taskbar Pins * - "" otherwise * * NOTE: This tries to avoid I/O, so paths are compared directly as * strings, which may not be accurate in all cases. It is intended * for noncritical telemetry use. */ AString classifyShortcut(in AString aPath); + + /* + * Check if setDefaultBrowserUserChoice() is expected to succeed. + * + * This checks the ProgIDs for this installation, and the hash of the existing + * UserChoice association. + * + * @return true if the check succeeds, false otherwise. + */ + bool canSetDefaultBrowserUserChoice(); + + /* + * checkAllProgIDsExist() and checkBrowserUserChoiceHashes() are components + * of canSetDefaultBrowserUserChoice(), broken out for telemetry purposes. + * + * @return true if the check succeeds, false otherwise. + */ + bool checkAllProgIDsExist(); + bool checkBrowserUserChoiceHashes(); };
--- a/browser/components/shell/nsWindowsShellService.cpp +++ b/browser/components/shell/nsWindowsShellService.cpp @@ -26,16 +26,17 @@ #include "nsIURLFormatter.h" #include "nsXULAppAPI.h" #include "mozilla/WindowsVersion.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/Promise.h" #include "mozilla/ErrorResult.h" #include "mozilla/gfx/2D.h" #include "WindowsDefaultBrowser.h" +#include "WindowsUserChoice.h" #include <windows.h> #include <shellapi.h> #include <propvarutil.h> #include <propkey.h> #ifdef __MINGW32__ // MinGW-w64 headers are missing PropVariantToString. @@ -226,16 +227,49 @@ nsresult nsWindowsShellService::LaunchCo } return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE; } nsresult nsWindowsShellService::LaunchControlPanelDefaultPrograms() { return ::LaunchControlPanelDefaultPrograms() ? NS_OK : NS_ERROR_FAILURE; } +NS_IMETHODIMP +nsWindowsShellService::CheckAllProgIDsExist(bool* aResult) { + *aResult = false; + nsAutoString aumid; + if (!mozilla::widget::WinTaskbar::GetAppUserModelID(aumid)) { + return NS_OK; + } + *aResult = + CheckProgIDExists(FormatProgID(L"FirefoxURL", aumid.get()).get()) && + CheckProgIDExists(FormatProgID(L"FirefoxHTML", aumid.get()).get()); + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsShellService::CheckBrowserUserChoiceHashes(bool* aResult) { + *aResult = ::CheckBrowserUserChoiceHashes(); + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsShellService::CanSetDefaultBrowserUserChoice(bool* aResult) { + *aResult = false; +// If the WDBA is not available, this could never succeed. +#ifdef MOZ_DEFAULT_BROWSER_AGENT + bool progIDsExist = false; + bool hashOk = false; + *aResult = NS_SUCCEEDED(CheckAllProgIDsExist(&progIDsExist)) && + progIDsExist && + NS_SUCCEEDED(CheckBrowserUserChoiceHashes(&hashOk)) && hashOk; +#endif + return NS_OK; +} + nsresult nsWindowsShellService::LaunchModernSettingsDialogDefaultApps() { return ::LaunchModernSettingsDialogDefaultApps() ? NS_OK : NS_ERROR_FAILURE; } nsresult nsWindowsShellService::InvokeHTTPOpenAsVerb() { nsCOMPtr<nsIURLFormatter> formatter( do_GetService("@mozilla.org/toolkit/URLFormatterService;1")); if (!formatter) {