Skip to content

Commit 954e579

Browse files
committed
Bug 1860271 - Resolved launch on login checkbox and infobar bugs when user created Startup shortcut exists r=nalexander,settings-reviewers
Differential Revision: https://phabricator.services.mozilla.com/D191763
1 parent 9f7d060 commit 954e579

File tree

7 files changed

+216
-23
lines changed

7 files changed

+216
-23
lines changed

browser/components/BrowserGlue.sys.mjs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1237,18 +1237,24 @@ BrowserGlue.prototype = {
12371237
].getService(Ci.nsIToolkitProfileService);
12381238
if (
12391239
AppConstants.platform == "win" &&
1240-
Services.prefs.getBoolPref(launchOnLoginPref) &&
12411240
!profileSvc.startWithLastProfile
12421241
) {
12431242
// If we don't start with last profile, the user
12441243
// likely sees the profile selector on launch.
1244+
if (Services.prefs.getBoolPref(launchOnLoginPref)) {
1245+
Services.telemetry.setEventRecordingEnabled(
1246+
"launch_on_login",
1247+
true
1248+
);
1249+
Services.telemetry.recordEvent(
1250+
"launch_on_login",
1251+
"last_profile_disable",
1252+
"startup"
1253+
);
1254+
}
12451255
Services.prefs.setBoolPref(launchOnLoginPref, false);
1246-
Services.telemetry.setEventRecordingEnabled("launch_on_login", true);
1247-
Services.telemetry.recordEvent(
1248-
"launch_on_login",
1249-
"last_profile_disable:",
1250-
"startup"
1251-
);
1256+
// Only remove registry key, not shortcut here as we can assume
1257+
// if a user manually created a shortcut they want this behavior.
12521258
await lazy.WindowsLaunchOnLogin.removeLaunchOnLoginRegistryKey();
12531259
}
12541260
break;

browser/components/newtab/lib/ASRouterTargeting.jsm

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
3535
TargetingContext: "resource://messaging-system/targeting/Targeting.sys.mjs",
3636
TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs",
3737
TelemetrySession: "resource://gre/modules/TelemetrySession.sys.mjs",
38+
WindowsLaunchOnLogin: "resource://gre/modules/WindowsLaunchOnLogin.sys.mjs",
3839
});
3940

4041
XPCOMUtils.defineLazyModuleGetters(lazy, {
@@ -836,6 +837,13 @@ const TargetingGetters = {
836837
return QueryCache.getters.doesAppNeedPrivatePin.get();
837838
},
838839

840+
get launchOnLoginEnabled() {
841+
if (AppConstants.platform !== "win") {
842+
return false;
843+
}
844+
return lazy.WindowsLaunchOnLogin.getLaunchOnLoginEnabled();
845+
},
846+
839847
/**
840848
* Is this invocation running in background task mode?
841849
*

browser/components/newtab/lib/OnboardingMessageProvider.jsm

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -990,7 +990,8 @@ const BASE_MESSAGES = () => [
990990
lifetime: 1,
991991
},
992992
trigger: { id: "defaultBrowserCheck" },
993-
targeting: `source == 'newtab' && 'browser.startup.windowsLaunchOnLogin.disableLaunchOnLoginPrompt'|preferenceValue == false
993+
targeting: `source == 'newtab' && !launchOnLoginEnabled
994+
&& 'browser.startup.windowsLaunchOnLogin.disableLaunchOnLoginPrompt'|preferenceValue == false
994995
&& 'browser.startup.windowsLaunchOnLogin.enabled'|preferenceValue == true && isDefaultBrowser && !activeNotifications`,
995996
},
996997
{
@@ -1053,7 +1054,8 @@ const BASE_MESSAGES = () => [
10531054
lifetime: 1,
10541055
},
10551056
trigger: { id: "defaultBrowserCheck" },
1056-
targeting: `source == 'newtab' && 'browser.startup.windowsLaunchOnLogin.disableLaunchOnLoginPrompt'|preferenceValue == false
1057+
targeting: `source == 'newtab' && !launchOnLoginEnabled
1058+
&& 'browser.startup.windowsLaunchOnLogin.disableLaunchOnLoginPrompt'|preferenceValue == false
10571059
&& 'browser.startup.windowsLaunchOnLogin.enabled'|preferenceValue == true && isDefaultBrowser && !activeNotifications
10581060
&& messageImpressions.INFOBAR_LAUNCH_ON_LOGIN[messageImpressions.INFOBAR_LAUNCH_ON_LOGIN | length - 1]
10591061
&& messageImpressions.INFOBAR_LAUNCH_ON_LOGIN[messageImpressions.INFOBAR_LAUNCH_ON_LOGIN | length - 1] <

browser/components/preferences/main.js

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,15 @@ var gMainPane = {
423423
NimbusFeatures.windowsLaunchOnLogin.recordExposureEvent({
424424
once: true,
425425
});
426-
if (NimbusFeatures.windowsLaunchOnLogin.getVariable("enabled")) {
426+
// We do a check here for startWithLastProfile as we could
427+
// have disabled the pref for the user before they're ever
428+
// exposed to the experiment on a new profile.
429+
if (
430+
NimbusFeatures.windowsLaunchOnLogin.getVariable("enabled") &&
431+
Cc["@mozilla.org/toolkit/profile-service;1"].getService(
432+
Ci.nsIToolkitProfileService
433+
).startWithLastProfile
434+
) {
427435
document.getElementById("windowsLaunchOnLoginBox").hidden = false;
428436
}
429437
}
@@ -659,17 +667,8 @@ var gMainPane = {
659667
let launchOnLoginCheckbox = document.getElementById(
660668
"windowsLaunchOnLogin"
661669
);
662-
let registryName = WindowsLaunchOnLogin.getLaunchOnLoginRegistryName();
663-
WindowsLaunchOnLogin.withLaunchOnLoginRegistryKey(async wrk => {
664-
try {
665-
// Reflect registry key value in about:preferences
666-
launchOnLoginCheckbox.checked = wrk.hasValue(registryName);
667-
} catch (e) {
668-
// We should only end up here if we fail to open the registry
669-
console.error("Failed to open Windows registry", e);
670-
}
671-
});
672-
670+
launchOnLoginCheckbox.checked =
671+
WindowsLaunchOnLogin.getLaunchOnLoginEnabled();
673672
let approvedByWindows = WindowsLaunchOnLogin.getLaunchOnLoginApproved();
674673
launchOnLoginCheckbox.disabled = !approvedByWindows;
675674
document.getElementById("windowsLaunchOnLoginDisabledBox").hidden =
@@ -1635,7 +1634,7 @@ var gMainPane = {
16351634
startupPref.value = newValue;
16361635
},
16371636

1638-
onWindowsLaunchOnLoginChange(event) {
1637+
async onWindowsLaunchOnLoginChange(event) {
16391638
if (AppConstants.platform !== "win") {
16401639
return;
16411640
}
@@ -1647,8 +1646,9 @@ var gMainPane = {
16471646
true
16481647
);
16491648
} else {
1650-
// windowsLaunchOnLogin has been unchecked: delete registry key
1649+
// windowsLaunchOnLogin has been unchecked: delete registry key and shortcut
16511650
WindowsLaunchOnLogin.removeLaunchOnLoginRegistryKey();
1651+
await WindowsLaunchOnLogin.removeLaunchOnLoginShortcuts();
16521652
}
16531653
},
16541654

browser/components/shell/nsIWindowsShellService.idl

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,29 @@ interface nsIWindowsShellService : nsISupports
5252
in AString aDescription, in nsIFile aIconFile, in unsigned short aIconIndex,
5353
in AString aAppUserModelId, in AString aShortcutFolder, in AString aShortcutName);
5454

55+
/*
56+
* Searches the %USERPROFILE%\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup
57+
* folder and returns an array with the path of all shortcuts with a target matching the
58+
* current Firefox install location. The AUMID isn't required here as we are only looking
59+
* for the currently running binary, whether that's firefox.exe or the private browsing
60+
* proxy executable.
61+
*
62+
* It is possible to return an empty array if no shortcuts are found.
63+
*
64+
* @return An array of paths for all launch on login shortcuts.s
65+
*
66+
* @throws NS_ERROR_ABORT
67+
* if instance cannot be created.
68+
* @throws NS_ERROR_FILE_NOT_FOUND
69+
* if %USERPROFILE%\AppData\Roaming\ cannot be opened.
70+
* @throws NS_ERROR_FAILURE
71+
* if the executable file cannot be found.
72+
* @throws NS_ERROR_FILE_UNRECOGNIZED_PATH
73+
* if the executable file cannot be converted into a string.
74+
*/
75+
76+
Array<AString> getLaunchOnLoginShortcuts();
77+
5578
/*
5679
* Pin the current app to the taskbar. If aPrivateBrowsing is true, the
5780
* Private Browsing version of the app (with a different icon and launch

browser/components/shell/nsWindowsShellService.cpp

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141

4242
#include <windows.h>
4343
#include <shellapi.h>
44+
#include <strsafe.h>
4445
#include <propvarutil.h>
4546
#include <propkey.h>
4647

@@ -911,6 +912,103 @@ nsWindowsShellService::CreateShortcut(
911912
return NS_OK;
912913
}
913914

915+
NS_IMETHODIMP
916+
nsWindowsShellService::GetLaunchOnLoginShortcuts(
917+
nsTArray<nsString>& aShortcutPaths) {
918+
aShortcutPaths.Clear();
919+
920+
// Get AppData\\Roaming folder using a known folder ID
921+
RefPtr<IKnownFolderManager> fManager;
922+
RefPtr<IKnownFolder> roamingAppData;
923+
LPWSTR roamingAppDataW;
924+
nsString roamingAppDataNS;
925+
HRESULT hr =
926+
CoCreateInstance(CLSID_KnownFolderManager, nullptr, CLSCTX_INPROC_SERVER,
927+
IID_IKnownFolderManager, getter_AddRefs(fManager));
928+
if (FAILED(hr)) {
929+
return NS_ERROR_ABORT;
930+
}
931+
fManager->GetFolder(FOLDERID_RoamingAppData,
932+
roamingAppData.StartAssignment());
933+
hr = roamingAppData->GetPath(0, &roamingAppDataW);
934+
if (FAILED(hr)) {
935+
return NS_ERROR_FILE_NOT_FOUND;
936+
}
937+
938+
// Append startup folder to AppData\\Roaming
939+
roamingAppDataNS.Assign(roamingAppDataW);
940+
CoTaskMemFree(roamingAppDataW);
941+
nsString startupFolder =
942+
roamingAppDataNS +
943+
u"\\Microsoft\\Windows\\Start Menu\\Programs\\Startup"_ns;
944+
nsString startupFolderWildcard = startupFolder + u"\\*.lnk"_ns;
945+
946+
// Get known path for binary file for later comparison with shortcuts.
947+
// Returns lowercase file path which should be fine for Windows as all
948+
// directories and files are case-insensitive by default.
949+
RefPtr<nsIFile> binFile;
950+
nsString binPath;
951+
nsresult rv = XRE_GetBinaryPath(binFile.StartAssignment());
952+
if (FAILED(rv)) {
953+
return NS_ERROR_FAILURE;
954+
}
955+
rv = binFile->GetPath(binPath);
956+
if (FAILED(rv)) {
957+
return NS_ERROR_FILE_UNRECOGNIZED_PATH;
958+
}
959+
960+
// Check for if first file exists with a shortcut extension (.lnk)
961+
WIN32_FIND_DATAW ffd;
962+
HANDLE fileHandle = INVALID_HANDLE_VALUE;
963+
fileHandle = FindFirstFileW(startupFolderWildcard.get(), &ffd);
964+
if (fileHandle == INVALID_HANDLE_VALUE) {
965+
// This means that no files were found in the folder which
966+
// doesn't imply an error. Most of the time the user won't
967+
// have any shortcuts here.
968+
return NS_OK;
969+
}
970+
971+
do {
972+
// Extract shortcut target path from every
973+
// shortcut in the startup folder.
974+
nsString fileName(ffd.cFileName);
975+
RefPtr<IShellLinkW> link;
976+
RefPtr<IPersistFile> ppf;
977+
nsString target;
978+
target.SetLength(MAX_PATH);
979+
CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
980+
IID_IShellLinkW, getter_AddRefs(link));
981+
hr = link->QueryInterface(IID_IPersistFile, getter_AddRefs(ppf));
982+
if (NS_WARN_IF(FAILED(hr))) {
983+
continue;
984+
}
985+
nsString filePath = startupFolder + u"\\"_ns + fileName;
986+
hr = ppf->Load(filePath.get(), STGM_READ);
987+
if (NS_WARN_IF(FAILED(hr))) {
988+
continue;
989+
}
990+
hr = link->Resolve(nullptr, SLR_NO_UI);
991+
if (NS_WARN_IF(FAILED(hr))) {
992+
continue;
993+
}
994+
hr = link->GetPath(target.get(), MAX_PATH, nullptr, 0);
995+
if (NS_WARN_IF(FAILED(hr))) {
996+
continue;
997+
}
998+
999+
// If shortcut target matches known binary file value
1000+
// then add the path to the shortcut as a valid
1001+
// startup shortcut. This has to be a substring search as
1002+
// the user could have added unknown command line arguments
1003+
// to the shortcut.
1004+
if (_wcsnicmp(target.get(), binPath.get(), binPath.Length()) == 0) {
1005+
aShortcutPaths.AppendElement(filePath);
1006+
}
1007+
} while (FindNextFile(fileHandle, &ffd) != 0);
1008+
FindClose(fileHandle);
1009+
return NS_OK;
1010+
}
1011+
9141012
// Look for any installer-created shortcuts in the given location that match
9151013
// the given AUMID and EXE Path. If one is found, output its path.
9161014
//

toolkit/modules/WindowsLaunchOnLogin.sys.mjs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,30 @@ export var WindowsLaunchOnLogin = {
8484
}
8585
},
8686

87+
/**
88+
* Gets a list of all launch on login shortcuts in the
89+
* %USERNAME%\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup folder
90+
* that point to the current Firefox executable.
91+
*/
92+
getLaunchOnLoginShortcutList() {
93+
let shellService = Cc["@mozilla.org/browser/shell-service;1"].getService(
94+
Ci.nsIWindowsShellService
95+
);
96+
return shellService.getLaunchOnLoginShortcuts();
97+
},
98+
99+
/**
100+
* Safely removes all launch on login shortcuts in the
101+
* %USERNAME%\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup folder
102+
* that point to the current Firefox executable.
103+
*/
104+
async removeLaunchOnLoginShortcuts() {
105+
let shortcuts = this.getLaunchOnLoginShortcutList();
106+
for (let i = 0; i < shortcuts.length; i++) {
107+
await IOUtils.remove(shortcuts[i]);
108+
}
109+
},
110+
87111
/**
88112
* Checks if Windows launch on login was independently enabled or disabled
89113
* by the user in the Windows Startup Apps menu. The registry key that
@@ -119,6 +143,38 @@ export var WindowsLaunchOnLogin = {
119143
return true;
120144
},
121145

146+
/**
147+
* Checks if Windows launch on login has an existing registry key or user-created shortcut in
148+
* %USERNAME%\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup. The registry key that
149+
* stores this information should not be modified.
150+
*/
151+
getLaunchOnLoginEnabled() {
152+
let registryName = this.getLaunchOnLoginRegistryName();
153+
let regExists = false;
154+
let shortcutExists = false;
155+
this.withLaunchOnLoginRegistryKey(wrk => {
156+
try {
157+
// Start by checking if registry key exists.
158+
regExists = wrk.hasValue(registryName);
159+
} catch (e) {
160+
// We should only end up here if we fail to open the registry
161+
console.error("Failed to open Windows registry", e);
162+
}
163+
});
164+
if (!regExists) {
165+
shortcutExists = !!this.getLaunchOnLoginShortcutList().length;
166+
}
167+
// Even if a user disables it later on we want the launch on login
168+
// infobar to remain disabled as the user is aware of the option.
169+
if (regExists || shortcutExists) {
170+
Services.prefs.setBoolPref(
171+
"browser.startup.windowsLaunchOnLogin.disableLaunchOnLoginPrompt",
172+
true
173+
);
174+
}
175+
return regExists || shortcutExists;
176+
},
177+
122178
/**
123179
* Quotes a string for use as a single command argument, using Windows quoting
124180
* conventions.

0 commit comments

Comments
 (0)