summaryrefslogtreecommitdiffstats
path: root/browser/components/shell
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/shell')
-rw-r--r--browser/components/shell/ShellService.jsm114
-rw-r--r--browser/components/shell/content/setDesktopBackground.js214
-rw-r--r--browser/components/shell/content/setDesktopBackground.xul84
-rw-r--r--browser/components/shell/jar.mn7
-rw-r--r--browser/components/shell/moz.build62
-rw-r--r--browser/components/shell/nsGNOMEShellService.cpp638
-rw-r--r--browser/components/shell/nsGNOMEShellService.h36
-rw-r--r--browser/components/shell/nsIGNOMEShellService.idl19
-rw-r--r--browser/components/shell/nsIMacShellService.idl15
-rw-r--r--browser/components/shell/nsIShellService.idl95
-rw-r--r--browser/components/shell/nsIWindowsShellService.idl17
-rw-r--r--browser/components/shell/nsMacShellService.cpp434
-rw-r--r--browser/components/shell/nsMacShellService.h32
-rw-r--r--browser/components/shell/nsSetDefaultBrowser.js30
-rw-r--r--browser/components/shell/nsSetDefaultBrowser.manifest3
-rw-r--r--browser/components/shell/nsShellService.h12
-rw-r--r--browser/components/shell/nsWindowsShellService.cpp1280
-rw-r--r--browser/components/shell/nsWindowsShellService.h37
-rw-r--r--browser/components/shell/test/.eslintrc.js7
-rw-r--r--browser/components/shell/test/browser.ini6
-rw-r--r--browser/components/shell/test/browser_420786.js88
-rw-r--r--browser/components/shell/test/browser_633221.js7
-rw-r--r--browser/components/shell/test/unit/.eslintrc.js7
-rw-r--r--browser/components/shell/test/unit/test_421977.js123
-rw-r--r--browser/components/shell/test/unit/xpcshell.ini7
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="&center.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,
+ &registeredApp);
+ 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]