Source code

Revision control

Copy as Markdown

Other Tools

/* 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/. */
/**
* "Launch on Login" is a Firefox feature automatically launches Firefox when the
* user logs in to Windows. The technical mechanism is simply writing a registry
* key to `Software\Microsoft\Windows\CurrentVersion\Run`, but there is an issue:
* when disabled in the Windows UI, additional registry keys are written under
* `Software\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved\Run`. Any
* keys stored here should be seen as a user override and should never be modified.
* When such keys are present, the launch on login feature should be considered
* disabled and not available from within Firefox. This module provides the
* functionality to access and modify these registry keys.
*/
export var WindowsLaunchOnLogin = {
/**
* Accepts another function as an argument and provides an open Windows
* launch on login registry key for the passed-in function to manipulate.
*
* @param func
* The function to use.
*/
async withLaunchOnLoginRegistryKey(func) {
let wrk = Cc["@mozilla.org/windows-registry-key;1"].createInstance(
Ci.nsIWindowsRegKey
);
wrk.open(
wrk.ROOT_KEY_CURRENT_USER,
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run",
wrk.ACCESS_ALL
);
try {
await func(wrk);
} finally {
wrk.close();
}
},
/**
* Safely creates a Windows launch on login registry key
*/
async createLaunchOnLoginRegistryKey() {
try {
await this.withLaunchOnLoginRegistryKey(async wrk => {
// Added launch option -os-autostart for telemetry
// Add quote marks around path to account for spaces in path
let autostartPath =
this.quoteString(Services.dirsvc.get("XREExeF", Ci.nsIFile).path) +
" -os-autostart";
try {
wrk.writeStringValue(
this.getLaunchOnLoginRegistryName(),
autostartPath
);
} catch (e) {
console.error("Could not write value to registry", e);
}
});
} catch (e) {
// We should only end up here if we fail to open the registry
console.error("Failed to open Windows registry", e);
}
},
/**
* Safely removes a Windows launch on login registry key
*/
async removeLaunchOnLoginRegistryKey() {
try {
await this.withLaunchOnLoginRegistryKey(async wrk => {
let registryName = this.getLaunchOnLoginRegistryName();
if (wrk.hasValue(registryName)) {
try {
wrk.removeValue(registryName);
} catch (e) {
console.error("Failed to remove Windows registry value", e);
}
}
});
} catch (e) {
// We should only end up here if we fail to open the registry
console.error("Failed to open Windows registry", e);
}
},
/**
* Gets a list of all launch on login shortcuts in the
* %USERNAME%\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup folder
* that point to the current Firefox executable.
*/
getLaunchOnLoginShortcutList() {
let shellService = Cc["@mozilla.org/browser/shell-service;1"].getService(
Ci.nsIWindowsShellService
);
return shellService.getLaunchOnLoginShortcuts();
},
/**
* Safely removes all launch on login shortcuts in the
* %USERNAME%\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup folder
* that point to the current Firefox executable.
*/
async removeLaunchOnLoginShortcuts() {
let shortcuts = this.getLaunchOnLoginShortcutList();
for (let i = 0; i < shortcuts.length; i++) {
await IOUtils.remove(shortcuts[i]);
}
},
/**
* Checks if Windows launch on login was independently enabled or disabled
* by the user in the Windows Startup Apps menu. The registry key that
* stores this information should not be modified.
*/
getLaunchOnLoginApproved() {
try {
let wrkApproved = Cc[
"@mozilla.org/windows-registry-key;1"
].createInstance(Ci.nsIWindowsRegKey);
wrkApproved.open(
wrkApproved.ROOT_KEY_CURRENT_USER,
"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StartupApproved\\Run",
wrkApproved.ACCESS_READ
);
let registryName = this.getLaunchOnLoginRegistryName();
if (!wrkApproved.hasValue(registryName)) {
wrkApproved.close();
return true;
}
// There's very little consistency with these binary values aside from if the first byte
// is even it's enabled and odd is disabled. There's also no published specification.
let approvedByWindows =
wrkApproved.readBinaryValue(registryName).charCodeAt(0).toString(16) %
2 ==
0;
wrkApproved.close();
return approvedByWindows;
} catch (e) {
// We should only end up here if we fail to open the registry
console.error("Failed to open Windows registry", e);
}
return true;
},
/**
* Checks if Windows launch on login has an existing registry key or user-created shortcut in
* %USERNAME%\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup. The registry key that
* stores this information should not be modified.
*/
getLaunchOnLoginEnabled() {
let registryName = this.getLaunchOnLoginRegistryName();
let regExists = false;
let shortcutExists = false;
this.withLaunchOnLoginRegistryKey(wrk => {
try {
// Start by checking if registry key exists.
regExists = wrk.hasValue(registryName);
} catch (e) {
// We should only end up here if we fail to open the registry
console.error("Failed to open Windows registry", e);
}
});
if (!regExists) {
shortcutExists = !!this.getLaunchOnLoginShortcutList().length;
}
// Even if a user disables it later on we want the launch on login
// infobar to remain disabled as the user is aware of the option.
if (regExists || shortcutExists) {
Services.prefs.setBoolPref(
"browser.startup.windowsLaunchOnLogin.disableLaunchOnLoginPrompt",
true
);
}
return regExists || shortcutExists;
},
/**
* Quotes a string for use as a single command argument, using Windows quoting
* conventions.
*
*
* @param {string} str
* The argument string to quote.
* @returns {string}
*/
quoteString(str) {
if (!/[\s"]/.test(str)) {
return str;
}
let escaped = str.replace(/(\\*)("|$)/g, (m0, m1, m2) => {
if (m2) {
m2 = `\\${m2}`;
}
return `${m1}${m1}${m2}`;
});
return `"${escaped}"`;
},
/**
* Generates a unique registry name for the current application
* like "Mozilla-Firefox-71AE18FE3142402B".
*/
getLaunchOnLoginRegistryName() {
let xreDirProvider = Cc[
"@mozilla.org/xre/directory-provider;1"
].createInstance(Ci.nsIXREDirProvider);
let registryName = `${Services.appinfo.vendor}-${
Services.appinfo.name
}-${xreDirProvider.getInstallHash()}`;
return registryName;
},
};