diff options
Diffstat (limited to 'browser/components/shell')
25 files changed, 3374 insertions, 0 deletions
diff --git a/browser/components/shell/ShellService.jsm b/browser/components/shell/ShellService.jsm new file mode 100644 index 000000000..2a3400b60 --- /dev/null +++ b/browser/components/shell/ShellService.jsm @@ -0,0 +1,114 @@ +/* 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"; + +this.EXPORTED_SYMBOLS = ["ShellService"]; + +const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; + +Cu.import("resource://gre/modules/AppConstants.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry", + "resource://gre/modules/WindowsRegistry.jsm"); + +/** + * 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; + }, + + /** + * 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 (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 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(startupCheck, forAllTypes); + } + return false; + } +}; + +XPCOMUtils.defineLazyServiceGetter(ShellServiceInternal, "shellService", + "@mozilla.org/browser/shell-service;1", Ci.nsIShellService); + +/** + * The external API exported by this module. + */ +this.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; + } +}); diff --git a/browser/components/shell/content/setDesktopBackground.js b/browser/components/shell/content/setDesktopBackground.js new file mode 100644 index 000000000..53cc70db0 --- /dev/null +++ b/browser/components/shell/content/setDesktopBackground.js @@ -0,0 +1,214 @@ +/* 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/. */ + +Components.utils.import("resource://gre/modules/AppConstants.jsm"); + +var Ci = Components.interfaces; + +var gSetBackground = { + _position : AppConstants.platform == "macosx" ? "STRETCH" : "", + _backgroundColor : AppConstants.platform != "macosx" ? 0 : undefined, + _screenWidth : 0, + _screenHeight : 0, + _image : null, + _canvas : null, + + get _shell() + { + return Components.classes["@mozilla.org/browser/shell-service;1"] + .getService(Ci.nsIShellService); + }, + + load: function () + { + this._canvas = document.getElementById("screen"); + this._screenWidth = screen.width; + this._screenHeight = screen.height; + if (AppConstants.platform == "macosx") { + document.documentElement.getButton("accept").hidden = true; + } + if (this._screenWidth / this._screenHeight >= 1.6) + document.getElementById("monitor").setAttribute("aspectratio", "16:10"); + + if (AppConstants.platform == "win") { + // Hide fill + fit options if < Win7 since they don't work. + var version = Components.classes["@mozilla.org/system-info;1"] + .getService(Ci.nsIPropertyBag2) + .getProperty("version"); + var isWindows7OrHigher = (parseFloat(version) >= 6.1); + if (!isWindows7OrHigher) { + document.getElementById("fillPosition").hidden = true; + document.getElementById("fitPosition").hidden = true; + } + } + + // make sure that the correct dimensions will be used + setTimeout(function(self) { + self.init(window.arguments[0]); + }, 0, this); + }, + + init: function (aImage) + { + this._image = aImage; + + // set the size of the coordinate space + this._canvas.width = this._canvas.clientWidth; + this._canvas.height = this._canvas.clientHeight; + + var ctx = this._canvas.getContext("2d"); + ctx.scale(this._canvas.clientWidth / this._screenWidth, this._canvas.clientHeight / this._screenHeight); + + if (AppConstants.platform != "macosx") { + this._initColor(); + } else { + // Make sure to reset the button state in case the user has already + // set an image as their desktop background. + var setDesktopBackground = document.getElementById("setDesktopBackground"); + setDesktopBackground.hidden = false; + var bundle = document.getElementById("backgroundBundle"); + setDesktopBackground.label = bundle.getString("DesktopBackgroundSet"); + setDesktopBackground.disabled = false; + + document.getElementById("showDesktopPreferences").hidden = true; + } + this.updatePosition(); + }, + + setDesktopBackground: function () + { + if (AppConstants.platform != "macosx") { + document.persist("menuPosition", "value"); + this._shell.desktopBackgroundColor = this._hexStringToLong(this._backgroundColor); + } else { + Components.classes["@mozilla.org/observer-service;1"] + .getService(Ci.nsIObserverService) + .addObserver(this, "shell:desktop-background-changed", false); + + var bundle = document.getElementById("backgroundBundle"); + var setDesktopBackground = document.getElementById("setDesktopBackground"); + setDesktopBackground.disabled = true; + setDesktopBackground.label = bundle.getString("DesktopBackgroundDownloading"); + } + this._shell.setDesktopBackground(this._image, + Ci.nsIShellService["BACKGROUND_" + this._position]); + }, + + updatePosition: function () + { + var ctx = this._canvas.getContext("2d"); + ctx.clearRect(0, 0, this._screenWidth, this._screenHeight); + + if (AppConstants.platform != "macosx") { + this._position = document.getElementById("menuPosition").value; + } + + switch (this._position) { + case "TILE": + ctx.save(); + ctx.fillStyle = ctx.createPattern(this._image, "repeat"); + ctx.fillRect(0, 0, this._screenWidth, this._screenHeight); + ctx.restore(); + break; + case "STRETCH": + ctx.drawImage(this._image, 0, 0, this._screenWidth, this._screenHeight); + break; + case "CENTER": { + let x = (this._screenWidth - this._image.naturalWidth) / 2; + let y = (this._screenHeight - this._image.naturalHeight) / 2; + ctx.drawImage(this._image, x, y); + break; + } + case "FILL": { + // Try maxing width first, overflow height. + let widthRatio = this._screenWidth / this._image.naturalWidth; + let width = this._image.naturalWidth * widthRatio; + let height = this._image.naturalHeight * widthRatio; + if (height < this._screenHeight) { + // Height less than screen, max height and overflow width. + let heightRatio = this._screenHeight / this._image.naturalHeight; + width = this._image.naturalWidth * heightRatio; + height = this._image.naturalHeight * heightRatio; + } + let x = (this._screenWidth - width) / 2; + let y = (this._screenHeight - height) / 2; + ctx.drawImage(this._image, x, y, width, height); + break; + } + case "FIT": { + // Try maxing width first, top and bottom borders. + let widthRatio = this._screenWidth / this._image.naturalWidth; + let width = this._image.naturalWidth * widthRatio; + let height = this._image.naturalHeight * widthRatio; + let x = 0; + let y = (this._screenHeight - height) / 2; + if (height > this._screenHeight) { + // Height overflow, maximise height, side borders. + let heightRatio = this._screenHeight / this._image.naturalHeight; + width = this._image.naturalWidth * heightRatio; + height = this._image.naturalHeight * heightRatio; + x = (this._screenWidth - width) / 2; + y = 0; + } + ctx.drawImage(this._image, x, y, width, height); + break; + } + } + } +}; + +if (AppConstants.platform != "macosx") { + gSetBackground["_initColor"] = function () + { + var color = this._shell.desktopBackgroundColor; + + const rMask = 4294901760; + const gMask = 65280; + const bMask = 255; + var r = (color & rMask) >> 16; + var g = (color & gMask) >> 8; + var b = (color & bMask); + this.updateColor(this._rgbToHex(r, g, b)); + + var colorpicker = document.getElementById("desktopColor"); + colorpicker.color = this._backgroundColor; + }; + + gSetBackground["updateColor"] = function (aColor) + { + this._backgroundColor = aColor; + this._canvas.style.backgroundColor = aColor; + }; + + // Converts a color string in the format "#RRGGBB" to an integer. + gSetBackground["_hexStringToLong"] = function (aString) + { + return parseInt(aString.substring(1, 3), 16) << 16 | + parseInt(aString.substring(3, 5), 16) << 8 | + parseInt(aString.substring(5, 7), 16); + }; + + gSetBackground["_rgbToHex"] = function (aR, aG, aB) + { + return "#" + [aR, aG, aB].map(aInt => aInt.toString(16).replace(/^(.)$/, "0$1")) + .join("").toUpperCase(); + }; +} else { + gSetBackground["observe"] = function (aSubject, aTopic, aData) + { + if (aTopic == "shell:desktop-background-changed") { + document.getElementById("setDesktopBackground").hidden = true; + document.getElementById("showDesktopPreferences").hidden = false; + + Components.classes["@mozilla.org/observer-service;1"] + .getService(Ci.nsIObserverService) + .removeObserver(this, "shell:desktop-background-changed"); + } + }; + + gSetBackground["showDesktopPrefs"] = function() + { + this._shell.openApplication(Ci.nsIMacShellService.APPLICATION_DESKTOP); + }; +} diff --git a/browser/components/shell/content/setDesktopBackground.xul b/browser/components/shell/content/setDesktopBackground.xul new file mode 100644 index 000000000..d7d4079e3 --- /dev/null +++ b/browser/components/shell/content/setDesktopBackground.xul @@ -0,0 +1,84 @@ +<?xml version="1.0"?> <!-- -*- Mode: HTML -*- --> + +# 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/. + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<?xml-stylesheet href="chrome://browser/skin/setDesktopBackground.css" type="text/css"?> + +<!DOCTYPE dialog SYSTEM "chrome://browser/locale/setDesktopBackground.dtd"> + +#ifdef XP_MACOSX +<?xul-overlay href="chrome://browser/content/macBrowserOverlay.xul"?> +#endif + +<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + windowtype="Shell:SetDesktopBackground" +#ifndef XP_MACOSX + buttons="accept,cancel" +#else + buttons="accept" +#endif + buttonlabelaccept="&setDesktopBackground.title;" + onload="gSetBackground.load();" + ondialogaccept="gSetBackground.setDesktopBackground();" + title="&setDesktopBackground.title;" + style="width: 30em;"> + + <stringbundle id="backgroundBundle" + src="chrome://browser/locale/shellservice.properties"/> + <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/> + <script type="application/javascript" src="chrome://browser/content/setDesktopBackground.js"/> + <script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/> + +#ifndef XP_MACOSX + <hbox align="center"> + <label value="&position.label;"/> + <menulist id="menuPosition" + label="&position.label;" + oncommand="gSetBackground.updatePosition();"> + <menupopup> + <menuitem label="¢er.label;" value="CENTER"/> + <menuitem label="&tile.label;" value="TILE"/> + <menuitem label="&stretch.label;" value="STRETCH"/> + <menuitem label="&fill.label;" value="FILL" id="fillPosition"/> + <menuitem label="&fit.label;" value="FIT" id="fitPosition"/> + </menupopup> + </menulist> + <spacer flex="1"/> + <label value="&color.label;"/> + <colorpicker id="desktopColor" + type="button" + onchange="gSetBackground.updateColor(this.color);"/> + </hbox> +#endif + <groupbox align="center"> + <caption label="&preview.label;"/> + <stack> + <!-- if width and height are not present, they default to 300x150 and stretch the stack --> + <html:canvas id="screen" width="1" height="1"/> + <image id="monitor"/> + </stack> + </groupbox> + +#ifdef XP_MACOSX + <separator/> + + <hbox align="right"> + <button id="setDesktopBackground" + label="&setDesktopBackground.title;" + oncommand="gSetBackground.setDesktopBackground();"/> + <button id="showDesktopPreferences" + label="&openDesktopPrefs.label;" + oncommand="gSetBackground.showDesktopPrefs();" + hidden="true"/> + </hbox> +#endif + +#ifdef XP_MACOSX +#include ../../../base/content/browserMountPoints.inc +#endif + +</dialog> diff --git a/browser/components/shell/jar.mn b/browser/components/shell/jar.mn new file mode 100644 index 000000000..1f33b5d56 --- /dev/null +++ b/browser/components/shell/jar.mn @@ -0,0 +1,7 @@ +# 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/. + +browser.jar: +* content/browser/setDesktopBackground.xul (content/setDesktopBackground.xul) + content/browser/setDesktopBackground.js (content/setDesktopBackground.js) diff --git a/browser/components/shell/moz.build b/browser/components/shell/moz.build new file mode 100644 index 000000000..7a605de5f --- /dev/null +++ b/browser/components/shell/moz.build @@ -0,0 +1,62 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini'] +BROWSER_CHROME_MANIFESTS += ['test/browser.ini'] + +JAR_MANIFESTS += ['jar.mn'] + +XPIDL_SOURCES += [ + 'nsIShellService.idl', +] + +if CONFIG['OS_ARCH'] == 'WINNT': + XPIDL_SOURCES += [ + 'nsIWindowsShellService.idl', + ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + XPIDL_SOURCES += [ + 'nsIMacShellService.idl', + ] +elif 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']: + XPIDL_SOURCES += [ + 'nsIGNOMEShellService.idl', + ] + +XPIDL_MODULE = 'shellservice' + +if CONFIG['OS_ARCH'] == 'WINNT': + SOURCES += [ + 'nsWindowsShellService.cpp', + ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + SOURCES += [ + 'nsMacShellService.cpp', + ] +elif 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']: + SOURCES += [ + 'nsGNOMEShellService.cpp', + ] + +if SOURCES: + FINAL_LIBRARY = 'browsercomps' + +EXTRA_COMPONENTS += [ + 'nsSetDefaultBrowser.js', + 'nsSetDefaultBrowser.manifest', +] + +EXTRA_JS_MODULES += [ + 'ShellService.jsm', +] + +for var in ('MOZ_APP_NAME', 'MOZ_APP_VERSION'): + DEFINES[var] = '"%s"' % CONFIG[var] + +CXXFLAGS += CONFIG['TK_CFLAGS'] + +with Files('**'): + BUG_COMPONENT = ('Firefox', 'Shell Integration') diff --git a/browser/components/shell/nsGNOMEShellService.cpp b/browser/components/shell/nsGNOMEShellService.cpp new file mode 100644 index 000000000..f6c2613d4 --- /dev/null +++ b/browser/components/shell/nsGNOMEShellService.cpp @@ -0,0 +1,638 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "mozilla/ArrayUtils.h" + +#include "nsCOMPtr.h" +#include "nsGNOMEShellService.h" +#include "nsShellService.h" +#include "nsIServiceManager.h" +#include "nsIFile.h" +#include "nsIProperties.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIPrefService.h" +#include "prenv.h" +#include "nsString.h" +#include "nsIGConfService.h" +#include "nsIGIOService.h" +#include "nsIGSettingsService.h" +#include "nsIStringBundle.h" +#include "nsIOutputStream.h" +#include "nsIProcess.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsIDOMHTMLImageElement.h" +#include "nsIImageLoadingContent.h" +#include "imgIRequest.h" +#include "imgIContainer.h" +#include "mozilla/Sprintf.h" +#if defined(MOZ_WIDGET_GTK) +#include "nsIImageToPixbuf.h" +#endif +#include "nsXULAppAPI.h" + +#include <glib.h> +#include <glib-object.h> +#include <gtk/gtk.h> +#include <gdk/gdk.h> +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <limits.h> +#include <stdlib.h> + +using namespace mozilla; + +struct ProtocolAssociation +{ + const char *name; + bool essential; +}; + +struct MimeTypeAssociation +{ + const char *mimeType; + const char *extensions; +}; + +static const ProtocolAssociation appProtocols[] = { + { "http", true }, + { "https", true }, + { "ftp", false }, + { "chrome", false } +}; + +static const MimeTypeAssociation appTypes[] = { + { "text/html", "htm html shtml" }, + { "application/xhtml+xml", "xhtml xht" } +}; + +// GConf registry key constants +#define DG_BACKGROUND "/desktop/gnome/background" + +#define kDesktopImageKey DG_BACKGROUND "/picture_filename" +#define kDesktopOptionsKey DG_BACKGROUND "/picture_options" +#define kDesktopDrawBGKey DG_BACKGROUND "/draw_background" +#define kDesktopColorKey DG_BACKGROUND "/primary_color" + +#define kDesktopBGSchema "org.gnome.desktop.background" +#define kDesktopImageGSKey "picture-uri" +#define kDesktopOptionGSKey "picture-options" +#define kDesktopDrawBGGSKey "draw-background" +#define kDesktopColorGSKey "primary-color" + +nsresult +nsGNOMEShellService::Init() +{ + nsresult rv; + + // GConf, GSettings or GIO _must_ be available, or we do not allow + // CreateInstance to succeed. + + nsCOMPtr<nsIGConfService> gconf = do_GetService(NS_GCONFSERVICE_CONTRACTID); + nsCOMPtr<nsIGIOService> giovfs = + do_GetService(NS_GIOSERVICE_CONTRACTID); + nsCOMPtr<nsIGSettingsService> gsettings = + do_GetService(NS_GSETTINGSSERVICE_CONTRACTID); + + if (!gconf && !giovfs && !gsettings) + return NS_ERROR_NOT_AVAILABLE; + + // Check G_BROKEN_FILENAMES. If it's set, then filenames in glib use + // the locale encoding. If it's not set, they use UTF-8. + mUseLocaleFilenames = PR_GetEnv("G_BROKEN_FILENAMES") != nullptr; + + if (GetAppPathFromLauncher()) + return NS_OK; + + nsCOMPtr<nsIProperties> dirSvc + (do_GetService("@mozilla.org/file/directory_service;1")); + NS_ENSURE_TRUE(dirSvc, NS_ERROR_NOT_AVAILABLE); + + nsCOMPtr<nsIFile> appPath; + rv = dirSvc->Get(XRE_EXECUTABLE_FILE, NS_GET_IID(nsIFile), + getter_AddRefs(appPath)); + NS_ENSURE_SUCCESS(rv, rv); + + return appPath->GetNativePath(mAppPath); +} + +NS_IMPL_ISUPPORTS(nsGNOMEShellService, nsIGNOMEShellService, nsIShellService) + +bool +nsGNOMEShellService::GetAppPathFromLauncher() +{ + gchar *tmp; + + const char *launcher = PR_GetEnv("MOZ_APP_LAUNCHER"); + if (!launcher) + return false; + + if (g_path_is_absolute(launcher)) { + mAppPath = launcher; + tmp = g_path_get_basename(launcher); + gchar *fullpath = g_find_program_in_path(tmp); + if (fullpath && mAppPath.Equals(fullpath)) + mAppIsInPath = true; + g_free(fullpath); + } else { + tmp = g_find_program_in_path(launcher); + if (!tmp) + return false; + mAppPath = tmp; + mAppIsInPath = true; + } + + g_free(tmp); + return true; +} + +bool +nsGNOMEShellService::KeyMatchesAppName(const char *aKeyValue) const +{ + + gchar *commandPath; + if (mUseLocaleFilenames) { + gchar *nativePath = g_filename_from_utf8(aKeyValue, -1, + nullptr, nullptr, nullptr); + if (!nativePath) { + NS_ERROR("Error converting path to filesystem encoding"); + return false; + } + + commandPath = g_find_program_in_path(nativePath); + g_free(nativePath); + } else { + commandPath = g_find_program_in_path(aKeyValue); + } + + if (!commandPath) + return false; + + bool matches = mAppPath.Equals(commandPath); + g_free(commandPath); + return matches; +} + +bool +nsGNOMEShellService::CheckHandlerMatchesAppName(const nsACString &handler) const +{ + gint argc; + gchar **argv; + nsAutoCString command(handler); + + // The string will be something of the form: [/path/to/]browser "%s" + // We want to remove all of the parameters and get just the binary name. + + if (g_shell_parse_argv(command.get(), &argc, &argv, nullptr) && argc > 0) { + command.Assign(argv[0]); + g_strfreev(argv); + } + + if (!KeyMatchesAppName(command.get())) + return false; // the handler is set to another app + + return true; +} + +NS_IMETHODIMP +nsGNOMEShellService::IsDefaultBrowser(bool aStartupCheck, + bool aForAllTypes, + bool* aIsDefaultBrowser) +{ + *aIsDefaultBrowser = false; + + nsCOMPtr<nsIGConfService> gconf = do_GetService(NS_GCONFSERVICE_CONTRACTID); + nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID); + + bool enabled; + nsAutoCString handler; + nsCOMPtr<nsIGIOMimeApp> gioApp; + + for (unsigned int i = 0; i < ArrayLength(appProtocols); ++i) { + if (!appProtocols[i].essential) + continue; + + if (gconf) { + handler.Truncate(); + gconf->GetAppForProtocol(nsDependentCString(appProtocols[i].name), + &enabled, handler); + + if (!CheckHandlerMatchesAppName(handler) || !enabled) + return NS_OK; // the handler is disabled or set to another app + } + + if (giovfs) { + handler.Truncate(); + giovfs->GetAppForURIScheme(nsDependentCString(appProtocols[i].name), + getter_AddRefs(gioApp)); + if (!gioApp) + return NS_OK; + + gioApp->GetCommand(handler); + + if (!CheckHandlerMatchesAppName(handler)) + return NS_OK; // the handler is set to another app + } + } + + *aIsDefaultBrowser = true; + + return NS_OK; +} + +NS_IMETHODIMP +nsGNOMEShellService::SetDefaultBrowser(bool aClaimAllTypes, + bool aForAllUsers) +{ +#ifdef DEBUG + if (aForAllUsers) + NS_WARNING("Setting the default browser for all users is not yet supported"); +#endif + + nsCOMPtr<nsIGConfService> gconf = do_GetService(NS_GCONFSERVICE_CONTRACTID); + nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID); + if (gconf) { + nsAutoCString appKeyValue; + if (mAppIsInPath) { + // mAppPath is in the users path, so use only the basename as the launcher + gchar *tmp = g_path_get_basename(mAppPath.get()); + appKeyValue = tmp; + g_free(tmp); + } else { + appKeyValue = mAppPath; + } + + appKeyValue.AppendLiteral(" %s"); + + for (unsigned int i = 0; i < ArrayLength(appProtocols); ++i) { + if (appProtocols[i].essential || aClaimAllTypes) { + gconf->SetAppForProtocol(nsDependentCString(appProtocols[i].name), + appKeyValue); + } + } + } + + if (giovfs) { + nsresult rv; + nsCOMPtr<nsIStringBundleService> bundleService = + do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIStringBundle> brandBundle; + rv = bundleService->CreateBundle(BRAND_PROPERTIES, getter_AddRefs(brandBundle)); + NS_ENSURE_SUCCESS(rv, rv); + + nsString brandShortName; + brandBundle->GetStringFromName(u"brandShortName", + getter_Copies(brandShortName)); + + // use brandShortName as the application id. + NS_ConvertUTF16toUTF8 id(brandShortName); + nsCOMPtr<nsIGIOMimeApp> appInfo; + rv = giovfs->CreateAppFromCommand(mAppPath, + id, + getter_AddRefs(appInfo)); + NS_ENSURE_SUCCESS(rv, rv); + + // set handler for the protocols + for (unsigned int i = 0; i < ArrayLength(appProtocols); ++i) { + if (appProtocols[i].essential || aClaimAllTypes) { + appInfo->SetAsDefaultForURIScheme(nsDependentCString(appProtocols[i].name)); + } + } + + // set handler for .html and xhtml files and MIME types: + if (aClaimAllTypes) { + // Add mime types for html, xhtml extension and set app to just created appinfo. + for (unsigned int i = 0; i < ArrayLength(appTypes); ++i) { + appInfo->SetAsDefaultForMimeType(nsDependentCString(appTypes[i].mimeType)); + appInfo->SetAsDefaultForFileExtensions(nsDependentCString(appTypes[i].extensions)); + } + } + } + + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (prefs) { + (void) prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, true); + // Reset the number of times the dialog should be shown + // before it is silenced. + (void) prefs->SetIntPref(PREF_DEFAULTBROWSERCHECKCOUNT, 0); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsGNOMEShellService::GetCanSetDesktopBackground(bool* aResult) +{ + // setting desktop background is currently only supported + // for Gnome or desktops using the same GSettings and GConf keys + const char* gnomeSession = getenv("GNOME_DESKTOP_SESSION_ID"); + if (gnomeSession) { + *aResult = true; + } else { + *aResult = false; + } + + return NS_OK; +} + +static nsresult +WriteImage(const nsCString& aPath, imgIContainer* aImage) +{ +#if !defined(MOZ_WIDGET_GTK) + return NS_ERROR_NOT_AVAILABLE; +#else + nsCOMPtr<nsIImageToPixbuf> imgToPixbuf = + do_GetService("@mozilla.org/widget/image-to-gdk-pixbuf;1"); + if (!imgToPixbuf) + return NS_ERROR_NOT_AVAILABLE; + + GdkPixbuf* pixbuf = imgToPixbuf->ConvertImageToPixbuf(aImage); + if (!pixbuf) + return NS_ERROR_NOT_AVAILABLE; + + gboolean res = gdk_pixbuf_save(pixbuf, aPath.get(), "png", nullptr, nullptr); + + g_object_unref(pixbuf); + return res ? NS_OK : NS_ERROR_FAILURE; +#endif +} + +NS_IMETHODIMP +nsGNOMEShellService::SetDesktopBackground(nsIDOMElement* aElement, + int32_t aPosition) +{ + nsresult rv; + nsCOMPtr<nsIImageLoadingContent> imageContent = do_QueryInterface(aElement, &rv); + if (!imageContent) return rv; + + // get the image container + nsCOMPtr<imgIRequest> request; + rv = imageContent->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, + getter_AddRefs(request)); + if (!request) return rv; + nsCOMPtr<imgIContainer> container; + rv = request->GetImage(getter_AddRefs(container)); + if (!container) return rv; + + // Set desktop wallpaper filling style + nsAutoCString options; + if (aPosition == BACKGROUND_TILE) + options.AssignLiteral("wallpaper"); + else if (aPosition == BACKGROUND_STRETCH) + options.AssignLiteral("stretched"); + else if (aPosition == BACKGROUND_FILL) + options.AssignLiteral("zoom"); + else if (aPosition == BACKGROUND_FIT) + options.AssignLiteral("scaled"); + else + options.AssignLiteral("centered"); + + // Write the background file to the home directory. + nsAutoCString filePath(PR_GetEnv("HOME")); + + // get the product brand name from localized strings + nsString brandName; + nsCID bundleCID = NS_STRINGBUNDLESERVICE_CID; + nsCOMPtr<nsIStringBundleService> bundleService(do_GetService(bundleCID)); + if (bundleService) { + nsCOMPtr<nsIStringBundle> brandBundle; + rv = bundleService->CreateBundle(BRAND_PROPERTIES, + getter_AddRefs(brandBundle)); + if (NS_SUCCEEDED(rv) && brandBundle) { + rv = brandBundle->GetStringFromName(u"brandShortName", + getter_Copies(brandName)); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + // build the file name + filePath.Append('/'); + filePath.Append(NS_ConvertUTF16toUTF8(brandName)); + filePath.AppendLiteral("_wallpaper.png"); + + // write the image to a file in the home dir + rv = WriteImage(filePath, container); + NS_ENSURE_SUCCESS(rv, rv); + + // Try GSettings first. If we don't have GSettings or the right schema, fall back + // to using GConf instead. Note that if GSettings works ok, the changes get + // mirrored to GConf by the gsettings->gconf bridge in gnome-settings-daemon + nsCOMPtr<nsIGSettingsService> gsettings = + do_GetService(NS_GSETTINGSSERVICE_CONTRACTID); + if (gsettings) { + nsCOMPtr<nsIGSettingsCollection> background_settings; + gsettings->GetCollectionForSchema( + NS_LITERAL_CSTRING(kDesktopBGSchema), getter_AddRefs(background_settings)); + if (background_settings) { + gchar *file_uri = g_filename_to_uri(filePath.get(), nullptr, nullptr); + if (!file_uri) + return NS_ERROR_FAILURE; + + background_settings->SetString(NS_LITERAL_CSTRING(kDesktopOptionGSKey), + options); + + background_settings->SetString(NS_LITERAL_CSTRING(kDesktopImageGSKey), + nsDependentCString(file_uri)); + g_free(file_uri); + background_settings->SetBoolean(NS_LITERAL_CSTRING(kDesktopDrawBGGSKey), + true); + return rv; + } + } + + // if the file was written successfully, set it as the system wallpaper + nsCOMPtr<nsIGConfService> gconf = do_GetService(NS_GCONFSERVICE_CONTRACTID); + + if (gconf) { + gconf->SetString(NS_LITERAL_CSTRING(kDesktopOptionsKey), options); + + // Set the image to an empty string first to force a refresh + // (since we could be writing a new image on top of an existing + // Firefox_wallpaper.png and nautilus doesn't monitor the file for changes) + gconf->SetString(NS_LITERAL_CSTRING(kDesktopImageKey), + EmptyCString()); + + gconf->SetString(NS_LITERAL_CSTRING(kDesktopImageKey), filePath); + gconf->SetBool(NS_LITERAL_CSTRING(kDesktopDrawBGKey), true); + } + + return rv; +} + +#define COLOR_16_TO_8_BIT(_c) ((_c) >> 8) +#define COLOR_8_TO_16_BIT(_c) ((_c) << 8 | (_c)) + +NS_IMETHODIMP +nsGNOMEShellService::GetDesktopBackgroundColor(uint32_t *aColor) +{ + nsCOMPtr<nsIGSettingsService> gsettings = + do_GetService(NS_GSETTINGSSERVICE_CONTRACTID); + nsCOMPtr<nsIGSettingsCollection> background_settings; + nsAutoCString background; + + if (gsettings) { + gsettings->GetCollectionForSchema( + NS_LITERAL_CSTRING(kDesktopBGSchema), getter_AddRefs(background_settings)); + if (background_settings) { + background_settings->GetString(NS_LITERAL_CSTRING(kDesktopColorGSKey), + background); + } + } + + if (!background_settings) { + nsCOMPtr<nsIGConfService> gconf = do_GetService(NS_GCONFSERVICE_CONTRACTID); + if (gconf) + gconf->GetString(NS_LITERAL_CSTRING(kDesktopColorKey), background); + } + + if (background.IsEmpty()) { + *aColor = 0; + return NS_OK; + } + + GdkColor color; + gboolean success = gdk_color_parse(background.get(), &color); + + NS_ENSURE_TRUE(success, NS_ERROR_FAILURE); + + *aColor = COLOR_16_TO_8_BIT(color.red) << 16 | + COLOR_16_TO_8_BIT(color.green) << 8 | + COLOR_16_TO_8_BIT(color.blue); + return NS_OK; +} + +static void +ColorToCString(uint32_t aColor, nsCString& aResult) +{ + // The #rrrrggggbbbb format is used to match gdk_color_to_string() + aResult.SetLength(13); + char *buf = aResult.BeginWriting(); + if (!buf) + return; + + uint16_t red = COLOR_8_TO_16_BIT((aColor >> 16) & 0xff); + uint16_t green = COLOR_8_TO_16_BIT((aColor >> 8) & 0xff); + uint16_t blue = COLOR_8_TO_16_BIT(aColor & 0xff); + + snprintf(buf, 14, "#%04x%04x%04x", red, green, blue); +} + +NS_IMETHODIMP +nsGNOMEShellService::SetDesktopBackgroundColor(uint32_t aColor) +{ + NS_ASSERTION(aColor <= 0xffffff, "aColor has extra bits"); + nsAutoCString colorString; + ColorToCString(aColor, colorString); + + nsCOMPtr<nsIGSettingsService> gsettings = + do_GetService(NS_GSETTINGSSERVICE_CONTRACTID); + if (gsettings) { + nsCOMPtr<nsIGSettingsCollection> background_settings; + gsettings->GetCollectionForSchema( + NS_LITERAL_CSTRING(kDesktopBGSchema), getter_AddRefs(background_settings)); + if (background_settings) { + background_settings->SetString(NS_LITERAL_CSTRING(kDesktopColorGSKey), + colorString); + return NS_OK; + } + } + + nsCOMPtr<nsIGConfService> gconf = do_GetService(NS_GCONFSERVICE_CONTRACTID); + + if (gconf) { + gconf->SetString(NS_LITERAL_CSTRING(kDesktopColorKey), colorString); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsGNOMEShellService::OpenApplication(int32_t aApplication) +{ + nsAutoCString scheme; + if (aApplication == APPLICATION_MAIL) + scheme.AssignLiteral("mailto"); + else if (aApplication == APPLICATION_NEWS) + scheme.AssignLiteral("news"); + else + return NS_ERROR_NOT_AVAILABLE; + + nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID); + if (giovfs) { + nsCOMPtr<nsIGIOMimeApp> gioApp; + giovfs->GetAppForURIScheme(scheme, getter_AddRefs(gioApp)); + if (gioApp) + return gioApp->Launch(EmptyCString()); + } + + nsCOMPtr<nsIGConfService> gconf = do_GetService(NS_GCONFSERVICE_CONTRACTID); + if (!gconf) + return NS_ERROR_FAILURE; + + bool enabled; + nsAutoCString appCommand; + gconf->GetAppForProtocol(scheme, &enabled, appCommand); + + if (!enabled) + return NS_ERROR_FAILURE; + + // XXX we don't currently handle launching a terminal window. + // If the handler requires a terminal, bail. + bool requiresTerminal; + gconf->HandlerRequiresTerminal(scheme, &requiresTerminal); + if (requiresTerminal) + return NS_ERROR_FAILURE; + + // Perform shell argument expansion + int argc; + char **argv; + if (!g_shell_parse_argv(appCommand.get(), &argc, &argv, nullptr)) + return NS_ERROR_FAILURE; + + char **newArgv = new char*[argc + 1]; + int newArgc = 0; + + // Run through the list of arguments. Copy all of them to the new + // argv except for %s, which we skip. + for (int i = 0; i < argc; ++i) { + if (strcmp(argv[i], "%s") != 0) + newArgv[newArgc++] = argv[i]; + } + + newArgv[newArgc] = nullptr; + + gboolean err = g_spawn_async(nullptr, newArgv, nullptr, G_SPAWN_SEARCH_PATH, + nullptr, nullptr, nullptr, nullptr); + + g_strfreev(argv); + delete[] newArgv; + + return err ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsGNOMEShellService::OpenApplicationWithURI(nsIFile* aApplication, const nsACString& aURI) +{ + nsresult rv; + nsCOMPtr<nsIProcess> process = + do_CreateInstance("@mozilla.org/process/util;1", &rv); + if (NS_FAILED(rv)) + return rv; + + rv = process->Init(aApplication); + if (NS_FAILED(rv)) + return rv; + + const nsCString spec(aURI); + const char* specStr = spec.get(); + return process->Run(false, &specStr, 1); +} + +NS_IMETHODIMP +nsGNOMEShellService::GetDefaultFeedReader(nsIFile** _retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} diff --git a/browser/components/shell/nsGNOMEShellService.h b/browser/components/shell/nsGNOMEShellService.h new file mode 100644 index 000000000..b3ef1a918 --- /dev/null +++ b/browser/components/shell/nsGNOMEShellService.h @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nsgnomeshellservice_h____ +#define nsgnomeshellservice_h____ + +#include "nsIGNOMEShellService.h" +#include "nsString.h" +#include "mozilla/Attributes.h" + +class nsGNOMEShellService final : public nsIGNOMEShellService +{ +public: + nsGNOMEShellService() : mAppIsInPath(false) { } + + NS_DECL_ISUPPORTS + NS_DECL_NSISHELLSERVICE + NS_DECL_NSIGNOMESHELLSERVICE + + nsresult Init(); + +private: + ~nsGNOMEShellService() {} + + bool KeyMatchesAppName(const char *aKeyValue) const; + bool CheckHandlerMatchesAppName(const nsACString& handler) const; + + bool GetAppPathFromLauncher(); + bool mUseLocaleFilenames; + nsCString mAppPath; + bool mAppIsInPath; +}; + +#endif // nsgnomeshellservice_h____ diff --git a/browser/components/shell/nsIGNOMEShellService.idl b/browser/components/shell/nsIGNOMEShellService.idl new file mode 100644 index 000000000..842ce5e8a --- /dev/null +++ b/browser/components/shell/nsIGNOMEShellService.idl @@ -0,0 +1,19 @@ +/* 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/. */ + +#include "nsIShellService.idl" + +[scriptable, uuid(2ce5c803-edcd-443d-98eb-ceba86d02d13)] +interface nsIGNOMEShellService : nsIShellService +{ + /** + * 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. + */ + readonly attribute boolean canSetDesktopBackground; +}; + diff --git a/browser/components/shell/nsIMacShellService.idl b/browser/components/shell/nsIMacShellService.idl new file mode 100644 index 000000000..6a532bbd0 --- /dev/null +++ b/browser/components/shell/nsIMacShellService.idl @@ -0,0 +1,15 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsIShellService.idl" + +[scriptable, uuid(387fdc80-0077-4b60-a0d9-d9e80a83ba64)] +interface nsIMacShellService : nsIShellService +{ + const long APPLICATION_KEYCHAIN_ACCESS = 2; + const long APPLICATION_NETWORK = 3; + const long APPLICATION_DESKTOP = 4; +}; + diff --git a/browser/components/shell/nsIShellService.idl b/browser/components/shell/nsIShellService.idl new file mode 100644 index 000000000..3e7e94b00 --- /dev/null +++ b/browser/components/shell/nsIShellService.idl @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsISupports.idl" + +interface nsIDOMElement; +interface nsIFile; + +[scriptable, uuid(2d1a95e4-5bd8-4eeb-b0a8-c1455fd2a357)] +interface nsIShellService : nsISupports +{ + /** + * Determines whether or not Firefox is the "Default Browser." + * This is simply whether or not Firefox is registered to handle + * http links. + * + * @param aStartupCheck true if this is the check being performed + * by the first browser window at startup, + * false otherwise. + * @param aForAllTypes true if the check should be made for HTTP and HTML. + * false if the check should be made for HTTP only. + * This parameter may be ignored on some platforms. + */ + boolean isDefaultBrowser(in boolean aStartupCheck, + [optional] in boolean aForAllTypes); + + /** + * Registers Firefox as the "Default Browser." + * + * @param aClaimAllTypes Register Firefox as the handler for + * additional protocols (ftp, chrome etc) + * and web documents (.html, .xhtml etc). + * @param aForAllUsers Whether or not Firefox should attempt + * to become the default browser for all + * users on a multi-user system. + */ + void setDefaultBrowser(in boolean aClaimAllTypes, in boolean aForAllUsers); + + /** + * Flags for positioning/sizing of the Desktop Background image. + */ + const long BACKGROUND_TILE = 1; + const long BACKGROUND_STRETCH = 2; + const long BACKGROUND_CENTER = 3; + const long BACKGROUND_FILL = 4; + const long BACKGROUND_FIT = 5; + + /** + * Sets the desktop background image using either the HTML <IMG> + * element supplied or the background image of the element supplied. + * + * @param aImageElement Either a HTML <IMG> element or an element with + * a background image from which to source the + * background image. + * @param aPosition How to place the image on the desktop + */ + void setDesktopBackground(in nsIDOMElement aElement, in long aPosition); + + /** + * Constants identifying applications that can be opened with + * openApplication. + */ + const long APPLICATION_MAIL = 0; + const long APPLICATION_NEWS = 1; + + /** + * Opens the application specified. If more than one application of the + * given type is available on the system, the default or "preferred" + * application is used. + */ + void openApplication(in long aApplication); + + /** + * The desktop background color, visible when no background image is + * used, or if the background image is centered and does not fill the + * entire screen. A rgb value, where (r << 16 | g << 8 | b) + */ + attribute unsigned long desktopBackgroundColor; + + /** + * Opens an application with a specific URI to load. + * @param application + * The application file (or bundle directory, on OS X) + * @param uri + * The uri to be loaded by the application + */ + void openApplicationWithURI(in nsIFile aApplication, in ACString aURI); + + /** + * The default system handler for web feeds + */ + readonly attribute nsIFile defaultFeedReader; +}; diff --git a/browser/components/shell/nsIWindowsShellService.idl b/browser/components/shell/nsIWindowsShellService.idl new file mode 100644 index 000000000..57ed37055 --- /dev/null +++ b/browser/components/shell/nsIWindowsShellService.idl @@ -0,0 +1,17 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsIShellService.idl" + +[scriptable, uuid(f8a26b94-49e5-4441-8fbc-315e0b4f22ef)] +interface nsIWindowsShellService : nsIShellService +{ + /** + * Provides the shell service an opportunity to do some Win7+ shortcut + * maintenance needed on initial startup of the browser. + */ + void shortcutMaintenance(); +}; + diff --git a/browser/components/shell/nsMacShellService.cpp b/browser/components/shell/nsMacShellService.cpp new file mode 100644 index 000000000..48db4896b --- /dev/null +++ b/browser/components/shell/nsMacShellService.cpp @@ -0,0 +1,434 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsDirectoryServiceDefs.h" +#include "nsIDOMElement.h" +#include "nsIDOMHTMLImageElement.h" +#include "nsIImageLoadingContent.h" +#include "nsIDocument.h" +#include "nsIContent.h" +#include "nsILocalFileMac.h" +#include "nsIObserverService.h" +#include "nsIPrefService.h" +#include "nsIServiceManager.h" +#include "nsIStringBundle.h" +#include "nsIURL.h" +#include "nsIWebBrowserPersist.h" +#include "nsMacShellService.h" +#include "nsIProperties.h" +#include "nsServiceManagerUtils.h" +#include "nsShellService.h" +#include "nsString.h" +#include "nsIDocShell.h" +#include "nsILoadContext.h" + +#include <CoreFoundation/CoreFoundation.h> +#include <ApplicationServices/ApplicationServices.h> + +#define NETWORK_PREFPANE NS_LITERAL_CSTRING("/System/Library/PreferencePanes/Network.prefPane") +#define DESKTOP_PREFPANE NS_LITERAL_CSTRING("/System/Library/PreferencePanes/DesktopScreenEffectsPref.prefPane") + +#define SAFARI_BUNDLE_IDENTIFIER "com.apple.Safari" + +NS_IMPL_ISUPPORTS(nsMacShellService, nsIMacShellService, nsIShellService, nsIWebProgressListener) + +NS_IMETHODIMP +nsMacShellService::IsDefaultBrowser(bool aStartupCheck, + bool aForAllTypes, + bool* aIsDefaultBrowser) +{ + *aIsDefaultBrowser = false; + + CFStringRef firefoxID = ::CFBundleGetIdentifier(::CFBundleGetMainBundle()); + if (!firefoxID) { + // CFBundleGetIdentifier is expected to return nullptr only if the specified + // bundle doesn't have a bundle identifier in its plist. In this case, that + // means a failure, since our bundle does have an identifier. + return NS_ERROR_FAILURE; + } + + // Get the default http handler's bundle ID (or nullptr if it has not been + // explicitly set) + CFStringRef defaultBrowserID = ::LSCopyDefaultHandlerForURLScheme(CFSTR("http")); + if (defaultBrowserID) { + *aIsDefaultBrowser = ::CFStringCompare(firefoxID, defaultBrowserID, 0) == kCFCompareEqualTo; + ::CFRelease(defaultBrowserID); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMacShellService::SetDefaultBrowser(bool aClaimAllTypes, bool aForAllUsers) +{ + // Note: We don't support aForAllUsers on Mac OS X. + + CFStringRef firefoxID = ::CFBundleGetIdentifier(::CFBundleGetMainBundle()); + if (!firefoxID) { + return NS_ERROR_FAILURE; + } + + if (::LSSetDefaultHandlerForURLScheme(CFSTR("http"), firefoxID) != noErr) { + return NS_ERROR_FAILURE; + } + if (::LSSetDefaultHandlerForURLScheme(CFSTR("https"), firefoxID) != noErr) { + return NS_ERROR_FAILURE; + } + + if (aClaimAllTypes) { + if (::LSSetDefaultHandlerForURLScheme(CFSTR("ftp"), firefoxID) != noErr) { + return NS_ERROR_FAILURE; + } + if (::LSSetDefaultRoleHandlerForContentType(kUTTypeHTML, kLSRolesAll, firefoxID) != noErr) { + return NS_ERROR_FAILURE; + } + } + + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (prefs) { + (void) prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, true); + // Reset the number of times the dialog should be shown + // before it is silenced. + (void) prefs->SetIntPref(PREF_DEFAULTBROWSERCHECKCOUNT, 0); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMacShellService::SetDesktopBackground(nsIDOMElement* aElement, + int32_t aPosition) +{ + // Note: We don't support aPosition on OS X. + + // Get the image URI: + nsresult rv; + nsCOMPtr<nsIImageLoadingContent> imageContent = do_QueryInterface(aElement, + &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIURI> imageURI; + rv = imageContent->GetCurrentURI(getter_AddRefs(imageURI)); + NS_ENSURE_SUCCESS(rv, rv); + + // We need the referer URI for nsIWebBrowserPersist::saveURI + nsCOMPtr<nsIContent> content = do_QueryInterface(aElement, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsIURI *docURI = content->OwnerDoc()->GetDocumentURI(); + if (!docURI) + return NS_ERROR_FAILURE; + + // Get the desired image file name + nsCOMPtr<nsIURL> imageURL(do_QueryInterface(imageURI)); + if (!imageURL) { + // XXXmano (bug 300293): Non-URL images (e.g. the data: protocol) are not + // yet supported. What filename should we take here? + return NS_ERROR_NOT_IMPLEMENTED; + } + + nsAutoCString fileName; + imageURL->GetFileName(fileName); + nsCOMPtr<nsIProperties> fileLocator + (do_GetService("@mozilla.org/file/directory_service;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + // Get the current user's "Pictures" folder (That's ~/Pictures): + fileLocator->Get(NS_OSX_PICTURE_DOCUMENTS_DIR, NS_GET_IID(nsIFile), + getter_AddRefs(mBackgroundFile)); + if (!mBackgroundFile) + return NS_ERROR_OUT_OF_MEMORY; + + nsAutoString fileNameUnicode; + CopyUTF8toUTF16(fileName, fileNameUnicode); + + // and add the imgage file name itself: + mBackgroundFile->Append(fileNameUnicode); + + // Download the image; the desktop background will be set in OnStateChange() + nsCOMPtr<nsIWebBrowserPersist> wbp + (do_CreateInstance("@mozilla.org/embedding/browser/nsWebBrowserPersist;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t flags = nsIWebBrowserPersist::PERSIST_FLAGS_NO_CONVERSION | + nsIWebBrowserPersist::PERSIST_FLAGS_REPLACE_EXISTING_FILES | + nsIWebBrowserPersist::PERSIST_FLAGS_FROM_CACHE; + + wbp->SetPersistFlags(flags); + wbp->SetProgressListener(this); + + nsCOMPtr<nsILoadContext> loadContext; + nsCOMPtr<nsISupports> container = content->OwnerDoc()->GetContainer(); + nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(container); + if (docShell) { + loadContext = do_QueryInterface(docShell); + } + + return wbp->SaveURI(imageURI, nullptr, + docURI, content->OwnerDoc()->GetReferrerPolicy(), + nullptr, nullptr, + mBackgroundFile, loadContext); +} + +NS_IMETHODIMP +nsMacShellService::OnProgressChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + int32_t aCurSelfProgress, + int32_t aMaxSelfProgress, + int32_t aCurTotalProgress, + int32_t aMaxTotalProgress) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMacShellService::OnLocationChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsIURI* aLocation, + uint32_t aFlags) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMacShellService::OnStatusChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsresult aStatus, + const char16_t* aMessage) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMacShellService::OnSecurityChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aState) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMacShellService::OnStateChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aStateFlags, + nsresult aStatus) +{ + if (aStateFlags & STATE_STOP) { + nsCOMPtr<nsIObserverService> os(do_GetService("@mozilla.org/observer-service;1")); + if (os) + os->NotifyObservers(nullptr, "shell:desktop-background-changed", nullptr); + + bool exists = false; + mBackgroundFile->Exists(&exists); + if (!exists) + return NS_OK; + + nsAutoCString nativePath; + mBackgroundFile->GetNativePath(nativePath); + + AEDesc tAEDesc = { typeNull, nil }; + OSErr err = noErr; + AliasHandle aliasHandle = nil; + FSRef pictureRef; + OSStatus status; + + // Convert the path into a FSRef + status = ::FSPathMakeRef((const UInt8*)nativePath.get(), &pictureRef, + nullptr); + if (status == noErr) { + err = ::FSNewAlias(nil, &pictureRef, &aliasHandle); + if (err == noErr && aliasHandle == nil) + err = paramErr; + + if (err == noErr) { + // We need the descriptor (based on the picture file reference) + // for the 'Set Desktop Picture' apple event. + char handleState = ::HGetState((Handle)aliasHandle); + ::HLock((Handle)aliasHandle); + err = ::AECreateDesc(typeAlias, *aliasHandle, + GetHandleSize((Handle)aliasHandle), &tAEDesc); + // unlock the alias handler + ::HSetState((Handle)aliasHandle, handleState); + ::DisposeHandle((Handle)aliasHandle); + } + if (err == noErr) { + AppleEvent tAppleEvent; + OSType sig = 'MACS'; + AEBuildError tAEBuildError; + // Create a 'Set Desktop Pictue' Apple Event + err = ::AEBuildAppleEvent(kAECoreSuite, kAESetData, typeApplSignature, + &sig, sizeof(OSType), kAutoGenerateReturnID, + kAnyTransactionID, &tAppleEvent, &tAEBuildError, + "'----':'obj '{want:type (prop),form:prop" \ + ",seld:type('dpic'),from:'null'()},data:(@)", + &tAEDesc); + if (err == noErr) { + AppleEvent reply = { typeNull, nil }; + // Sent the event we built, the reply event isn't necessary + err = ::AESend(&tAppleEvent, &reply, kAENoReply, kAENormalPriority, + kNoTimeOut, nil, nil); + ::AEDisposeDesc(&tAppleEvent); + } + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMacShellService::OpenApplication(int32_t aApplication) +{ + nsresult rv = NS_OK; + CFURLRef appURL = nil; + OSStatus err = noErr; + + switch (aApplication) { + case nsIShellService::APPLICATION_MAIL: + { + CFURLRef tempURL = ::CFURLCreateWithString(kCFAllocatorDefault, + CFSTR("mailto:"), nullptr); + err = ::LSGetApplicationForURL(tempURL, kLSRolesAll, nullptr, &appURL); + ::CFRelease(tempURL); + } + break; + case nsIShellService::APPLICATION_NEWS: + { + CFURLRef tempURL = ::CFURLCreateWithString(kCFAllocatorDefault, + CFSTR("news:"), nullptr); + err = ::LSGetApplicationForURL(tempURL, kLSRolesAll, nullptr, &appURL); + ::CFRelease(tempURL); + } + break; + case nsIMacShellService::APPLICATION_KEYCHAIN_ACCESS: + err = ::LSGetApplicationForInfo('APPL', 'kcmr', nullptr, kLSRolesAll, + nullptr, &appURL); + break; + case nsIMacShellService::APPLICATION_NETWORK: + { + nsCOMPtr<nsIFile> lf; + rv = NS_NewNativeLocalFile(NETWORK_PREFPANE, true, getter_AddRefs(lf)); + NS_ENSURE_SUCCESS(rv, rv); + bool exists; + lf->Exists(&exists); + if (!exists) + return NS_ERROR_FILE_NOT_FOUND; + return lf->Launch(); + } + case nsIMacShellService::APPLICATION_DESKTOP: + { + nsCOMPtr<nsIFile> lf; + rv = NS_NewNativeLocalFile(DESKTOP_PREFPANE, true, getter_AddRefs(lf)); + NS_ENSURE_SUCCESS(rv, rv); + bool exists; + lf->Exists(&exists); + if (!exists) + return NS_ERROR_FILE_NOT_FOUND; + return lf->Launch(); + } + } + + if (appURL && err == noErr) { + err = ::LSOpenCFURLRef(appURL, nullptr); + rv = err != noErr ? NS_ERROR_FAILURE : NS_OK; + + ::CFRelease(appURL); + } + + return rv; +} + +NS_IMETHODIMP +nsMacShellService::GetDesktopBackgroundColor(uint32_t *aColor) +{ + // This method and |SetDesktopBackgroundColor| has no meaning on Mac OS X. + // The mac desktop preferences UI uses pictures for the few solid colors it + // supports. + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsMacShellService::SetDesktopBackgroundColor(uint32_t aColor) +{ + // This method and |GetDesktopBackgroundColor| has no meaning on Mac OS X. + // The mac desktop preferences UI uses pictures for the few solid colors it + // supports. + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsMacShellService::OpenApplicationWithURI(nsIFile* aApplication, const nsACString& aURI) +{ + nsCOMPtr<nsILocalFileMac> lfm(do_QueryInterface(aApplication)); + CFURLRef appURL; + nsresult rv = lfm->GetCFURL(&appURL); + if (NS_FAILED(rv)) + return rv; + + const nsCString spec(aURI); + const UInt8* uriString = (const UInt8*)spec.get(); + CFURLRef uri = ::CFURLCreateWithBytes(nullptr, uriString, aURI.Length(), + kCFStringEncodingUTF8, nullptr); + if (!uri) + return NS_ERROR_OUT_OF_MEMORY; + + CFArrayRef uris = ::CFArrayCreate(nullptr, (const void**)&uri, 1, nullptr); + if (!uris) { + ::CFRelease(uri); + return NS_ERROR_OUT_OF_MEMORY; + } + + LSLaunchURLSpec launchSpec; + launchSpec.appURL = appURL; + launchSpec.itemURLs = uris; + launchSpec.passThruParams = nullptr; + launchSpec.launchFlags = kLSLaunchDefaults; + launchSpec.asyncRefCon = nullptr; + + OSErr err = ::LSOpenFromURLSpec(&launchSpec, nullptr); + + ::CFRelease(uris); + ::CFRelease(uri); + + return err != noErr ? NS_ERROR_FAILURE : NS_OK; +} + +NS_IMETHODIMP +nsMacShellService::GetDefaultFeedReader(nsIFile** _retval) +{ + nsresult rv = NS_ERROR_FAILURE; + *_retval = nullptr; + + CFStringRef defaultHandlerID = ::LSCopyDefaultHandlerForURLScheme(CFSTR("feed")); + if (!defaultHandlerID) { + defaultHandlerID = ::CFStringCreateWithCString(kCFAllocatorDefault, + SAFARI_BUNDLE_IDENTIFIER, + kCFStringEncodingASCII); + } + + CFURLRef defaultHandlerURL = nullptr; + OSStatus status = ::LSFindApplicationForInfo(kLSUnknownCreator, + defaultHandlerID, + nullptr, // inName + nullptr, // outAppRef + &defaultHandlerURL); + + if (status == noErr && defaultHandlerURL) { + nsCOMPtr<nsILocalFileMac> defaultReader = + do_CreateInstance("@mozilla.org/file/local;1", &rv); + if (NS_SUCCEEDED(rv)) { + rv = defaultReader->InitWithCFURL(defaultHandlerURL); + if (NS_SUCCEEDED(rv)) { + NS_ADDREF(*_retval = defaultReader); + rv = NS_OK; + } + } + + ::CFRelease(defaultHandlerURL); + } + + ::CFRelease(defaultHandlerID); + + return rv; +} diff --git a/browser/components/shell/nsMacShellService.h b/browser/components/shell/nsMacShellService.h new file mode 100644 index 000000000..db9527809 --- /dev/null +++ b/browser/components/shell/nsMacShellService.h @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nsmacshellservice_h____ +#define nsmacshellservice_h____ + +#include "nsIMacShellService.h" +#include "nsIWebProgressListener.h" +#include "nsIFile.h" +#include "nsCOMPtr.h" + +class nsMacShellService : public nsIMacShellService, + public nsIWebProgressListener +{ +public: + nsMacShellService() {}; + + NS_DECL_ISUPPORTS + NS_DECL_NSISHELLSERVICE + NS_DECL_NSIMACSHELLSERVICE + NS_DECL_NSIWEBPROGRESSLISTENER + +protected: + virtual ~nsMacShellService() {}; + +private: + nsCOMPtr<nsIFile> mBackgroundFile; +}; + +#endif // nsmacshellservice_h____ diff --git a/browser/components/shell/nsSetDefaultBrowser.js b/browser/components/shell/nsSetDefaultBrowser.js new file mode 100644 index 000000000..bb09ab213 --- /dev/null +++ b/browser/components/shell/nsSetDefaultBrowser.js @@ -0,0 +1,30 @@ +/* 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/. */ + +/* + * --setDefaultBrowser commandline handler + * Makes the current executable the "default browser". + */ + +const Cc = Components.classes; +const Ci = Components.interfaces; +Components.utils.import("resource:///modules/ShellService.jsm"); +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +function nsSetDefaultBrowser() {} + +nsSetDefaultBrowser.prototype = { + handle: function nsSetDefault_handle(aCmdline) { + if (aCmdline.handleFlag("setDefaultBrowser", false)) { + ShellService.setDefaultBrowser(true, true); + } + }, + + helpInfo: " --setDefaultBrowser Set this app as the default browser.\n", + + classID: Components.ID("{F57899D0-4E2C-4ac6-9E29-50C736103B0C}"), + QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler]), +}; + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsSetDefaultBrowser]); diff --git a/browser/components/shell/nsSetDefaultBrowser.manifest b/browser/components/shell/nsSetDefaultBrowser.manifest new file mode 100644 index 000000000..bf3c0f04f --- /dev/null +++ b/browser/components/shell/nsSetDefaultBrowser.manifest @@ -0,0 +1,3 @@ +component {F57899D0-4E2C-4ac6-9E29-50C736103B0C} nsSetDefaultBrowser.js +contract @mozilla.org/browser/default-browser-clh;1 {F57899D0-4E2C-4ac6-9E29-50C736103B0C} +category command-line-handler m-setdefaultbrowser @mozilla.org/browser/default-browser-clh;1 diff --git a/browser/components/shell/nsShellService.h b/browser/components/shell/nsShellService.h new file mode 100644 index 000000000..516a8423a --- /dev/null +++ b/browser/components/shell/nsShellService.h @@ -0,0 +1,12 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#define PREF_CHECKDEFAULTBROWSER "browser.shell.checkDefaultBrowser" +#define PREF_SKIPDEFAULTBROWSERCHECK "browser.shell.skipDefaultBrowserCheck" +#define PREF_DEFAULTBROWSERCHECKCOUNT "browser.shell.defaultBrowserCheckCount" + +#define SHELLSERVICE_PROPERTIES "chrome://browser/locale/shellservice.properties" +#define BRAND_PROPERTIES "chrome://branding/locale/brand.properties" + diff --git a/browser/components/shell/nsWindowsShellService.cpp b/browser/components/shell/nsWindowsShellService.cpp new file mode 100644 index 000000000..416e00cbc --- /dev/null +++ b/browser/components/shell/nsWindowsShellService.cpp @@ -0,0 +1,1280 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsWindowsShellService.h" + +#include "imgIContainer.h" +#include "imgIRequest.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/RefPtr.h" +#include "nsIDOMElement.h" +#include "nsIDOMHTMLImageElement.h" +#include "nsIImageLoadingContent.h" +#include "nsIOutputStream.h" +#include "nsIPrefService.h" +#include "nsIPrefLocalizedString.h" +#include "nsIServiceManager.h" +#include "nsIStringBundle.h" +#include "nsNetUtil.h" +#include "nsServiceManagerUtils.h" +#include "nsShellService.h" +#include "nsIProcess.h" +#include "nsICategoryManager.h" +#include "nsBrowserCompsCID.h" +#include "nsDirectoryServiceUtils.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIWindowsRegKey.h" +#include "nsUnicharUtils.h" +#include "nsIWinTaskbar.h" +#include "nsISupportsPrimitives.h" +#include "nsIURLFormatter.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" +#include "mozilla/WindowsVersion.h" + +#include "windows.h" +#include "shellapi.h" + +#ifdef _WIN32_WINNT +#undef _WIN32_WINNT +#endif +#define _WIN32_WINNT 0x0600 +#define INITGUID +#undef NTDDI_VERSION +#define NTDDI_VERSION NTDDI_WIN8 +// Needed for access to IApplicationActivationManager +#include <shlobj.h> + +#include <mbstring.h> +#include <shlwapi.h> + +#include <lm.h> +#undef ACCESS_READ + +#ifndef MAX_BUF +#define MAX_BUF 4096 +#endif + +#define REG_SUCCEEDED(val) \ + (val == ERROR_SUCCESS) + +#define REG_FAILED(val) \ + (val != ERROR_SUCCESS) + +#define NS_TASKBAR_CONTRACTID "@mozilla.org/windows-taskbar;1" + +using mozilla::IsWin8OrLater; +using namespace mozilla; +using namespace mozilla::gfx; + +NS_IMPL_ISUPPORTS(nsWindowsShellService, nsIWindowsShellService, nsIShellService) + +static nsresult +OpenKeyForReading(HKEY aKeyRoot, const nsAString& aKeyName, HKEY* aKey) +{ + const nsString &flatName = PromiseFlatString(aKeyName); + + DWORD res = ::RegOpenKeyExW(aKeyRoot, flatName.get(), 0, KEY_READ, aKey); + switch (res) { + case ERROR_SUCCESS: + break; + case ERROR_ACCESS_DENIED: + return NS_ERROR_FILE_ACCESS_DENIED; + case ERROR_FILE_NOT_FOUND: + return NS_ERROR_NOT_AVAILABLE; + } + + return NS_OK; +} + +/////////////////////////////////////////////////////////////////////////////// +// Default Browser Registry Settings +// +// The setting of these values are made by an external binary since writing +// these values may require elevation. +// +// - File Extension Mappings +// ----------------------- +// The following file extensions: +// .htm .html .shtml .xht .xhtml +// are mapped like so: +// +// HKCU\SOFTWARE\Classes\.<ext>\ (default) REG_SZ FirefoxHTML +// +// as aliases to the class: +// +// HKCU\SOFTWARE\Classes\FirefoxHTML\ +// DefaultIcon (default) REG_SZ <apppath>,1 +// shell\open\command (default) REG_SZ <apppath> -osint -url "%1" +// shell\open\ddeexec (default) REG_SZ <empty string> +// +// - Windows Vista and above Protocol Handler +// +// HKCU\SOFTWARE\Classes\FirefoxURL\ (default) REG_SZ <appname> URL +// EditFlags REG_DWORD 2 +// FriendlyTypeName REG_SZ <appname> URL +// DefaultIcon (default) REG_SZ <apppath>,1 +// shell\open\command (default) REG_SZ <apppath> -osint -url "%1" +// shell\open\ddeexec (default) REG_SZ <empty string> +// +// - Protocol Mappings +// ----------------- +// The following protocols: +// HTTP, HTTPS, FTP +// are mapped like so: +// +// HKCU\SOFTWARE\Classes\<protocol>\ +// DefaultIcon (default) REG_SZ <apppath>,1 +// shell\open\command (default) REG_SZ <apppath> -osint -url "%1" +// shell\open\ddeexec (default) REG_SZ <empty string> +// +// - Windows Start Menu (XP SP1 and newer) +// ------------------------------------------------- +// The following keys are set to make Firefox appear in the Start Menu as the +// browser: +// +// HKCU\SOFTWARE\Clients\StartMenuInternet\FIREFOX.EXE\ +// (default) REG_SZ <appname> +// DefaultIcon (default) REG_SZ <apppath>,0 +// InstallInfo HideIconsCommand REG_SZ <uninstpath> /HideShortcuts +// InstallInfo IconsVisible REG_DWORD 1 +// InstallInfo ReinstallCommand REG_SZ <uninstpath> /SetAsDefaultAppGlobal +// InstallInfo ShowIconsCommand REG_SZ <uninstpath> /ShowShortcuts +// shell\open\command (default) REG_SZ <apppath> +// shell\properties (default) REG_SZ <appname> &Options +// shell\properties\command (default) REG_SZ <apppath> -preferences +// shell\safemode (default) REG_SZ <appname> &Safe Mode +// shell\safemode\command (default) REG_SZ <apppath> -safe-mode +// + +// The values checked are all default values so the value name is not needed. +typedef struct { + const char* keyName; + const char* valueData; + const char* oldValueData; +} SETTING; + +#define APP_REG_NAME L"Firefox" +#define VAL_FILE_ICON "%APPPATH%,1" +#define VAL_OPEN "\"%APPPATH%\" -osint -url \"%1\"" +#define OLD_VAL_OPEN "\"%APPPATH%\" -requestPending -osint -url \"%1\"" +#define DI "\\DefaultIcon" +#define SOC "\\shell\\open\\command" +#define SOD "\\shell\\open\\ddeexec" +// Used for updating the FTP protocol handler's shell open command under HKCU. +#define FTP_SOC L"Software\\Classes\\ftp\\shell\\open\\command" + +#define MAKE_KEY_NAME1(PREFIX, MID) \ + PREFIX MID + +// The DefaultIcon registry key value should never be used when checking if +// Firefox is the default browser for file handlers since other applications +// (e.g. MS Office) may modify the DefaultIcon registry key value to add Icon +// Handlers. see http://msdn2.microsoft.com/en-us/library/aa969357.aspx for +// more info. The FTP protocol is not checked so advanced users can set the FTP +// handler to another application and still have Firefox check if it is the +// default HTTP and HTTPS handler. +// *** Do not add additional checks here unless you skip them when aForAllTypes +// is false below***. +static SETTING gSettings[] = { + // File Handler Class + // ***keep this as the first entry because when aForAllTypes is not set below + // it will skip over this check.*** + { MAKE_KEY_NAME1("FirefoxHTML", SOC), VAL_OPEN, OLD_VAL_OPEN }, + + // Protocol Handler Class - for Vista and above + { MAKE_KEY_NAME1("FirefoxURL", SOC), VAL_OPEN, OLD_VAL_OPEN }, + + // Protocol Handlers + { MAKE_KEY_NAME1("HTTP", DI), VAL_FILE_ICON }, + { MAKE_KEY_NAME1("HTTP", SOC), VAL_OPEN, OLD_VAL_OPEN }, + { MAKE_KEY_NAME1("HTTPS", DI), VAL_FILE_ICON }, + { MAKE_KEY_NAME1("HTTPS", SOC), VAL_OPEN, OLD_VAL_OPEN } +}; + +// The settings to disable DDE are separate from the default browser settings +// since they are only checked when Firefox is the default browser and if they +// are incorrect they are fixed without notifying the user. +static SETTING gDDESettings[] = { + // File Handler Class + { MAKE_KEY_NAME1("Software\\Classes\\FirefoxHTML", SOD) }, + + // Protocol Handler Class - for Vista and above + { MAKE_KEY_NAME1("Software\\Classes\\FirefoxURL", SOD) }, + + // Protocol Handlers + { MAKE_KEY_NAME1("Software\\Classes\\FTP", SOD) }, + { MAKE_KEY_NAME1("Software\\Classes\\HTTP", SOD) }, + { MAKE_KEY_NAME1("Software\\Classes\\HTTPS", SOD) } +}; + +nsresult +GetHelperPath(nsAutoString& aPath) +{ + nsresult rv; + nsCOMPtr<nsIProperties> directoryService = + do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> appHelper; + rv = directoryService->Get(XRE_EXECUTABLE_FILE, + NS_GET_IID(nsIFile), + getter_AddRefs(appHelper)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = appHelper->SetNativeLeafName(NS_LITERAL_CSTRING("uninstall")); + NS_ENSURE_SUCCESS(rv, rv); + + rv = appHelper->AppendNative(NS_LITERAL_CSTRING("helper.exe")); + NS_ENSURE_SUCCESS(rv, rv); + + rv = appHelper->GetPath(aPath); + + aPath.Insert(L'"', 0); + aPath.Append(L'"'); + return rv; +} + +nsresult +LaunchHelper(nsAutoString& aPath) +{ + STARTUPINFOW si = {sizeof(si), 0}; + PROCESS_INFORMATION pi = {0}; + + if (!CreateProcessW(nullptr, (LPWSTR)aPath.get(), nullptr, nullptr, FALSE, + 0, nullptr, nullptr, &si, &pi)) { + return NS_ERROR_FAILURE; + } + + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsShellService::ShortcutMaintenance() +{ + nsresult rv; + + // XXX App ids were updated to a constant install path hash, + // XXX this code can be removed after a few upgrade cycles. + + // Launch helper.exe so it can update the application user model ids on + // shortcuts in the user's taskbar and start menu. This keeps older pinned + // shortcuts grouped correctly after major updates. Note, we also do this + // through the upgrade installer script, however, this is the only place we + // have a chance to trap links created by users who do control the install/ + // update process of the browser. + + nsCOMPtr<nsIWinTaskbar> taskbarInfo = + do_GetService(NS_TASKBAR_CONTRACTID); + if (!taskbarInfo) // If we haven't built with win7 sdk features, this fails. + return NS_OK; + + // Avoid if this isn't Win7+ + bool isSupported = false; + taskbarInfo->GetAvailable(&isSupported); + if (!isSupported) + return NS_OK; + + nsAutoString appId; + if (NS_FAILED(taskbarInfo->GetDefaultGroupId(appId))) + return NS_ERROR_UNEXPECTED; + + NS_NAMED_LITERAL_CSTRING(prefName, "browser.taskbar.lastgroupid"); + nsCOMPtr<nsIPrefBranch> prefs = + do_GetService(NS_PREFSERVICE_CONTRACTID); + if (!prefs) + return NS_ERROR_UNEXPECTED; + + nsCOMPtr<nsISupportsString> prefString; + rv = prefs->GetComplexValue(prefName.get(), + NS_GET_IID(nsISupportsString), + getter_AddRefs(prefString)); + if (NS_SUCCEEDED(rv)) { + nsAutoString version; + prefString->GetData(version); + if (!version.IsEmpty() && version.Equals(appId)) { + // We're all good, get out of here. + return NS_OK; + } + } + // Update the version in prefs + prefString = + do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return rv; + + prefString->SetData(appId); + rv = prefs->SetComplexValue(prefName.get(), + NS_GET_IID(nsISupportsString), + prefString); + if (NS_FAILED(rv)) { + NS_WARNING("Couldn't set last user model id!"); + return NS_ERROR_UNEXPECTED; + } + + nsAutoString appHelperPath; + if (NS_FAILED(GetHelperPath(appHelperPath))) + return NS_ERROR_UNEXPECTED; + + appHelperPath.AppendLiteral(" /UpdateShortcutAppUserModelIds"); + + return LaunchHelper(appHelperPath); +} + +static bool +IsAARDefault(const RefPtr<IApplicationAssociationRegistration>& pAAR, + LPCWSTR aClassName) +{ + // Make sure the Prog ID matches what we have + LPWSTR registeredApp; + bool isProtocol = *aClassName != L'.'; + ASSOCIATIONTYPE queryType = isProtocol ? AT_URLPROTOCOL : AT_FILEEXTENSION; + HRESULT hr = pAAR->QueryCurrentDefault(aClassName, queryType, AL_EFFECTIVE, + ®isteredApp); + if (FAILED(hr)) { + return false; + } + + LPCWSTR progID = isProtocol ? L"FirefoxURL" : L"FirefoxHTML"; + bool isDefault = !wcsicmp(registeredApp, progID); + CoTaskMemFree(registeredApp); + + return isDefault; +} + +static void +IsDefaultBrowserWin8(bool aCheckAllTypes, bool* aIsDefaultBrowser) +{ + RefPtr<IApplicationAssociationRegistration> pAAR; + HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationRegistration, + nullptr, + CLSCTX_INPROC, + IID_IApplicationAssociationRegistration, + getter_AddRefs(pAAR)); + if (FAILED(hr)) { + return; + } + + bool res = IsAARDefault(pAAR, L"http"); + if (*aIsDefaultBrowser) { + *aIsDefaultBrowser = res; + } + res = IsAARDefault(pAAR, L".html"); + if (*aIsDefaultBrowser && aCheckAllTypes) { + *aIsDefaultBrowser = res; + } +} + +/* + * Query's the AAR for the default status. + * This only checks for FirefoxURL and if aCheckAllTypes is set, then + * it also checks for FirefoxHTML. Note that those ProgIDs are shared + * by all Firefox browsers. +*/ +bool +nsWindowsShellService::IsDefaultBrowserVista(bool aCheckAllTypes, + bool* aIsDefaultBrowser) +{ + RefPtr<IApplicationAssociationRegistration> pAAR; + HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationRegistration, + nullptr, + CLSCTX_INPROC, + IID_IApplicationAssociationRegistration, + getter_AddRefs(pAAR)); + if (FAILED(hr)) { + return false; + } + + if (aCheckAllTypes) { + BOOL res; + hr = pAAR->QueryAppIsDefaultAll(AL_EFFECTIVE, + APP_REG_NAME, + &res); + *aIsDefaultBrowser = res; + } else if (!IsWin8OrLater()) { + *aIsDefaultBrowser = IsAARDefault(pAAR, L"http"); + } + + return true; +} + +NS_IMETHODIMP +nsWindowsShellService::IsDefaultBrowser(bool aStartupCheck, + bool aForAllTypes, + bool* aIsDefaultBrowser) +{ + // Assume we're the default unless one of the several checks below tell us + // otherwise. + *aIsDefaultBrowser = true; + + wchar_t exePath[MAX_BUF]; + if (!::GetModuleFileNameW(0, exePath, MAX_BUF)) + return NS_ERROR_FAILURE; + + // Convert the path to a long path since GetModuleFileNameW returns the path + // that was used to launch Firefox which is not necessarily a long path. + if (!::GetLongPathNameW(exePath, exePath, MAX_BUF)) + return NS_ERROR_FAILURE; + + nsAutoString appLongPath(exePath); + + HKEY theKey; + DWORD res; + nsresult rv; + wchar_t currValue[MAX_BUF]; + + SETTING* settings = gSettings; + if (!aForAllTypes && IsWin8OrLater()) { + // Skip over the file handler check + settings++; + } + + SETTING* end = gSettings + sizeof(gSettings) / sizeof(SETTING); + + for (; settings < end; ++settings) { + NS_ConvertUTF8toUTF16 keyName(settings->keyName); + NS_ConvertUTF8toUTF16 valueData(settings->valueData); + int32_t offset = valueData.Find("%APPPATH%"); + valueData.Replace(offset, 9, appLongPath); + + rv = OpenKeyForReading(HKEY_CLASSES_ROOT, keyName, &theKey); + if (NS_FAILED(rv)) { + *aIsDefaultBrowser = false; + return NS_OK; + } + + ::ZeroMemory(currValue, sizeof(currValue)); + DWORD len = sizeof currValue; + res = ::RegQueryValueExW(theKey, L"", nullptr, nullptr, + (LPBYTE)currValue, &len); + // Close the key that was opened. + ::RegCloseKey(theKey); + if (REG_FAILED(res) || + _wcsicmp(valueData.get(), currValue)) { + // Key wasn't set or was set to something other than our registry entry. + NS_ConvertUTF8toUTF16 oldValueData(settings->oldValueData); + offset = oldValueData.Find("%APPPATH%"); + oldValueData.Replace(offset, 9, appLongPath); + // The current registry value doesn't match the current or the old format. + if (_wcsicmp(oldValueData.get(), currValue)) { + *aIsDefaultBrowser = false; + return NS_OK; + } + + res = ::RegOpenKeyExW(HKEY_CLASSES_ROOT, keyName.get(), + 0, KEY_SET_VALUE, &theKey); + if (REG_FAILED(res)) { + // If updating the open command fails try to update it using the helper + // application when setting Firefox as the default browser. + *aIsDefaultBrowser = false; + return NS_OK; + } + + res = ::RegSetValueExW(theKey, L"", 0, REG_SZ, + (const BYTE *) valueData.get(), + (valueData.Length() + 1) * sizeof(char16_t)); + // Close the key that was created. + ::RegCloseKey(theKey); + if (REG_FAILED(res)) { + // If updating the open command fails try to update it using the helper + // application when setting Firefox as the default browser. + *aIsDefaultBrowser = false; + return NS_OK; + } + } + } + + // Only check if Firefox is the default browser on Vista and above if the + // previous checks show that Firefox is the default browser. + if (*aIsDefaultBrowser) { + IsDefaultBrowserVista(aForAllTypes, aIsDefaultBrowser); + if (IsWin8OrLater()) { + IsDefaultBrowserWin8(aForAllTypes, aIsDefaultBrowser); + } + } + + // To handle the case where DDE isn't disabled due for a user because there + // account didn't perform a Firefox update this will check if Firefox is the + // default browser and if dde is disabled for each handler + // and if it isn't disable it. When Firefox is not the default browser the + // helper application will disable dde for each handler. + if (*aIsDefaultBrowser && aForAllTypes) { + // Check ftp settings + + end = gDDESettings + sizeof(gDDESettings) / sizeof(SETTING); + + for (settings = gDDESettings; settings < end; ++settings) { + NS_ConvertUTF8toUTF16 keyName(settings->keyName); + + rv = OpenKeyForReading(HKEY_CURRENT_USER, keyName, &theKey); + if (NS_FAILED(rv)) { + ::RegCloseKey(theKey); + // If disabling DDE fails try to disable it using the helper + // application when setting Firefox as the default browser. + *aIsDefaultBrowser = false; + return NS_OK; + } + + ::ZeroMemory(currValue, sizeof(currValue)); + DWORD len = sizeof currValue; + res = ::RegQueryValueExW(theKey, L"", nullptr, nullptr, + (LPBYTE)currValue, &len); + // Close the key that was opened. + ::RegCloseKey(theKey); + if (REG_FAILED(res) || char16_t('\0') != *currValue) { + // Key wasn't set or was set to something other than our registry entry. + // Delete the key along with all of its childrean and then recreate it. + ::SHDeleteKeyW(HKEY_CURRENT_USER, keyName.get()); + res = ::RegCreateKeyExW(HKEY_CURRENT_USER, keyName.get(), 0, nullptr, + REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, + nullptr, &theKey, nullptr); + if (REG_FAILED(res)) { + // If disabling DDE fails try to disable it using the helper + // application when setting Firefox as the default browser. + *aIsDefaultBrowser = false; + return NS_OK; + } + + res = ::RegSetValueExW(theKey, L"", 0, REG_SZ, (const BYTE *) L"", + sizeof(char16_t)); + // Close the key that was created. + ::RegCloseKey(theKey); + if (REG_FAILED(res)) { + // If disabling DDE fails try to disable it using the helper + // application when setting Firefox as the default browser. + *aIsDefaultBrowser = false; + return NS_OK; + } + } + } + + // Update the FTP protocol handler's shell open command if it is the old + // format. + res = ::RegOpenKeyExW(HKEY_CURRENT_USER, FTP_SOC, 0, KEY_ALL_ACCESS, + &theKey); + // Don't update the FTP protocol handler's shell open command when opening + // its registry key fails under HKCU since it most likely doesn't exist. + if (NS_FAILED(rv)) { + return NS_OK; + } + + NS_ConvertUTF8toUTF16 oldValueOpen(OLD_VAL_OPEN); + int32_t offset = oldValueOpen.Find("%APPPATH%"); + oldValueOpen.Replace(offset, 9, appLongPath); + + ::ZeroMemory(currValue, sizeof(currValue)); + DWORD len = sizeof currValue; + res = ::RegQueryValueExW(theKey, L"", nullptr, nullptr, (LPBYTE)currValue, + &len); + + // Don't update the FTP protocol handler's shell open command when the + // current registry value doesn't exist or matches the old format. + if (REG_FAILED(res) || + _wcsicmp(oldValueOpen.get(), currValue)) { + ::RegCloseKey(theKey); + return NS_OK; + } + + NS_ConvertUTF8toUTF16 valueData(VAL_OPEN); + valueData.Replace(offset, 9, appLongPath); + res = ::RegSetValueExW(theKey, L"", 0, REG_SZ, + (const BYTE *) valueData.get(), + (valueData.Length() + 1) * sizeof(char16_t)); + // Close the key that was created. + ::RegCloseKey(theKey); + // If updating the FTP protocol handlers shell open command fails try to + // update it using the helper application when setting Firefox as the + // default browser. + if (REG_FAILED(res)) { + *aIsDefaultBrowser = false; + } + } + + return NS_OK; +} + +static nsresult +DynSHOpenWithDialog(HWND hwndParent, const OPENASINFO *poainfo) +{ + // shell32.dll is in the knownDLLs list so will always be loaded from the + // system32 directory. + static const wchar_t kSehllLibraryName[] = L"shell32.dll"; + HMODULE shellDLL = ::LoadLibraryW(kSehllLibraryName); + if (!shellDLL) { + return NS_ERROR_FAILURE; + } + + decltype(SHOpenWithDialog)* SHOpenWithDialogFn = + (decltype(SHOpenWithDialog)*) GetProcAddress(shellDLL, "SHOpenWithDialog"); + + if (!SHOpenWithDialogFn) { + return NS_ERROR_FAILURE; + } + + nsresult rv; + HRESULT hr = SHOpenWithDialogFn(hwndParent, poainfo); + if (SUCCEEDED(hr) || (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED))) { + rv = NS_OK; + } else { + rv = NS_ERROR_FAILURE; + } + FreeLibrary(shellDLL); + return rv; +} + +nsresult +nsWindowsShellService::LaunchControlPanelDefaultsSelectionUI() +{ + IApplicationAssociationRegistrationUI* pAARUI; + HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationRegistrationUI, + NULL, + CLSCTX_INPROC, + IID_IApplicationAssociationRegistrationUI, + (void**)&pAARUI); + if (SUCCEEDED(hr)) { + hr = pAARUI->LaunchAdvancedAssociationUI(APP_REG_NAME); + pAARUI->Release(); + } + return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE; +} + +nsresult +nsWindowsShellService::LaunchControlPanelDefaultPrograms() +{ + // This Default Programs feature is Win7+ only. + if (!IsWin7OrLater()) { + return NS_ERROR_FAILURE; + } + + // Build the path control.exe path safely + WCHAR controlEXEPath[MAX_PATH + 1] = { '\0' }; + if (!GetSystemDirectoryW(controlEXEPath, MAX_PATH)) { + return NS_ERROR_FAILURE; + } + LPCWSTR controlEXE = L"control.exe"; + if (wcslen(controlEXEPath) + wcslen(controlEXE) >= MAX_PATH) { + return NS_ERROR_FAILURE; + } + if (!PathAppendW(controlEXEPath, controlEXE)) { + return NS_ERROR_FAILURE; + } + + WCHAR params[] = L"control.exe /name Microsoft.DefaultPrograms /page " + "pageDefaultProgram\\pageAdvancedSettings?pszAppName=" APP_REG_NAME; + STARTUPINFOW si = {sizeof(si), 0}; + si.dwFlags = STARTF_USESHOWWINDOW; + si.wShowWindow = SW_SHOWDEFAULT; + PROCESS_INFORMATION pi = {0}; + if (!CreateProcessW(controlEXEPath, params, nullptr, nullptr, FALSE, + 0, nullptr, nullptr, &si, &pi)) { + return NS_ERROR_FAILURE; + } + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + + return NS_OK; +} + +static bool +IsWindowsLogonConnected() +{ + WCHAR userName[UNLEN + 1]; + DWORD size = ArrayLength(userName); + if (!GetUserNameW(userName, &size)) { + return false; + } + + LPUSER_INFO_24 info; + if (NetUserGetInfo(nullptr, userName, 24, (LPBYTE *)&info) + != NERR_Success) { + return false; + } + bool connected = info->usri24_internet_identity; + NetApiBufferFree(info); + + return connected; +} + +static bool +SettingsAppBelievesConnected() +{ + nsresult rv; + nsCOMPtr<nsIWindowsRegKey> regKey = + do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv); + if (NS_FAILED(rv)) { + return false; + } + + rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + NS_LITERAL_STRING("SOFTWARE\\Microsoft\\Windows\\Shell\\Associations"), + nsIWindowsRegKey::ACCESS_READ); + if (NS_FAILED(rv)) { + return false; + } + + uint32_t value; + rv = regKey->ReadIntValue(NS_LITERAL_STRING("IsConnectedAtLogon"), &value); + if (NS_FAILED(rv)) { + return false; + } + + return !!value; +} + +nsresult +nsWindowsShellService::LaunchModernSettingsDialogDefaultApps() +{ + if (!IsWindowsBuildOrLater(14965) && + !IsWindowsLogonConnected() && SettingsAppBelievesConnected()) { + // Use the classic Control Panel to work around a bug of older + // builds of Windows 10. + return LaunchControlPanelDefaultPrograms(); + } + + IApplicationActivationManager* pActivator; + HRESULT hr = CoCreateInstance(CLSID_ApplicationActivationManager, + nullptr, + CLSCTX_INPROC, + IID_IApplicationActivationManager, + (void**)&pActivator); + + if (SUCCEEDED(hr)) { + DWORD pid; + hr = pActivator->ActivateApplication( + L"windows.immersivecontrolpanel_cw5n1h2txyewy" + L"!microsoft.windows.immersivecontrolpanel", + L"page=SettingsPageAppsDefaults", AO_NONE, &pid); + if (SUCCEEDED(hr)) { + // Do not check error because we could at least open + // the "Default apps" setting. + pActivator->ActivateApplication( + L"windows.immersivecontrolpanel_cw5n1h2txyewy" + L"!microsoft.windows.immersivecontrolpanel", + L"page=SettingsPageAppsDefaults" + L"&target=SystemSettings_DefaultApps_Browser", AO_NONE, &pid); + } + pActivator->Release(); + return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE; + } + return NS_OK; +} + +nsresult +nsWindowsShellService::InvokeHTTPOpenAsVerb() +{ + nsCOMPtr<nsIURLFormatter> formatter( + do_GetService("@mozilla.org/toolkit/URLFormatterService;1")); + if (!formatter) { + return NS_ERROR_UNEXPECTED; + } + + nsString urlStr; + nsresult rv = formatter->FormatURLPref( + NS_LITERAL_STRING("app.support.baseURL"), urlStr); + if (NS_FAILED(rv)) { + return rv; + } + if (!StringBeginsWith(urlStr, NS_LITERAL_STRING("https://"))) { + return NS_ERROR_FAILURE; + } + urlStr.AppendLiteral("win10-default-browser"); + + SHELLEXECUTEINFOW seinfo = { sizeof(SHELLEXECUTEINFOW) }; + seinfo.lpVerb = L"openas"; + seinfo.lpFile = urlStr.get(); + seinfo.nShow = SW_SHOWNORMAL; + if (!ShellExecuteExW(&seinfo)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +nsresult +nsWindowsShellService::LaunchHTTPHandlerPane() +{ + OPENASINFO info; + info.pcszFile = L"http"; + info.pcszClass = nullptr; + info.oaifInFlags = OAIF_FORCE_REGISTRATION | + OAIF_URL_PROTOCOL | + OAIF_REGISTER_EXT; + return DynSHOpenWithDialog(nullptr, &info); +} + +NS_IMETHODIMP +nsWindowsShellService::SetDefaultBrowser(bool aClaimAllTypes, bool aForAllUsers) +{ + nsAutoString appHelperPath; + if (NS_FAILED(GetHelperPath(appHelperPath))) + return NS_ERROR_FAILURE; + + if (aForAllUsers) { + appHelperPath.AppendLiteral(" /SetAsDefaultAppGlobal"); + } else { + appHelperPath.AppendLiteral(" /SetAsDefaultAppUser"); + } + + nsresult rv = LaunchHelper(appHelperPath); + if (NS_SUCCEEDED(rv) && IsWin8OrLater()) { + if (aClaimAllTypes) { + if (IsWin10OrLater()) { + rv = LaunchModernSettingsDialogDefaultApps(); + } else { + rv = LaunchControlPanelDefaultsSelectionUI(); + } + // The above call should never really fail, but just in case + // fall back to showing the HTTP association screen only. + if (NS_FAILED(rv)) { + if (IsWin10OrLater()) { + rv = InvokeHTTPOpenAsVerb(); + } else { + rv = LaunchHTTPHandlerPane(); + } + } + } else { + // Windows 10 blocks attempts to load the + // HTTP Handler association dialog. + if (IsWin10OrLater()) { + rv = LaunchModernSettingsDialogDefaultApps(); + } else { + rv = LaunchHTTPHandlerPane(); + } + + // The above call should never really fail, but just in case + // fall back to showing control panel for all defaults + if (NS_FAILED(rv)) { + rv = LaunchControlPanelDefaultsSelectionUI(); + } + } + } + + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (prefs) { + (void) prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, true); + // Reset the number of times the dialog should be shown + // before it is silenced. + (void) prefs->SetIntPref(PREF_DEFAULTBROWSERCHECKCOUNT, 0); + } + + return rv; +} + +static nsresult +WriteBitmap(nsIFile* aFile, imgIContainer* aImage) +{ + nsresult rv; + + RefPtr<SourceSurface> surface = + aImage->GetFrame(imgIContainer::FRAME_FIRST, + imgIContainer::FLAG_SYNC_DECODE); + NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE); + + // For either of the following formats we want to set the biBitCount member + // of the BITMAPINFOHEADER struct to 32, below. For that value the bitmap + // format defines that the A8/X8 WORDs in the bitmap byte stream be ignored + // for the BI_RGB value we use for the biCompression member. + MOZ_ASSERT(surface->GetFormat() == SurfaceFormat::B8G8R8A8 || + surface->GetFormat() == SurfaceFormat::B8G8R8X8); + + RefPtr<DataSourceSurface> dataSurface = surface->GetDataSurface(); + NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE); + + int32_t width = dataSurface->GetSize().width; + int32_t height = dataSurface->GetSize().height; + int32_t bytesPerPixel = 4 * sizeof(uint8_t); + uint32_t bytesPerRow = bytesPerPixel * width; + + // initialize these bitmap structs which we will later + // serialize directly to the head of the bitmap file + BITMAPINFOHEADER bmi; + bmi.biSize = sizeof(BITMAPINFOHEADER); + bmi.biWidth = width; + bmi.biHeight = height; + bmi.biPlanes = 1; + bmi.biBitCount = (WORD)bytesPerPixel*8; + bmi.biCompression = BI_RGB; + bmi.biSizeImage = bytesPerRow * height; + bmi.biXPelsPerMeter = 0; + bmi.biYPelsPerMeter = 0; + bmi.biClrUsed = 0; + bmi.biClrImportant = 0; + + BITMAPFILEHEADER bf; + bf.bfType = 0x4D42; // 'BM' + bf.bfReserved1 = 0; + bf.bfReserved2 = 0; + bf.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); + bf.bfSize = bf.bfOffBits + bmi.biSizeImage; + + // get a file output stream + nsCOMPtr<nsIOutputStream> stream; + rv = NS_NewLocalFileOutputStream(getter_AddRefs(stream), aFile); + NS_ENSURE_SUCCESS(rv, rv); + + DataSourceSurface::MappedSurface map; + if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) { + return NS_ERROR_FAILURE; + } + + // write the bitmap headers and rgb pixel data to the file + rv = NS_ERROR_FAILURE; + if (stream) { + uint32_t written; + stream->Write((const char*)&bf, sizeof(BITMAPFILEHEADER), &written); + if (written == sizeof(BITMAPFILEHEADER)) { + stream->Write((const char*)&bmi, sizeof(BITMAPINFOHEADER), &written); + if (written == sizeof(BITMAPINFOHEADER)) { + // write out the image data backwards because the desktop won't + // show bitmaps with negative heights for top-to-bottom + uint32_t i = map.mStride * height; + do { + i -= map.mStride; + stream->Write(((const char*)map.mData) + i, bytesPerRow, &written); + if (written == bytesPerRow) { + rv = NS_OK; + } else { + rv = NS_ERROR_FAILURE; + break; + } + } while (i != 0); + } + } + + stream->Close(); + } + + dataSurface->Unmap(); + + return rv; +} + +NS_IMETHODIMP +nsWindowsShellService::SetDesktopBackground(nsIDOMElement* aElement, + int32_t aPosition) +{ + nsresult rv; + + nsCOMPtr<imgIContainer> container; + nsCOMPtr<nsIDOMHTMLImageElement> imgElement(do_QueryInterface(aElement)); + if (!imgElement) { + // XXX write background loading stuff! + return NS_ERROR_NOT_AVAILABLE; + } + else { + nsCOMPtr<nsIImageLoadingContent> imageContent = + do_QueryInterface(aElement, &rv); + if (!imageContent) + return rv; + + // get the image container + nsCOMPtr<imgIRequest> request; + rv = imageContent->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, + getter_AddRefs(request)); + if (!request) + return rv; + rv = request->GetImage(getter_AddRefs(container)); + if (!container) + return NS_ERROR_FAILURE; + } + + // get the file name from localized strings + nsCOMPtr<nsIStringBundleService> + bundleService(do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIStringBundle> shellBundle; + rv = bundleService->CreateBundle(SHELLSERVICE_PROPERTIES, + getter_AddRefs(shellBundle)); + NS_ENSURE_SUCCESS(rv, rv); + + // e.g. "Desktop Background.bmp" + nsString fileLeafName; + rv = shellBundle->GetStringFromName + (u"desktopBackgroundLeafNameWin", + getter_Copies(fileLeafName)); + NS_ENSURE_SUCCESS(rv, rv); + + // get the profile root directory + nsCOMPtr<nsIFile> file; + rv = NS_GetSpecialDirectory(NS_APP_APPLICATION_REGISTRY_DIR, + getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + + // eventually, the path is "%APPDATA%\Mozilla\Firefox\Desktop Background.bmp" + rv = file->Append(fileLeafName); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString path; + rv = file->GetPath(path); + NS_ENSURE_SUCCESS(rv, rv); + + // write the bitmap to a file in the profile directory + rv = WriteBitmap(file, container); + + // if the file was written successfully, set it as the system wallpaper + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIWindowsRegKey> regKey = + do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = regKey->Create(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + NS_LITERAL_STRING("Control Panel\\Desktop"), + nsIWindowsRegKey::ACCESS_SET_VALUE); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString tile; + nsAutoString style; + switch (aPosition) { + case BACKGROUND_TILE: + style.Assign('0'); + tile.Assign('1'); + break; + case BACKGROUND_CENTER: + style.Assign('0'); + tile.Assign('0'); + break; + case BACKGROUND_STRETCH: + style.Assign('2'); + tile.Assign('0'); + break; + case BACKGROUND_FILL: + style.AssignLiteral("10"); + tile.Assign('0'); + break; + case BACKGROUND_FIT: + style.Assign('6'); + tile.Assign('0'); + break; + } + + rv = regKey->WriteStringValue(NS_LITERAL_STRING("TileWallpaper"), tile); + NS_ENSURE_SUCCESS(rv, rv); + rv = regKey->WriteStringValue(NS_LITERAL_STRING("WallpaperStyle"), style); + NS_ENSURE_SUCCESS(rv, rv); + rv = regKey->Close(); + NS_ENSURE_SUCCESS(rv, rv); + + ::SystemParametersInfoW(SPI_SETDESKWALLPAPER, 0, (PVOID)path.get(), + SPIF_UPDATEINIFILE | SPIF_SENDCHANGE); + } + return rv; +} + +NS_IMETHODIMP +nsWindowsShellService::OpenApplication(int32_t aApplication) +{ + nsAutoString application; + switch (aApplication) { + case nsIShellService::APPLICATION_MAIL: + application.AssignLiteral("Mail"); + break; + case nsIShellService::APPLICATION_NEWS: + application.AssignLiteral("News"); + break; + } + + // The Default Client section of the Windows Registry looks like this: + // + // Clients\aClient\ + // e.g. aClient = "Mail"... + // \Mail\(default) = Client Subkey Name + // \Client Subkey Name + // \Client Subkey Name\shell\open\command\ + // \Client Subkey Name\shell\open\command\(default) = path to exe + // + + // Find the default application for this class. + HKEY theKey; + nsresult rv = OpenKeyForReading(HKEY_CLASSES_ROOT, application, &theKey); + if (NS_FAILED(rv)) + return rv; + + wchar_t buf[MAX_BUF]; + DWORD type, len = sizeof buf; + DWORD res = ::RegQueryValueExW(theKey, EmptyString().get(), 0, + &type, (LPBYTE)&buf, &len); + + if (REG_FAILED(res) || !*buf) + return NS_OK; + + // Close the key we opened. + ::RegCloseKey(theKey); + + // Find the "open" command + application.Append('\\'); + application.Append(buf); + application.AppendLiteral("\\shell\\open\\command"); + + rv = OpenKeyForReading(HKEY_CLASSES_ROOT, application, &theKey); + if (NS_FAILED(rv)) + return rv; + + ::ZeroMemory(buf, sizeof(buf)); + len = sizeof buf; + res = ::RegQueryValueExW(theKey, EmptyString().get(), 0, + &type, (LPBYTE)&buf, &len); + if (REG_FAILED(res) || !*buf) + return NS_ERROR_FAILURE; + + // Close the key we opened. + ::RegCloseKey(theKey); + + // Look for any embedded environment variables and substitute their + // values, as |::CreateProcessW| is unable to do this. + nsAutoString path(buf); + int32_t end = path.Length(); + int32_t cursor = 0, temp = 0; + ::ZeroMemory(buf, sizeof(buf)); + do { + cursor = path.FindChar('%', cursor); + if (cursor < 0) + break; + + temp = path.FindChar('%', cursor + 1); + ++cursor; + + ::ZeroMemory(&buf, sizeof(buf)); + + ::GetEnvironmentVariableW(nsAutoString(Substring(path, cursor, temp - cursor)).get(), + buf, sizeof(buf)); + + // "+ 2" is to subtract the extra characters used to delimit the environment + // variable ('%'). + path.Replace((cursor - 1), temp - cursor + 2, nsDependentString(buf)); + + ++cursor; + } + while (cursor < end); + + STARTUPINFOW si; + PROCESS_INFORMATION pi; + + ::ZeroMemory(&si, sizeof(STARTUPINFOW)); + ::ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); + + BOOL success = ::CreateProcessW(nullptr, (LPWSTR)path.get(), nullptr, + nullptr, FALSE, 0, nullptr, nullptr, + &si, &pi); + if (!success) + return NS_ERROR_FAILURE; + + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsShellService::GetDesktopBackgroundColor(uint32_t* aColor) +{ + uint32_t color = ::GetSysColor(COLOR_DESKTOP); + *aColor = (GetRValue(color) << 16) | (GetGValue(color) << 8) | GetBValue(color); + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsShellService::SetDesktopBackgroundColor(uint32_t aColor) +{ + int aParameters[2] = { COLOR_BACKGROUND, COLOR_DESKTOP }; + BYTE r = (aColor >> 16); + BYTE g = (aColor << 16) >> 24; + BYTE b = (aColor << 24) >> 24; + COLORREF colors[2] = { RGB(r,g,b), RGB(r,g,b) }; + + ::SetSysColors(sizeof(aParameters) / sizeof(int), aParameters, colors); + + nsresult rv; + nsCOMPtr<nsIWindowsRegKey> regKey = + do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = regKey->Create(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + NS_LITERAL_STRING("Control Panel\\Colors"), + nsIWindowsRegKey::ACCESS_SET_VALUE); + NS_ENSURE_SUCCESS(rv, rv); + + wchar_t rgb[12]; + _snwprintf(rgb, 12, L"%u %u %u", r, g, b); + + rv = regKey->WriteStringValue(NS_LITERAL_STRING("Background"), + nsDependentString(rgb)); + NS_ENSURE_SUCCESS(rv, rv); + + return regKey->Close(); +} + +nsWindowsShellService::nsWindowsShellService() +{ +} + +nsWindowsShellService::~nsWindowsShellService() +{ +} + +NS_IMETHODIMP +nsWindowsShellService::OpenApplicationWithURI(nsIFile* aApplication, + const nsACString& aURI) +{ + nsresult rv; + nsCOMPtr<nsIProcess> process = + do_CreateInstance("@mozilla.org/process/util;1", &rv); + if (NS_FAILED(rv)) + return rv; + + rv = process->Init(aApplication); + if (NS_FAILED(rv)) + return rv; + + const nsCString spec(aURI); + const char* specStr = spec.get(); + return process->Run(false, &specStr, 1); +} + +NS_IMETHODIMP +nsWindowsShellService::GetDefaultFeedReader(nsIFile** _retval) +{ + *_retval = nullptr; + + nsresult rv; + nsCOMPtr<nsIWindowsRegKey> regKey = + do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, + NS_LITERAL_STRING("feed\\shell\\open\\command"), + nsIWindowsRegKey::ACCESS_READ); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString path; + rv = regKey->ReadStringValue(EmptyString(), path); + NS_ENSURE_SUCCESS(rv, rv); + if (path.IsEmpty()) + return NS_ERROR_FAILURE; + + if (path.First() == '"') { + // Everything inside the quotes + path = Substring(path, 1, path.FindChar('"', 1) - 1); + } + else { + // Everything up to the first space + path = Substring(path, 0, path.FindChar(' ')); + } + + nsCOMPtr<nsIFile> defaultReader = + do_CreateInstance("@mozilla.org/file/local;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = defaultReader->InitWithPath(path); + NS_ENSURE_SUCCESS(rv, rv); + + bool exists; + rv = defaultReader->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + if (!exists) + return NS_ERROR_FAILURE; + + NS_ADDREF(*_retval = defaultReader); + return NS_OK; +} diff --git a/browser/components/shell/nsWindowsShellService.h b/browser/components/shell/nsWindowsShellService.h new file mode 100644 index 000000000..b9c473a15 --- /dev/null +++ b/browser/components/shell/nsWindowsShellService.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nswindowsshellservice_h____ +#define nswindowsshellservice_h____ + +#include "nscore.h" +#include "nsString.h" +#include "nsIWindowsShellService.h" +#include "nsITimer.h" + +#include <windows.h> +#include <ole2.h> + +class nsWindowsShellService : public nsIWindowsShellService +{ + virtual ~nsWindowsShellService(); + +public: + nsWindowsShellService(); + + NS_DECL_ISUPPORTS + NS_DECL_NSISHELLSERVICE + NS_DECL_NSIWINDOWSSHELLSERVICE + +protected: + bool IsDefaultBrowserVista(bool aCheckAllTypes, bool* aIsDefaultBrowser); + nsresult LaunchControlPanelDefaultsSelectionUI(); + nsresult LaunchControlPanelDefaultPrograms(); + nsresult LaunchModernSettingsDialogDefaultApps(); + nsresult InvokeHTTPOpenAsVerb(); + nsresult LaunchHTTPHandlerPane(); +}; + +#endif // nswindowsshellservice_h____ diff --git a/browser/components/shell/test/.eslintrc.js b/browser/components/shell/test/.eslintrc.js new file mode 100644 index 000000000..c764b133d --- /dev/null +++ b/browser/components/shell/test/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../testing/mochitest/browser.eslintrc.js" + ] +}; diff --git a/browser/components/shell/test/browser.ini b/browser/components/shell/test/browser.ini new file mode 100644 index 000000000..8f18415c0 --- /dev/null +++ b/browser/components/shell/test/browser.ini @@ -0,0 +1,6 @@ +[DEFAULT] +skip-if = os != "linux" + +[browser_420786.js] +[browser_633221.js] + diff --git a/browser/components/shell/test/browser_420786.js b/browser/components/shell/test/browser_420786.js new file mode 100644 index 000000000..ae4521890 --- /dev/null +++ b/browser/components/shell/test/browser_420786.js @@ -0,0 +1,88 @@ +const DG_BACKGROUND = "/desktop/gnome/background" +const DG_IMAGE_KEY = DG_BACKGROUND + "/picture_filename"; +const DG_OPTION_KEY = DG_BACKGROUND + "/picture_options"; +const DG_DRAW_BG_KEY = DG_BACKGROUND + "/draw_background"; + +function onPageLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onPageLoad, true); + + var bs = Cc["@mozilla.org/intl/stringbundle;1"]. + getService(Ci.nsIStringBundleService); + var brandName = bs.createBundle("chrome://branding/locale/brand.properties"). + GetStringFromName("brandShortName"); + + var dirSvc = Cc["@mozilla.org/file/directory_service;1"]. + getService(Ci.nsIDirectoryServiceProvider); + var homeDir = dirSvc.getFile("Home", {}); + + var wpFile = homeDir.clone(); + wpFile.append(brandName + "_wallpaper.png"); + + // Backup the existing wallpaper so that this test doesn't change the user's + // settings. + var wpFileBackup = homeDir.clone() + wpFileBackup.append(brandName + "_wallpaper.png.backup"); + + if (wpFileBackup.exists()) + wpFileBackup.remove(false); + + if (wpFile.exists()) + wpFile.copyTo(null, wpFileBackup.leafName); + + var shell = Cc["@mozilla.org/browser/shell-service;1"]. + getService(Ci.nsIShellService); + var gconf = Cc["@mozilla.org/gnome-gconf-service;1"]. + getService(Ci.nsIGConfService); + + var prevImageKey = gconf.getString(DG_IMAGE_KEY); + var prevOptionKey = gconf.getString(DG_OPTION_KEY); + var prevDrawBgKey = gconf.getBool(DG_DRAW_BG_KEY); + + var image = content.document.images[0]; + + function checkWallpaper(position, expectedGConfPosition) { + shell.setDesktopBackground(image, position); + ok(wpFile.exists(), "Wallpaper was written to disk"); + is(gconf.getString(DG_IMAGE_KEY), wpFile.path, + "Wallpaper file GConf key is correct"); + is(gconf.getString(DG_OPTION_KEY), expectedGConfPosition, + "Wallpaper position GConf key is correct"); + } + + checkWallpaper(Ci.nsIShellService.BACKGROUND_TILE, "wallpaper"); + checkWallpaper(Ci.nsIShellService.BACKGROUND_STRETCH, "stretched"); + checkWallpaper(Ci.nsIShellService.BACKGROUND_CENTER, "centered"); + checkWallpaper(Ci.nsIShellService.BACKGROUND_FILL, "zoom"); + checkWallpaper(Ci.nsIShellService.BACKGROUND_FIT, "scaled"); + + // Restore GConf and wallpaper + + gconf.setString(DG_IMAGE_KEY, prevImageKey); + gconf.setString(DG_OPTION_KEY, prevOptionKey); + gconf.setBool(DG_DRAW_BG_KEY, prevDrawBgKey); + + wpFile.remove(false); + if (wpFileBackup.exists()) + wpFileBackup.moveTo(null, wpFile.leafName); + + gBrowser.removeCurrentTab(); + finish(); +} + +function test() { + try { + // If GSettings is available, then the GConf tests + // will fail + Cc["@mozilla.org/gsettings-service;1"]. + getService(Ci.nsIGSettingsService). + getCollectionForSchema("org.gnome.desktop.background"); + todo(false, "This test doesn't work when GSettings is available"); + return; + } catch (e) { } + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", onPageLoad, true); + content.location = "about:logo"; + + waitForExplicitFinish(); +} diff --git a/browser/components/shell/test/browser_633221.js b/browser/components/shell/test/browser_633221.js new file mode 100644 index 000000000..7929e8098 --- /dev/null +++ b/browser/components/shell/test/browser_633221.js @@ -0,0 +1,7 @@ +Components.utils.import("resource:///modules/ShellService.jsm"); + +function test() { + ShellService.setDefaultBrowser(true, false); + ok(ShellService.isDefaultBrowser(true, false), "we got here and are the default browser"); + ok(ShellService.isDefaultBrowser(true, true), "we got here and are the default browser"); +} diff --git a/browser/components/shell/test/unit/.eslintrc.js b/browser/components/shell/test/unit/.eslintrc.js new file mode 100644 index 000000000..d35787cd2 --- /dev/null +++ b/browser/components/shell/test/unit/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/xpcshell/xpcshell.eslintrc.js" + ] +}; diff --git a/browser/components/shell/test/unit/test_421977.js b/browser/components/shell/test/unit/test_421977.js new file mode 100644 index 000000000..637db4b91 --- /dev/null +++ b/browser/components/shell/test/unit/test_421977.js @@ -0,0 +1,123 @@ +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cr = Components.results; + +const GCONF_BG_COLOR_KEY = "/desktop/gnome/background/primary_color"; + +var gShell; +var gGConf; + +/** + * Converts from a rgb numerical color valule (r << 16 | g << 8 | b) + * into a hex string in #RRGGBB format. + */ +function colorToHex(aColor) { + const rMask = 4294901760; + const gMask = 65280; + const bMask = 255; + + var r = (aColor & rMask) >> 16; + var g = (aColor & gMask) >> 8; + var b = (aColor & bMask); + + return "#" + [r, g, b].map(aInt => + aInt.toString(16).replace(/^(.)$/, "0$1")) + .join("").toUpperCase(); +} + +/** + * Converts a color string in #RRGGBB format to a rgb numerical color value + * (r << 16 | g << 8 | b). + */ +function hexToColor(aString) { + return parseInt(aString.substring(1, 3), 16) << 16 | + parseInt(aString.substring(3, 5), 16) << 8 | + parseInt(aString.substring(5, 7), 16); +} + +/** + * Checks that setting the GConf background key to aGConfColor will + * result in the Shell component returning a background color equals + * to aExpectedShellColor in #RRGGBB format. + */ +function checkGConfToShellColor(aGConfColor, aExpectedShellColor) { + + gGConf.setString(GCONF_BG_COLOR_KEY, aGConfColor); + var shellColor = colorToHex(gShell.desktopBackgroundColor); + + do_check_eq(shellColor, aExpectedShellColor); +} + +/** + * Checks that setting the background color (in #RRGGBB format) using the Shell + * component will result in having a GConf key for the background color set to + * aExpectedGConfColor. + */ +function checkShellToGConfColor(aShellColor, aExpectedGConfColor) { + + gShell.desktopBackgroundColor = hexToColor(aShellColor); + var gconfColor = gGConf.getString(GCONF_BG_COLOR_KEY); + + do_check_eq(gconfColor, aExpectedGConfColor); +} + +function run_test() { + + // This test is Linux specific for now + if (!("@mozilla.org/gnome-gconf-service;1" in Cc)) + return; + + try { + // If GSettings is available, then the GConf tests + // will fail + Cc["@mozilla.org/gsettings-service;1"]. + getService(Ci.nsIGSettingsService). + getCollectionForSchema("org.gnome.desktop.background"); + return; + } catch (e) { } + + gGConf = Cc["@mozilla.org/gnome-gconf-service;1"]. + getService(Ci.nsIGConfService); + + gShell = Cc["@mozilla.org/browser/shell-service;1"]. + getService(Ci.nsIShellService); + + // Save the original background color so that we can restore it + // after the test. + var origGConfColor = gGConf.getString(GCONF_BG_COLOR_KEY); + + try { + + checkGConfToShellColor("#000", "#000000"); + checkGConfToShellColor("#00f", "#0000FF"); + checkGConfToShellColor("#b2f", "#BB22FF"); + checkGConfToShellColor("#fff", "#FFFFFF"); + + checkGConfToShellColor("#000000", "#000000"); + checkGConfToShellColor("#0000ff", "#0000FF"); + checkGConfToShellColor("#b002f0", "#B002F0"); + checkGConfToShellColor("#ffffff", "#FFFFFF"); + + checkGConfToShellColor("#000000000", "#000000"); + checkGConfToShellColor("#00f00f00f", "#000000"); + checkGConfToShellColor("#aaabbbccc", "#AABBCC"); + checkGConfToShellColor("#fffffffff", "#FFFFFF"); + + checkGConfToShellColor("#000000000000", "#000000"); + checkGConfToShellColor("#000f000f000f", "#000000"); + checkGConfToShellColor("#00ff00ff00ff", "#000000"); + checkGConfToShellColor("#aaaabbbbcccc", "#AABBCC"); + checkGConfToShellColor("#111122223333", "#112233"); + checkGConfToShellColor("#ffffffffffff", "#FFFFFF"); + + checkShellToGConfColor("#000000", "#000000000000"); + checkShellToGConfColor("#0000FF", "#00000000ffff"); + checkShellToGConfColor("#FFFFFF", "#ffffffffffff"); + checkShellToGConfColor("#0A0B0C", "#0a0a0b0b0c0c"); + checkShellToGConfColor("#A0B0C0", "#a0a0b0b0c0c0"); + checkShellToGConfColor("#AABBCC", "#aaaabbbbcccc"); + + } finally { + gGConf.setString(GCONF_BG_COLOR_KEY, origGConfColor); + } +} diff --git a/browser/components/shell/test/unit/xpcshell.ini b/browser/components/shell/test/unit/xpcshell.ini new file mode 100644 index 000000000..be00037e0 --- /dev/null +++ b/browser/components/shell/test/unit/xpcshell.ini @@ -0,0 +1,7 @@ +[DEFAULT] +head = +tail = +firefox-appdir = browser +skip-if = toolkit == 'android' + +[test_421977.js] |