StartupOSIntegration.sys.mjs (10412B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; 6 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; 7 8 const PRIVATE_BROWSING_BINARY = "private_browsing.exe"; 9 // Index of Private Browsing icon in private_browsing.exe 10 // Must line up with IDI_PBICON_PB_PB_EXE in nsNativeAppSupportWin.h. 11 const PRIVATE_BROWSING_EXE_ICON_INDEX = 1; 12 const PREF_PRIVATE_BROWSING_SHORTCUT_CREATED = 13 "browser.privacySegmentation.createdShortcut"; 14 15 const lazy = {}; 16 17 XPCOMUtils.defineLazyServiceGetters(lazy, { 18 BrowserHandler: ["@mozilla.org/browser/clh;1", Ci.nsIBrowserHandler], 19 profileService: [ 20 "@mozilla.org/toolkit/profile-service;1", 21 Ci.nsIToolkitProfileService, 22 ], 23 }); 24 25 ChromeUtils.defineESModuleGetters(lazy, { 26 FirefoxBridgeExtensionUtils: 27 "resource:///modules/FirefoxBridgeExtensionUtils.sys.mjs", 28 PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", 29 ShellService: "moz-src:///browser/components/shell/ShellService.sys.mjs", 30 WindowsLaunchOnLogin: "resource://gre/modules/WindowsLaunchOnLogin.sys.mjs", 31 WindowsGPOParser: "resource://gre/modules/policies/WindowsGPOParser.sys.mjs", 32 }); 33 34 ChromeUtils.defineLazyGetter(lazy, "log", () => { 35 let { ConsoleAPI } = ChromeUtils.importESModule( 36 "resource://gre/modules/Console.sys.mjs" 37 ); 38 let consoleOptions = { 39 // tip: set maxLogLevel to "debug" and use lazy.log.debug() to create 40 // detailed messages during development. See LOG_LEVELS in Console.sys.mjs 41 // for details. 42 maxLogLevel: "error", 43 maxLogLevelPref: "browser.policies.loglevel", 44 prefix: "StartupOSIntegration.sys.mjs", 45 }; 46 return new ConsoleAPI(consoleOptions); 47 }); 48 49 function WindowsRegPoliciesGetter(wrk, root, regLocation) { 50 wrk.open(root, regLocation, wrk.ACCESS_READ); 51 let policies; 52 if (wrk.hasChild("Mozilla\\" + Services.appinfo.name)) { 53 policies = lazy.WindowsGPOParser.readPolicies(wrk, policies); 54 } 55 wrk.close(); 56 return policies; 57 } 58 59 export let StartupOSIntegration = { 60 isPrivateBrowsingAllowedInRegistry() { 61 // If there is an attempt to open Private Browsing before 62 // EnterprisePolicies are initialized the Windows registry 63 // can be checked to determine if it is enabled 64 if (Services.policies.status > Ci.nsIEnterprisePolicies.UNINITIALIZED) { 65 // Yield to policies engine if initialized 66 let privateAllowed = Services.policies.isAllowed("privatebrowsing"); 67 lazy.log.debug( 68 `Yield to initialized policies engine: Private Browsing Allowed = ${privateAllowed}` 69 ); 70 return privateAllowed; 71 } 72 if (AppConstants.platform !== "win") { 73 // Not using Windows so no registry, return true 74 lazy.log.debug( 75 "AppConstants.platform is not 'win': Private Browsing allowed" 76 ); 77 return true; 78 } 79 // If all other checks fail only then do we check registry 80 let wrk = Cc["@mozilla.org/windows-registry-key;1"].createInstance( 81 Ci.nsIWindowsRegKey 82 ); 83 let regLocation = "SOFTWARE\\Policies"; 84 let userPolicies, machinePolicies; 85 // Only check HKEY_LOCAL_MACHINE if not in testing 86 if (!Cu.isInAutomation) { 87 machinePolicies = WindowsRegPoliciesGetter( 88 wrk, 89 wrk.ROOT_KEY_LOCAL_MACHINE, 90 regLocation 91 ); 92 } 93 // Check machine policies before checking user policies 94 // HKEY_LOCAL_MACHINE supersedes HKEY_CURRENT_USER so only check 95 // HKEY_CURRENT_USER if the registry key is not present in 96 // HKEY_LOCAL_MACHINE at all 97 if (machinePolicies && "DisablePrivateBrowsing" in machinePolicies) { 98 lazy.log.debug( 99 `DisablePrivateBrowsing in HKEY_LOCAL_MACHINE is ${machinePolicies.DisablePrivateBrowsing}` 100 ); 101 return !(machinePolicies.DisablePrivateBrowsing === 1); 102 } 103 userPolicies = WindowsRegPoliciesGetter( 104 wrk, 105 wrk.ROOT_KEY_CURRENT_USER, 106 regLocation 107 ); 108 if (userPolicies && "DisablePrivateBrowsing" in userPolicies) { 109 lazy.log.debug( 110 `DisablePrivateBrowsing in HKEY_CURRENT_USER is ${userPolicies.DisablePrivateBrowsing}` 111 ); 112 return !(userPolicies.DisablePrivateBrowsing === 1); 113 } 114 // Private browsing allowed if no registry entry exists 115 lazy.log.debug( 116 "No DisablePrivateBrowsing registry entry: Private Browsing allowed" 117 ); 118 return true; 119 }, 120 121 checkForLaunchOnLogin() { 122 // We only support launch on login on Windows at the moment. 123 if (AppConstants.platform != "win") { 124 return; 125 } 126 let launchOnLoginPref = "browser.startup.windowsLaunchOnLogin.enabled"; 127 if (!lazy.profileService.startWithLastProfile) { 128 // If we don't start with last profile, the user 129 // likely sees the profile selector on launch. 130 if (Services.prefs.getBoolPref(launchOnLoginPref)) { 131 Glean.launchOnLogin.lastProfileDisableStartup.record(); 132 // Disable launch on login messaging if we are disabling the 133 // feature. 134 Services.prefs.setBoolPref( 135 "browser.startup.windowsLaunchOnLogin.disableLaunchOnLoginPrompt", 136 true 137 ); 138 } 139 // To reduce confusion when running multiple Gecko profiles, 140 // delete launch on login shortcuts and registry keys so that 141 // users are not presented with the outdated profile selector 142 // dialog. 143 lazy.WindowsLaunchOnLogin.removeLaunchOnLogin(); 144 } 145 }, 146 147 // Note: currently only invoked on Windows and macOS. 148 async onStartupIdle() { 149 // Catch and report exceptions, including async rejections: 150 let safeCall = async fn => { 151 try { 152 await fn(); 153 } catch (ex) { 154 console.error(ex); 155 } 156 }; 157 // Note that we explicitly do not await calls to `safeCall` as 158 // these individual calls are independent and can run without 159 // waiting for each other. 160 161 // Currently we only support Firefox bridge on Windows and macOS. 162 safeCall(() => this.ensureBridgeRegistered()); 163 164 if (AppConstants.platform == "win") { 165 if (Services.sysinfo.getProperty("hasWinPackageId")) { 166 safeCall(() => this.maybePinMSIXToStartMenu()); 167 } 168 safeCall(() => this.ensurePrivateBrowsingShortcutExists()); 169 } 170 }, 171 172 async ensureBridgeRegistered() { 173 if (!Services.prefs.getBoolPref("browser.firefoxbridge.enabled", false)) { 174 return; 175 } 176 let { defaultProfile, currentProfile } = lazy.profileService; 177 if (defaultProfile && currentProfile == defaultProfile) { 178 await lazy.FirefoxBridgeExtensionUtils.ensureRegistered(); 179 } else { 180 lazy.log.debug( 181 "FirefoxBridgeExtensionUtils failed to register due to non-default current profile." 182 ); 183 } 184 }, 185 186 // Silently pin Firefox to the start menu on first run when using MSIX on a 187 // new profile. 188 // If not first run, check if Firefox is no longer pinned to the Start Menu 189 // when it previously was and send telemetry. 190 async maybePinMSIXToStartMenu() { 191 if (!Services.sysinfo.getProperty("hasWinPackageId")) { 192 return; 193 } 194 if ( 195 lazy.BrowserHandler.firstRunProfile && 196 (await lazy.ShellService.doesAppNeedStartMenuPin()) 197 ) { 198 await lazy.ShellService.pinToStartMenu(); 199 return; 200 } 201 await lazy.ShellService.recordWasPreviouslyPinnedToStartMenu(); 202 }, 203 204 // Ensure a Private Browsing Shortcut exists. This is needed in case 205 // a user tries to use Windows functionality to pin our Private Browsing 206 // mode icon to the Taskbar (eg: the "Pin to Taskbar" context menu item). 207 // This is also created by the installer, but it's possible that a user 208 // has removed it, or is running out of a zip build. The consequences of not 209 // having a Shortcut for this are that regular Firefox will be pinned instead 210 // of the Private Browsing version -- so it's quite important we do our best 211 // to make sure one is available. 212 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1762994 for additional 213 // background. 214 async ensurePrivateBrowsingShortcutExists() { 215 if ( 216 // If the feature is disabled, don't do this. 217 !Services.prefs.getBoolPref( 218 "browser.privateWindowSeparation.enabled", 219 true 220 ) || 221 // We don't want a shortcut if it's been disabled, eg: by enterprise policy. 222 !lazy.PrivateBrowsingUtils.enabled || 223 // Private Browsing shortcuts for packaged builds come with the package, 224 // if they exist at all. We shouldn't try to create our own. 225 Services.sysinfo.getProperty("hasWinPackageId") || 226 // If we've ever done this successfully before, don't try again. The 227 // user may have deleted the shortcut, and we don't want to force it 228 // on them. 229 Services.prefs.getBoolPref(PREF_PRIVATE_BROWSING_SHORTCUT_CREATED, false) 230 ) { 231 return; 232 } 233 234 let shellService = Cc["@mozilla.org/browser/shell-service;1"].getService( 235 Ci.nsIWindowsShellService 236 ); 237 let winTaskbar = Cc["@mozilla.org/windows-taskbar;1"].getService( 238 Ci.nsIWinTaskbar 239 ); 240 241 if ( 242 !(await shellService.hasPinnableShortcut( 243 winTaskbar.defaultPrivateGroupId, 244 true 245 )) 246 ) { 247 let appdir = Services.dirsvc.get("GreD", Ci.nsIFile); 248 let exe = appdir.clone(); 249 exe.append(PRIVATE_BROWSING_BINARY); 250 let strings = new Localization( 251 ["branding/brand.ftl", "browser/browser.ftl"], 252 true 253 ); 254 let [desc] = await strings.formatValues([ 255 "private-browsing-shortcut-text-2", 256 ]); 257 await shellService.createShortcut( 258 exe, 259 [], 260 desc, 261 exe, 262 // The code we're calling indexes from 0 instead of 1 263 PRIVATE_BROWSING_EXE_ICON_INDEX - 1, 264 winTaskbar.defaultPrivateGroupId, 265 "Programs", 266 desc + ".lnk", 267 appdir 268 ); 269 } 270 // We always set this as long as no exception has been thrown. This 271 // ensure that it is `true` both if we created one because it didn't 272 // exist, or if it already existed (most likely because it was created 273 // by the installer). This avoids the need to call `hasPinnableShortcut` 274 // again, which necessarily does pointless I/O. 275 Services.prefs.setBoolPref(PREF_PRIVATE_BROWSING_SHORTCUT_CREATED, true); 276 }, 277 };