/* 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/. */ "use strict"; var EXPORTED_SYMBOLS = ["ShellService"]; const { AppConstants } = ChromeUtils.import( "resource://gre/modules/AppConstants.jsm" ); const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { XPCOMUtils } = ChromeUtils.import( "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 * which is especially true for Linux with its many different desktop * environments. */ get canSetDesktopBackground() { if (AppConstants.platform == "win" || AppConstants.platform == "macosx") { return true; } if (AppConstants.platform == "linux") { if (this.shellService) { let linuxShellService = this.shellService.QueryInterface( Ci.nsIGNOMEShellService ); return linuxShellService.canSetDesktopBackground; } } return false; }, isDefaultBrowserOptOut() { if (AppConstants.platform == "win") { let optOutValue = WindowsRegistry.readRegKey( Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, "Software\\Mozilla\\Firefox", "DefaultBrowserOptOut" ); WindowsRegistry.removeRegKey( Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, "Software\\Mozilla\\Firefox", "DefaultBrowserOptOut" ); if (optOutValue == "True") { Services.prefs.setBoolPref("browser.shell.checkDefaultBrowser", false); return true; } } return false; }, /** * Used to determine whether or not to show a "Set Default Browser" * query dialog. This attribute is true if the application is starting * up and "browser.shell.checkDefaultBrowser" is true, otherwise it * is false. */ _checkedThisSession: false, get shouldCheckDefaultBrowser() { // If we've already checked, the browser has been started and this is a // new window open, and we don't want to check again. if (this._checkedThisSession) { return false; } if (!Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser")) { return false; } if (this.isDefaultBrowserOptOut()) { return false; } return true; }, set shouldCheckDefaultBrowser(shouldCheck) { Services.prefs.setBoolPref( "browser.shell.checkDefaultBrowser", !!shouldCheck ); }, isDefaultBrowser(startupCheck, forAllTypes) { // If this is the first browser window, maintain internal state that we've // checked this session (so that subsequent window opens don't show the // default browser dialog). if (startupCheck) { 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.platform == "win" && Services.prefs.getBoolPref( "browser.shell.setDefaultBrowserUserChoice", false ) && Services.sysinfo.getProperty("version") == "10.0" ) { // 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+. // Windows 8 is version 6.2. let version = Services.sysinfo.getProperty("version"); claimAllTypes = parseFloat(version) < 6.2; } catch (ex) {} } try { ShellService.setDefaultBrowser(claimAllTypes, false); } catch (ex) { setAsDefaultError = true; Cu.reportError(ex); } // Here BROWSER_IS_USER_DEFAULT and BROWSER_SET_USER_DEFAULT_ERROR appear // to be inverse of each other, but that is only because this function is // called when the browser is set as the default. During startup we record // the BROWSER_IS_USER_DEFAULT value without recording BROWSER_SET_USER_DEFAULT_ERROR. Services.telemetry .getHistogramById("BROWSER_IS_USER_DEFAULT") .add(!setAsDefaultError); Services.telemetry .getHistogramById("BROWSER_SET_DEFAULT_ERROR") .add(setAsDefaultError); }, async doesAppNeedPin() { // Currently this only works on certain Windows versions. try { // First check if we can even pin the app where an exception means no. this.shellService .QueryInterface(Ci.nsIWindowsShellService) .checkPinCurrentAppToTaskbar(); // Then check if we're already pinned. return !(await this.shellService.isCurrentAppPinnedToTaskbarAsync()); } catch (ex) { return false; } }, async pinToTaskbar() { if (await this.doesAppNeedPin()) { try { this.shellService.pinCurrentAppToTaskbar(); } catch (ex) { Cu.reportError(ex); } } }, }; XPCOMUtils.defineLazyServiceGetter( ShellServiceInternal, "shellService", "@mozilla.org/browser/shell-service;1", Ci.nsIShellService ); /** * The external API exported by this module. */ var ShellService = new Proxy(ShellServiceInternal, { get(target, name) { if (name in target) { return target[name]; } if (target.shellService) { return target.shellService[name]; } Services.console.logStringMessage( `${name} not found in ShellService: ${target.shellService}` ); return undefined; }, });