Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.

Commit 00133ca

Browse files
committed
Bug 1703578 - Part 3: Invoke WDBA to set UserChoice. r=bytesized
Depends on D113427 Differential Revision: https://phabricator.services.mozilla.com/D113428
1 parent f22ce8f commit 00133ca

5 files changed

Lines changed: 158 additions & 1 deletion

File tree

browser/app/profile/firefox.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,12 @@ pref("browser.shell.mostRecentDateSetAsDefault", "");
263263
pref("browser.shell.skipDefaultBrowserCheckOnFirstRun", true);
264264
pref("browser.shell.didSkipDefaultBrowserCheckOnFirstRun", false);
265265
pref("browser.shell.defaultBrowserCheckCount", 0);
266+
#if defined(XP_WIN)
267+
// Attempt to set the default browser on Windows 10 using the UserChoice registry keys,
268+
// before falling back to launching the modern Settings dialog.
269+
pref("browser.shell.setDefaultBrowserUserChoice", true);
270+
#endif
271+
266272

267273
// 0 = blank, 1 = home (browser.startup.homepage), 2 = last visited page, 3 = resume previous browser session
268274
// The behavior of option 3 is detailed at: http://wiki.mozilla.org/Session_Restore

browser/components/shell/ShellService.jsm

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,18 @@ ChromeUtils.defineModuleGetter(
1919
"resource://gre/modules/WindowsRegistry.jsm"
2020
);
2121

22+
XPCOMUtils.defineLazyModuleGetters(this, {
23+
Subprocess: "resource://gre/modules/Subprocess.jsm",
24+
setTimeout: "resource://gre/modules/Timer.jsm",
25+
});
26+
27+
XPCOMUtils.defineLazyServiceGetter(
28+
this,
29+
"XreDirProvider",
30+
"@mozilla.org/xre/directory-provider;1",
31+
"nsIXREDirProvider"
32+
);
33+
2234
/**
2335
* Internal functionality to save and restore the docShell.allow* properties.
2436
*/
@@ -112,6 +124,84 @@ let ShellServiceInternal = {
112124
return false;
113125
},
114126

127+
/*
128+
* Set the default browser through the UserChoice registry keys on Windows.
129+
*
130+
* NOTE: This does NOT open the System Settings app for manual selection
131+
* in case of failure. If that is desired, catch the exception and call
132+
* setDefaultBrowser().
133+
*
134+
* @return Promise, resolves when successful, rejects with Error on failure.
135+
*/
136+
async setAsDefaultUserChoice() {
137+
if (AppConstants.platform != "win") {
138+
throw new Error("Windows-only");
139+
}
140+
141+
// We launch the WDBA to handle the registry writes, see
142+
// SetDefaultBrowserUserChoice() in
143+
// toolkit/mozapps/defaultagent/SetDefaultBrowser.cpp.
144+
// This is external in case an overzealous antimalware product decides to
145+
// quarrantine any program that writes UserChoice, though this has not
146+
// occurred during extensive testing.
147+
148+
if (!ShellService.checkAllProgIDsExist()) {
149+
throw new Error("checkAllProgIDsExist() failed");
150+
}
151+
152+
if (!ShellService.checkBrowserUserChoiceHashes()) {
153+
throw new Error("checkBrowserUserChoiceHashes() failed");
154+
}
155+
156+
const wdba = Services.dirsvc.get("XREExeF", Ci.nsIFile);
157+
wdba.leafName = "default-browser-agent.exe";
158+
const aumi = XreDirProvider.getInstallHash();
159+
160+
const exeProcess = await Subprocess.call({
161+
command: wdba.path,
162+
arguments: ["set-default-browser-user-choice", aumi],
163+
});
164+
165+
// exit codes
166+
const S_OK = 0;
167+
const STILL_ACTIVE = 0x103;
168+
169+
const exeWaitTimeoutMs = 2000; // 2 seconds
170+
const exeWaitPromise = exeProcess.wait();
171+
const timeoutPromise = new Promise(function(resolve, reject) {
172+
setTimeout(() => resolve({ exitCode: STILL_ACTIVE }), exeWaitTimeoutMs);
173+
});
174+
const { exitCode } = await Promise.race([exeWaitPromise, timeoutPromise]);
175+
176+
if (exitCode != S_OK) {
177+
throw new Error(`WDBA nonzero exit code ${exitCode}`);
178+
}
179+
},
180+
181+
// override nsIShellService.setDefaultBrowser() on the ShellService proxy.
182+
setDefaultBrowser(claimAllTypes, forAllUsers) {
183+
// On Windows 10, our best chance is to set UserChoice, so try that first.
184+
if (
185+
AppConstants.isPlatformAndVersionAtLeast("win", "10") &&
186+
Services.prefs.getBoolPref(
187+
"browser.shell.setDefaultBrowserUserChoice",
188+
true
189+
)
190+
) {
191+
// nsWindowsShellService::SetDefaultBrowser() kicks off several
192+
// operations, but doesn't wait for their result. So we don't need to
193+
// await the result of setAsDefaultUserChoice() here, either, we just need
194+
// to fall back in case it fails.
195+
this.setAsDefaultUserChoice().catch(err => {
196+
Cu.reportError(err);
197+
this.shellService.setDefaultBrowser(claimAllTypes, forAllUsers);
198+
});
199+
return;
200+
}
201+
202+
this.shellService.setDefaultBrowser(claimAllTypes, forAllUsers);
203+
},
204+
115205
setAsDefault() {
116206
let claimAllTypes = true;
117207
let setAsDefaultError = false;

browser/components/shell/moz.build

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,14 @@ elif CONFIG["OS_ARCH"] == "WINNT":
5151
SOURCES += [
5252
"nsWindowsShellService.cpp",
5353
"WindowsDefaultBrowser.cpp",
54+
"WindowsUserChoice.cpp",
5455
]
5556
LOCAL_INCLUDES += [
5657
"../../../other-licenses/nsis/Contrib/CityHash/cityhash",
5758
]
5859
OS_LIBS += [
60+
"bcrypt",
61+
"crypt32",
5962
"propsys",
6063
]
6164

@@ -70,7 +73,12 @@ EXTRA_JS_MODULES += [
7073
"ShellService.jsm",
7174
]
7275

73-
for var in ("MOZ_APP_DISPLAYNAME", "MOZ_APP_NAME", "MOZ_APP_VERSION"):
76+
for var in (
77+
"MOZ_APP_DISPLAYNAME",
78+
"MOZ_APP_NAME",
79+
"MOZ_APP_VERSION",
80+
"MOZ_DEFAULT_BROWSER_AGENT",
81+
):
7482
DEFINES[var] = '"%s"' % CONFIG[var]
7583

7684
CXXFLAGS += CONFIG["TK_CFLAGS"]

browser/components/shell/nsIWindowsShellService.idl

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,23 @@ interface nsIWindowsShellService : nsISupports
8989
* for noncritical telemetry use.
9090
*/
9191
AString classifyShortcut(in AString aPath);
92+
93+
/*
94+
* Check if setDefaultBrowserUserChoice() is expected to succeed.
95+
*
96+
* This checks the ProgIDs for this installation, and the hash of the existing
97+
* UserChoice association.
98+
*
99+
* @return true if the check succeeds, false otherwise.
100+
*/
101+
bool canSetDefaultBrowserUserChoice();
102+
103+
/*
104+
* checkAllProgIDsExist() and checkBrowserUserChoiceHashes() are components
105+
* of canSetDefaultBrowserUserChoice(), broken out for telemetry purposes.
106+
*
107+
* @return true if the check succeeds, false otherwise.
108+
*/
109+
bool checkAllProgIDsExist();
110+
bool checkBrowserUserChoiceHashes();
92111
};

browser/components/shell/nsWindowsShellService.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "mozilla/ErrorResult.h"
3232
#include "mozilla/gfx/2D.h"
3333
#include "WindowsDefaultBrowser.h"
34+
#include "WindowsUserChoice.h"
3435

3536
#include <windows.h>
3637
#include <shellapi.h>
@@ -231,6 +232,39 @@ nsresult nsWindowsShellService::LaunchControlPanelDefaultPrograms() {
231232
return ::LaunchControlPanelDefaultPrograms() ? NS_OK : NS_ERROR_FAILURE;
232233
}
233234

235+
NS_IMETHODIMP
236+
nsWindowsShellService::CheckAllProgIDsExist(bool* aResult) {
237+
*aResult = false;
238+
nsAutoString aumid;
239+
if (!mozilla::widget::WinTaskbar::GetAppUserModelID(aumid)) {
240+
return NS_OK;
241+
}
242+
*aResult =
243+
CheckProgIDExists(FormatProgID(L"FirefoxURL", aumid.get()).get()) &&
244+
CheckProgIDExists(FormatProgID(L"FirefoxHTML", aumid.get()).get());
245+
return NS_OK;
246+
}
247+
248+
NS_IMETHODIMP
249+
nsWindowsShellService::CheckBrowserUserChoiceHashes(bool* aResult) {
250+
*aResult = ::CheckBrowserUserChoiceHashes();
251+
return NS_OK;
252+
}
253+
254+
NS_IMETHODIMP
255+
nsWindowsShellService::CanSetDefaultBrowserUserChoice(bool* aResult) {
256+
*aResult = false;
257+
// If the WDBA is not available, this could never succeed.
258+
#ifdef MOZ_DEFAULT_BROWSER_AGENT
259+
bool progIDsExist = false;
260+
bool hashOk = false;
261+
*aResult = NS_SUCCEEDED(CheckAllProgIDsExist(&progIDsExist)) &&
262+
progIDsExist &&
263+
NS_SUCCEEDED(CheckBrowserUserChoiceHashes(&hashOk)) && hashOk;
264+
#endif
265+
return NS_OK;
266+
}
267+
234268
nsresult nsWindowsShellService::LaunchModernSettingsDialogDefaultApps() {
235269
return ::LaunchModernSettingsDialogDefaultApps() ? NS_OK : NS_ERROR_FAILURE;
236270
}

0 commit comments

Comments
 (0)