summaryrefslogtreecommitdiffstats
path: root/comm/mail/components/shell
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/components/shell')
-rw-r--r--comm/mail/components/shell/components.conf37
-rw-r--r--comm/mail/components/shell/moz.build43
-rw-r--r--comm/mail/components/shell/nsGNOMEShellService.cpp341
-rw-r--r--comm/mail/components/shell/nsGNOMEShellService.h49
-rw-r--r--comm/mail/components/shell/nsIShellService.idl52
-rw-r--r--comm/mail/components/shell/nsMacShellService.cpp156
-rw-r--r--comm/mail/components/shell/nsMacShellService.h36
-rw-r--r--comm/mail/components/shell/nsToolkitShellService.h23
-rw-r--r--comm/mail/components/shell/nsWindowsShellService.cpp329
-rw-r--r--comm/mail/components/shell/nsWindowsShellService.h51
-rw-r--r--comm/mail/components/shell/test/unit/test_shellService.js22
-rw-r--r--comm/mail/components/shell/test/unit/xpcshell.ini2
12 files changed, 1141 insertions, 0 deletions
diff --git a/comm/mail/components/shell/components.conf b/comm/mail/components/shell/components.conf
new file mode 100644
index 0000000000..7b7712522a
--- /dev/null
+++ b/comm/mail/components/shell/components.conf
@@ -0,0 +1,37 @@
+# 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/.
+
+Classes = []
+
+if buildconfig.substs["OS_ARCH"] == "WINNT":
+ Classes += [
+ {
+ "cid": "{02ebbe84-c179-4598-af18-1bf2c4bc1df9}",
+ "contract_ids": ["@mozilla.org/mail/shell-service;1"],
+ "type": "nsWindowsShellService",
+ "init_method": "Init",
+ "headers": ["/comm/mail/components/shell/nsWindowsShellService.h"],
+ },
+ ]
+
+if buildconfig.substs["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ Classes += [
+ {
+ "cid": "{bddef0f4-5e2d-4846-bdec-86d0781d8ded}",
+ "contract_ids": ["@mozilla.org/mail/shell-service;1"],
+ "type": "nsGNOMEShellService",
+ "init_method": "Init",
+ "headers": ["/comm/mail/components/shell/nsGNOMEShellService.h"],
+ },
+ ]
+
+if buildconfig.substs["OS_ARCH"] == "Darwin":
+ Classes += [
+ {
+ "cid": "{85a27035-b970-4079-b9d2-e21f69e6b21f}",
+ "contract_ids": ["@mozilla.org/mail/shell-service;1"],
+ "type": "nsMacShellService",
+ "headers": ["/comm/mail/components/shell/nsMacShellService.h"],
+ },
+ ]
diff --git a/comm/mail/components/shell/moz.build b/comm/mail/components/shell/moz.build
new file mode 100644
index 0000000000..6687759226
--- /dev/null
+++ b/comm/mail/components/shell/moz.build
@@ -0,0 +1,43 @@
+# 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/.
+
+DEFINES["MOZ_APP_NAME"] = '"%s"' % CONFIG["MOZ_APP_NAME"]
+
+XPIDL_SOURCES += [
+ "nsIShellService.idl",
+]
+
+XPIDL_MODULE = "shellservice"
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ SOURCES += [
+ "nsWindowsShellService.cpp",
+ ]
+ LOCAL_INCLUDES += [
+ "/other-licenses/nsis/Contrib/CityHash/cityhash",
+ ]
+
+elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ SOURCES += [
+ "nsMacShellService.cpp",
+ ]
+elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ SOURCES += [
+ "nsGNOMEShellService.cpp",
+ ]
+
+if SOURCES:
+ FINAL_LIBRARY = "mailcomps"
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
+
+XPCSHELL_TESTS_MANIFESTS += [
+ "test/unit/xpcshell.ini",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
diff --git a/comm/mail/components/shell/nsGNOMEShellService.cpp b/comm/mail/components/shell/nsGNOMEShellService.cpp
new file mode 100644
index 0000000000..f7381b2adc
--- /dev/null
+++ b/comm/mail/components/shell/nsGNOMEShellService.cpp
@@ -0,0 +1,341 @@
+/* -*- 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 "nsGNOMEShellService.h"
+#include "nsIGIOService.h"
+#include "nsCOMPtr.h"
+#include "nsIServiceManager.h"
+#include "prenv.h"
+#include "nsIFile.h"
+#include "nsIStringBundle.h"
+#include "nsIPromptService.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsEmbedCID.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Components.h"
+
+#include <glib.h>
+#include <limits.h>
+#include <stdlib.h>
+
+using mozilla::ArrayLength;
+
+static const char* const sMailProtocols[] = {"mailto", "mid"};
+
+static const char* const sNewsProtocols[] = {"news", "snews", "nntp"};
+
+static const char* const sFeedProtocols[] = {"feed"};
+
+static const char* const sCalendarProtocols[] = {"webcal", "webcals"};
+
+struct AppTypeAssociation {
+ uint16_t type;
+ const char* const* protocols;
+ unsigned int protocolsLength;
+ const char* mimeType;
+ const char* extensions;
+};
+
+static bool IsRunningAsASnap() {
+ // SNAP holds the path to the snap, use SNAP_NAME
+ // which is easier to parse.
+ const char* snap_name = PR_GetEnv("SNAP_NAME");
+
+ // return early if not set.
+ if (snap_name == nullptr) {
+ return false;
+ }
+
+ // snap_name as defined on https://snapcraft.io/thunderbird
+ return (strcmp(snap_name, "thunderbird") == 0);
+}
+
+static const AppTypeAssociation sAppTypes[] = {
+ {
+ nsIShellService::MAIL, sMailProtocols, ArrayLength(sMailProtocols),
+ "message/rfc822",
+ nullptr // don't associate .eml extension, as that breaks printing
+ // those
+ },
+ {nsIShellService::NEWS, sNewsProtocols, ArrayLength(sNewsProtocols),
+ nullptr, nullptr},
+ {nsIShellService::RSS, sFeedProtocols, ArrayLength(sFeedProtocols),
+ "application/rss+xml", "rss"},
+ {nsIShellService::CALENDAR, sCalendarProtocols,
+ ArrayLength(sCalendarProtocols), "text/calendar", "ics"}};
+
+nsGNOMEShellService::nsGNOMEShellService()
+ : mUseLocaleFilenames(false),
+ mCheckedThisSession(false),
+ mAppIsInPath(false) {}
+
+nsresult nsGNOMEShellService::Init() {
+ nsresult rv;
+
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+
+ if (!giovfs) 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<nsIFile> appPath;
+ rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR,
+ getter_AddRefs(appPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = appPath->AppendNative(nsLiteralCString(MOZ_APP_NAME));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = appPath->GetNativePath(mAppPath);
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(nsGNOMEShellService, nsIShellService, nsIToolkitShellService)
+
+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;
+}
+
+NS_IMETHODIMP
+nsGNOMEShellService::IsDefaultClient(bool aStartupCheck, uint16_t aApps,
+ bool* aIsDefaultClient) {
+ *aIsDefaultClient = true;
+
+ for (unsigned int i = 0; i < MOZ_ARRAY_LENGTH(sAppTypes); i++) {
+ if (aApps & sAppTypes[i].type)
+ *aIsDefaultClient &=
+ checkDefault(sAppTypes[i].protocols, sAppTypes[i].protocolsLength);
+ }
+
+ // If this is the first mail window, maintain internal state that we've
+ // checked this session (so that subsequent window opens don't show the
+ // default client dialog).
+ if (aStartupCheck) mCheckedThisSession = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGNOMEShellService::SetDefaultClient(bool aForAllUsers, uint16_t aApps) {
+ nsresult rv = NS_OK;
+ for (unsigned int i = 0; i < MOZ_ARRAY_LENGTH(sAppTypes); i++) {
+ if (aApps & sAppTypes[i].type) {
+ nsresult tmp =
+ MakeDefault(sAppTypes[i].protocols, sAppTypes[i].protocolsLength,
+ sAppTypes[i].mimeType, sAppTypes[i].extensions);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsGNOMEShellService::GetShouldCheckDefaultClient(bool* aResult) {
+ if (mCheckedThisSession) {
+ *aResult = false;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ return prefs->GetBoolPref("mail.shell.checkDefaultClient", aResult);
+}
+
+NS_IMETHODIMP
+nsGNOMEShellService::SetShouldCheckDefaultClient(bool aShouldCheck) {
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ return prefs->SetBoolPref("mail.shell.checkDefaultClient", aShouldCheck);
+}
+
+bool nsGNOMEShellService::KeyMatchesAppName(const char* aKeyValue) const {
+ gchar* commandPath;
+ if (mUseLocaleFilenames) {
+ gchar* nativePath = g_filename_from_utf8(aKeyValue, -1, NULL, NULL, NULL);
+ 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);
+
+ if (g_shell_parse_argv(command.get(), &argc, &argv, NULL)) {
+ command.Assign(argv[0]);
+ g_strfreev(argv);
+ } else {
+ return false;
+ }
+
+ return KeyMatchesAppName(command.get());
+}
+
+bool nsGNOMEShellService::checkDefault(const char* const* aProtocols,
+ unsigned int aLength) {
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+
+ nsAutoCString handler;
+ nsresult rv;
+
+ for (unsigned int i = 0; i < aLength; ++i) {
+ if (IsRunningAsASnap()) {
+ const gchar* argv[] = {"xdg-settings", "get",
+ "default-url-scheme-handler", aProtocols[i],
+ nullptr};
+ GSpawnFlags flags = static_cast<GSpawnFlags>(G_SPAWN_SEARCH_PATH |
+ G_SPAWN_STDERR_TO_DEV_NULL);
+ gchar* output = nullptr;
+ gint exit_status = 0;
+ if (!g_spawn_sync(nullptr, (gchar**)argv, nullptr, flags, nullptr,
+ nullptr, &output, nullptr, &exit_status, nullptr)) {
+ return false;
+ }
+ if (exit_status != 0) {
+ g_free(output);
+ return false;
+ }
+ if (strcmp(output, "thunderbird.desktop\n") == 0) {
+ g_free(output);
+ return true;
+ }
+ g_free(output);
+ return false;
+ }
+
+ if (giovfs) {
+ handler.Truncate();
+ nsCOMPtr<nsIHandlerApp> handlerApp;
+ rv = giovfs->GetAppForURIScheme(nsDependentCString(aProtocols[i]),
+ getter_AddRefs(handlerApp));
+ if (NS_FAILED(rv) || !handlerApp) {
+ return false;
+ }
+ nsCOMPtr<nsIGIOMimeApp> app = do_QueryInterface(handlerApp, &rv);
+ if (NS_FAILED(rv) || !app) {
+ return false;
+ }
+ rv = app->GetCommand(handler);
+ if (NS_SUCCEEDED(rv) && !CheckHandlerMatchesAppName(handler)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+nsresult nsGNOMEShellService::MakeDefault(const char* const* aProtocols,
+ unsigned int aProtocolsLength,
+ const char* aMimeType,
+ const char* aExtensions) {
+ nsAutoCString appKeyValue;
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+ 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");
+
+ if (IsRunningAsASnap()) {
+ for (unsigned int i = 0; i < aProtocolsLength; ++i) {
+ const gchar* argv[] = {"xdg-settings",
+ "set",
+ "default-url-scheme-handler",
+ aProtocols[i],
+ "thunderbird.desktop",
+ nullptr};
+ GSpawnFlags flags = static_cast<GSpawnFlags>(G_SPAWN_SEARCH_PATH |
+ G_SPAWN_STDOUT_TO_DEV_NULL |
+ G_SPAWN_STDERR_TO_DEV_NULL);
+ g_spawn_sync(nullptr, (gchar**)argv, nullptr, flags, nullptr, nullptr,
+ nullptr, nullptr, nullptr, nullptr);
+ }
+ }
+
+ nsresult rv;
+ if (giovfs) {
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::components::StringBundle::Service();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIStringBundle> brandBundle;
+ rv = bundleService->CreateBundle(BRAND_PROPERTIES,
+ getter_AddRefs(brandBundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString brandShortName;
+ brandBundle->GetStringFromName("brandShortName", brandShortName);
+
+ // use brandShortName as the application id.
+ NS_ConvertUTF16toUTF8 id(brandShortName);
+
+ nsCOMPtr<nsIGIOMimeApp> app;
+ rv = giovfs->CreateAppFromCommand(mAppPath, id, getter_AddRefs(app));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (unsigned int i = 0; i < aProtocolsLength; ++i) {
+ rv = app->SetAsDefaultForURIScheme(nsDependentCString(aProtocols[i]));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (aMimeType)
+ rv = app->SetAsDefaultForMimeType(nsDependentCString(aMimeType));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (aExtensions)
+ rv =
+ app->SetAsDefaultForFileExtensions(nsDependentCString(aExtensions));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return NS_OK;
+}
diff --git a/comm/mail/components/shell/nsGNOMEShellService.h b/comm/mail/components/shell/nsGNOMEShellService.h
new file mode 100644
index 0000000000..402eb31f41
--- /dev/null
+++ b/comm/mail/components/shell/nsGNOMEShellService.h
@@ -0,0 +1,49 @@
+/* -*- 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 "nsIShellService.h"
+#include "nsString.h"
+#include "nsToolkitShellService.h"
+
+#define BRAND_PROPERTIES "chrome://branding/locale/brand.properties"
+
+#define NS_MAILGNOMEINTEGRATION_CID \
+ { \
+ 0xbddef0f4, 0x5e2d, 0x4846, { \
+ 0xbd, 0xec, 0x86, 0xd0, 0x78, 0x1d, 0x8d, 0xed \
+ } \
+ }
+
+class nsGNOMEShellService : public nsIShellService,
+ public nsToolkitShellService {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISHELLSERVICE
+
+ nsresult Init();
+ nsGNOMEShellService();
+
+ protected:
+ virtual ~nsGNOMEShellService(){};
+
+ bool KeyMatchesAppName(const char* aKeyValue) const;
+ bool checkDefault(const char* const* aProtocols, unsigned int aLength);
+ nsresult MakeDefault(const char* const* aProtocols,
+ unsigned int aProtocolsLength, const char* mimeType,
+ const char* extensions);
+
+ private:
+ bool GetAppPathFromLauncher();
+ bool CheckHandlerMatchesAppName(const nsACString& handler) const;
+ bool mUseLocaleFilenames;
+ bool mCheckedThisSession;
+ nsCString mAppPath;
+ bool mAppIsInPath;
+};
+
+#endif
diff --git a/comm/mail/components/shell/nsIShellService.idl b/comm/mail/components/shell/nsIShellService.idl
new file mode 100644
index 0000000000..307cf9e48d
--- /dev/null
+++ b/comm/mail/components/shell/nsIShellService.idl
@@ -0,0 +1,52 @@
+/* -*- 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"
+
+[scriptable, uuid(95F53544-F445-48d1-B3A2-D54AA020BC3D)]
+interface nsIShellService : nsISupports
+{
+ /**
+ * app types we can be registered to handle
+ */
+ const unsigned short MAIL = 0x0001;
+ const unsigned short NEWS = 0x0002;
+ const unsigned short RSS = 0x0004;
+ const unsigned short CALENDAR = 0x0008;
+
+ /**
+ * Determines whether or not Thunderbird is the "Default Client" for the
+ * passed in app type.
+ *
+ * This is simply whether or not Thunderbid is registered to handle
+ * the url scheme associated with the app.
+ *
+ * @param aStartupCheck true if this is the check being performed
+ * by the first mail window at startup,
+ * false otherwise.
+ * @param aApps the application types being tested (Mail, News, RSS, etc.)
+ */
+ boolean isDefaultClient(in boolean aStartupCheck, in unsigned short aApps);
+
+ /**
+ * Registers Thunderbird as the "Default Mail Client" for the
+ * passed in app type.
+ *
+ * @param aForAllUsers Whether or not Thunderbird should attempt
+ * to become the default client for all
+ * users on a multi-user system.
+ * @param aApps the application types being tested (Mail, News, RSS, etc.)
+ */
+ void setDefaultClient(in boolean aForAllUsers, in unsigned short aApps);
+
+ /**
+ * Used to determine whether or not to show a "Set Default Client"
+ * query dialog. This attribute is true if the application is starting
+ * up and "mail.shell.checkDefaultClient" is true, otherwise it
+ * is false.
+ */
+ attribute boolean shouldCheckDefaultClient;
+};
diff --git a/comm/mail/components/shell/nsMacShellService.cpp b/comm/mail/components/shell/nsMacShellService.cpp
new file mode 100644
index 0000000000..383e3a2896
--- /dev/null
+++ b/comm/mail/components/shell/nsMacShellService.cpp
@@ -0,0 +1,156 @@
+/* -*- 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 "nsMacShellService.h"
+#include "nsCOMPtr.h"
+#include "nsIServiceManager.h"
+#include "nsIStringBundle.h"
+#include "nsIPromptService.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsString.h"
+#include "nsEmbedCID.h"
+
+// These Launch Services functions are undocumented. We're using them since
+// they're the only way to set the default opener for URLs
+extern "C" {
+// Returns the CFURL for application currently set as the default opener for
+// the given URL scheme. appURL must be released by the caller.
+extern OSStatus _LSCopyDefaultSchemeHandlerURL(CFStringRef scheme,
+ CFURLRef* appURL);
+extern OSStatus _LSSetDefaultSchemeHandlerURL(CFStringRef scheme,
+ CFURLRef appURL);
+extern OSStatus _LSSaveAndRefresh(void);
+}
+
+NS_IMPL_ISUPPORTS(nsMacShellService, nsIShellService, nsIToolkitShellService)
+
+nsMacShellService::nsMacShellService() : mCheckedThisSession(false) {}
+
+NS_IMETHODIMP
+nsMacShellService::IsDefaultClient(bool aStartupCheck, uint16_t aApps,
+ bool* aIsDefaultClient) {
+ *aIsDefaultClient = true;
+ if (aApps & nsIShellService::MAIL)
+ *aIsDefaultClient &= isDefaultHandlerForProtocol(CFSTR("mailto"));
+ if (aApps & nsIShellService::NEWS)
+ *aIsDefaultClient &= isDefaultHandlerForProtocol(CFSTR("news"));
+ if (aApps & nsIShellService::RSS)
+ *aIsDefaultClient &= isDefaultHandlerForProtocol(CFSTR("feed"));
+ if (aApps & nsIShellService::CALENDAR)
+ *aIsDefaultClient &= isDefaultHandlerForProtocol(CFSTR("webcal"));
+
+ // if this is the first mail window, maintain internal state that we've
+ // checked this session (so that subsequent window opens don't show the
+ // default client dialog.
+
+ if (aStartupCheck) mCheckedThisSession = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacShellService::SetDefaultClient(bool aForAllUsers, uint16_t aApps) {
+ nsresult rv = NS_OK;
+ if (aApps & nsIShellService::MAIL) {
+ rv = setAsDefaultHandlerForProtocol(CFSTR("mailto"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = setAsDefaultHandlerForProtocol(CFSTR("mid"));
+ }
+ if (NS_SUCCEEDED(rv) && aApps & nsIShellService::NEWS)
+ rv = setAsDefaultHandlerForProtocol(CFSTR("news"));
+ if (NS_SUCCEEDED(rv) && aApps & nsIShellService::RSS)
+ rv = setAsDefaultHandlerForProtocol(CFSTR("feed"));
+ if (NS_SUCCEEDED(rv) && aApps & nsIShellService::CALENDAR) {
+ rv = setAsDefaultHandlerForProtocol(CFSTR("webcal"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = setAsDefaultHandlerForProtocol(CFSTR("webcals"));
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMacShellService::GetShouldCheckDefaultClient(bool* aResult) {
+ if (mCheckedThisSession) {
+ *aResult = false;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ return prefs->GetBoolPref("mail.shell.checkDefaultClient", aResult);
+}
+
+NS_IMETHODIMP
+nsMacShellService::SetShouldCheckDefaultClient(bool aShouldCheck) {
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ return prefs->SetBoolPref("mail.shell.checkDefaultClient", aShouldCheck);
+}
+
+bool nsMacShellService::isDefaultHandlerForProtocol(CFStringRef aScheme) {
+ bool isDefault = false;
+ // Since neither Launch Services nor Internet Config actually differ between
+ // bundles which have the same bundle identifier (That is, if we set our
+ // URL of our bundle as the default handler for the given protocol,
+ // Launch Service might return the URL of another thunderbird bundle as the
+ // default handler for that protocol), we are comparing the identifiers of the
+ // bundles rather than their URLs.
+
+ CFStringRef tbirdID = ::CFBundleGetIdentifier(CFBundleGetMainBundle());
+ if (!tbirdID) {
+ // CFBundleGetIdentifier is expected to return NULL only if the specified
+ // bundle doesn't have a bundle identifier in its dictionary. In this case,
+ // that means a failure, since our bundle does have an identifier.
+ return isDefault;
+ }
+
+ ::CFRetain(tbirdID);
+
+ // Get the default handler URL of the given protocol
+ CFURLRef defaultHandlerURL;
+ OSStatus err = ::_LSCopyDefaultSchemeHandlerURL(aScheme, &defaultHandlerURL);
+
+ if (err == noErr) {
+ // Get a reference to the bundle (based on its URL)
+ CFBundleRef defaultHandlerBundle =
+ ::CFBundleCreate(NULL, defaultHandlerURL);
+ if (defaultHandlerBundle) {
+ CFStringRef defaultHandlerID =
+ ::CFBundleGetIdentifier(defaultHandlerBundle);
+ if (defaultHandlerID) {
+ ::CFRetain(defaultHandlerID);
+ // and compare it to our bundle identifier
+ isDefault = ::CFStringCompare(tbirdID, defaultHandlerID, 0) ==
+ kCFCompareEqualTo;
+ ::CFRelease(defaultHandlerID);
+ } else {
+ // If the bundle doesn't have an identifier in its info property list,
+ // it's not our bundle.
+ isDefault = false;
+ }
+
+ ::CFRelease(defaultHandlerBundle);
+ }
+
+ ::CFRelease(defaultHandlerURL);
+ } else {
+ // If |_LSCopyDefaultSchemeHandlerURL| failed, there's no default
+ // handler for the given protocol
+ isDefault = false;
+ }
+
+ ::CFRelease(tbirdID);
+ return isDefault;
+}
+
+nsresult nsMacShellService::setAsDefaultHandlerForProtocol(
+ CFStringRef aScheme) {
+ CFURLRef tbirdURL = ::CFBundleCopyBundleURL(CFBundleGetMainBundle());
+
+ ::_LSSetDefaultSchemeHandlerURL(aScheme, tbirdURL);
+ ::_LSSaveAndRefresh();
+ ::CFRelease(tbirdURL);
+
+ return NS_OK;
+}
diff --git a/comm/mail/components/shell/nsMacShellService.h b/comm/mail/components/shell/nsMacShellService.h
new file mode 100644
index 0000000000..7a301cb2fb
--- /dev/null
+++ b/comm/mail/components/shell/nsMacShellService.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 nsMacShellService_h_
+#define nsMacShellService_h_
+
+#include "nsIShellService.h"
+#include "nsString.h"
+#include "nsToolkitShellService.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#define NS_MAILMACINTEGRATION_CID \
+ { \
+ 0x85a27035, 0xb970, 0x4079, { \
+ 0xb9, 0xd2, 0xe2, 0x1f, 0x69, 0xe6, 0xb2, 0x1f \
+ } \
+ }
+
+class nsMacShellService : public nsIShellService, public nsToolkitShellService {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISHELLSERVICE
+ nsMacShellService();
+
+ protected:
+ bool isDefaultHandlerForProtocol(CFStringRef aScheme);
+ nsresult setAsDefaultHandlerForProtocol(CFStringRef aScheme);
+
+ private:
+ virtual ~nsMacShellService(){};
+ bool mCheckedThisSession;
+};
+#endif
diff --git a/comm/mail/components/shell/nsToolkitShellService.h b/comm/mail/components/shell/nsToolkitShellService.h
new file mode 100644
index 0000000000..160f9d7cbe
--- /dev/null
+++ b/comm/mail/components/shell/nsToolkitShellService.h
@@ -0,0 +1,23 @@
+/* -*- 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 nstoolkitshellservice_h____
+#define nstoolkitshellservice_h____
+
+#include "nsIToolkitShellService.h"
+
+class nsToolkitShellService : public nsIToolkitShellService {
+ public:
+ NS_IMETHOD IsDefaultClient(bool aStartupCheck, uint16_t aApps,
+ bool* aIsDefaultClient) = 0;
+
+ NS_IMETHODIMP IsDefaultApplication(bool* aIsDefaultClient) {
+ // This does some OS-specific checking: GConf on Linux, mailto/news protocol
+ // handler on Mac, registry and application association checks on Windows.
+ return IsDefaultClient(false, nsIShellService::MAIL, aIsDefaultClient);
+ }
+};
+
+#endif // nstoolkitshellservice_h____
diff --git a/comm/mail/components/shell/nsWindowsShellService.cpp b/comm/mail/components/shell/nsWindowsShellService.cpp
new file mode 100644
index 0000000000..e82cf0ed0e
--- /dev/null
+++ b/comm/mail/components/shell/nsWindowsShellService.cpp
@@ -0,0 +1,329 @@
+/* -*- 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 "nsIServiceManager.h"
+#include "nsICategoryManager.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsIPrefService.h"
+#include "windows.h"
+#include "shellapi.h"
+#include "nsIFile.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsUnicharUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIProperties.h"
+#include "nsString.h"
+
+#ifdef _WIN32_WINNT
+# undef _WIN32_WINNT
+#endif
+#define _WIN32_WINNT 0x0600
+#define INITGUID
+#include <shlobj.h>
+
+#include <mbstring.h>
+
+#ifndef MAX_BUF
+# define MAX_BUF 4096
+#endif
+
+#define REG_FAILED(val) (val != ERROR_SUCCESS)
+
+NS_IMPL_ISUPPORTS(nsWindowsShellService, nsIShellService,
+ nsIToolkitShellService)
+
+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 Mail Registry Settings
+///////////////////////////////////////////////////////////////////////////////
+
+typedef enum {
+ NO_SUBSTITUTION = 0x00,
+ APP_PATH_SUBSTITUTION = 0x01
+} SettingFlags;
+
+// APP_REG_NAME_MAIL and APP_REG_NAME_NEWS should be kept in synch with
+// AppRegNameMail and AppRegNameNews in the installer file: defines.nsi.in
+#define APP_REG_NAME_MAIL L"Thunderbird"
+#define APP_REG_NAME_NEWS L"Thunderbird (News)"
+#define APP_REG_NAME_CALENDAR L"Thunderbird (Calendar)"
+#define CLS_EML "ThunderbirdEML"
+#define CLS_MAILTOURL "Thunderbird.Url.mailto"
+#define CLS_MIDURL "Thunderbird.Url.mid"
+#define CLS_NEWSURL "Thunderbird.Url.news"
+#define CLS_FEEDURL "Thunderbird.Url.feed"
+#define CLS_WEBCALURL "Thunderbird.Url.webcal"
+#define CLS_ICS "ThunderbirdICS"
+#define SOP "\\shell\\open\\command"
+#define VAL_OPEN "\"%APPPATH%\" \"%1\""
+#define VAL_MAIL_OPEN "\"%APPPATH%\" -osint -mail \"%1\""
+#define VAL_COMPOSE_OPEN "\"%APPPATH%\" -osint -compose \"%1\""
+
+#define MAKE_KEY_NAME1(PREFIX, MID) PREFIX MID
+
+static SETTING gMailSettings[] = {
+ // File Extension Class
+ {".eml", "", CLS_EML, NO_SUBSTITUTION},
+
+ // File Extension Class
+ {MAKE_KEY_NAME1(CLS_EML, SOP), "", VAL_OPEN, APP_PATH_SUBSTITUTION},
+
+ // Protocol Handler Class - for Vista and above
+ {MAKE_KEY_NAME1(CLS_MAILTOURL, SOP), "", VAL_COMPOSE_OPEN,
+ APP_PATH_SUBSTITUTION},
+ {MAKE_KEY_NAME1(CLS_MIDURL, SOP), "", VAL_OPEN, APP_PATH_SUBSTITUTION},
+
+ // Protocol Handlers
+ {MAKE_KEY_NAME1("mailto", SOP), "", VAL_COMPOSE_OPEN,
+ APP_PATH_SUBSTITUTION},
+ {MAKE_KEY_NAME1("mid", SOP), "", VAL_OPEN, APP_PATH_SUBSTITUTION},
+};
+
+static SETTING gNewsSettings[] = {
+ // Protocol Handler Class - for Vista and above
+ {MAKE_KEY_NAME1(CLS_NEWSURL, SOP), "", VAL_MAIL_OPEN,
+ APP_PATH_SUBSTITUTION},
+
+ // Protocol Handlers
+ {MAKE_KEY_NAME1("news", SOP), "", VAL_MAIL_OPEN, APP_PATH_SUBSTITUTION},
+ {MAKE_KEY_NAME1("nntp", SOP), "", VAL_MAIL_OPEN, APP_PATH_SUBSTITUTION},
+};
+
+static SETTING gCalendarSettings[] = {
+ // File Extension Class
+ {".ics", "", CLS_ICS, NO_SUBSTITUTION},
+
+ // File Extension Class
+ {MAKE_KEY_NAME1(CLS_ICS, SOP), "", VAL_OPEN, APP_PATH_SUBSTITUTION},
+
+ // Protocol Handlers
+ {MAKE_KEY_NAME1(CLS_WEBCALURL, SOP), "", VAL_OPEN, APP_PATH_SUBSTITUTION},
+ {MAKE_KEY_NAME1("webcal", SOP), "", VAL_OPEN, APP_PATH_SUBSTITUTION},
+ {MAKE_KEY_NAME1("webcals", SOP), "", VAL_OPEN, APP_PATH_SUBSTITUTION},
+};
+
+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(NS_XPCOM_CURRENT_PROCESS_DIR, NS_GET_IID(nsIFile),
+ getter_AddRefs(appHelper));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = appHelper->Append(u"uninstall"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = appHelper->Append(u"helper.exe"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return appHelper->GetPath(aPath);
+}
+
+nsresult LaunchHelper(nsAutoString& aPath, nsAutoString& aParams) {
+ SHELLEXECUTEINFOW executeInfo = {0};
+
+ executeInfo.cbSize = sizeof(SHELLEXECUTEINFOW);
+ executeInfo.hwnd = NULL;
+ executeInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
+ executeInfo.lpDirectory = NULL;
+ executeInfo.lpFile = aPath.get();
+ executeInfo.lpParameters = aParams.get();
+ executeInfo.nShow = SW_SHOWNORMAL;
+
+ if (ShellExecuteExW(&executeInfo))
+ // Block until the program exits
+ WaitForSingleObject(executeInfo.hProcess, INFINITE);
+ else
+ return NS_ERROR_ABORT;
+
+ // We're going to ignore errors here since there's nothing we can do about
+ // them, and helper.exe seems to return non-zero ret on success.
+ return NS_OK;
+}
+
+nsresult nsWindowsShellService::Init() {
+ WCHAR appPath[MAX_BUF];
+ if (!::GetModuleFileNameW(0, appPath, MAX_BUF)) return NS_ERROR_FAILURE;
+
+ // Convert the path to a long path since GetModuleFileNameW returns the path
+ // that was used to launch the app which is not necessarily a long path.
+ if (!::GetLongPathNameW(appPath, appPath, MAX_BUF)) return NS_ERROR_FAILURE;
+
+ mAppLongPath = appPath;
+
+ return NS_OK;
+}
+
+nsWindowsShellService::nsWindowsShellService() : mCheckedThisSession(false) {}
+
+NS_IMETHODIMP
+nsWindowsShellService::IsDefaultClient(bool aStartupCheck, uint16_t aApps,
+ bool* aIsDefaultClient) {
+ // If this is the first mail window, maintain internal state that we've
+ // checked this session (so that subsequent window opens don't show the
+ // default client dialog).
+ if (aStartupCheck) mCheckedThisSession = true;
+
+ *aIsDefaultClient = true;
+
+ // for each type,
+ if (aApps & nsIShellService::MAIL) {
+ *aIsDefaultClient &=
+ TestForDefault(gMailSettings, sizeof(gMailSettings) / sizeof(SETTING));
+ // Only check if this app is default on Vista if the previous checks
+ // indicate that this app is the default.
+ if (*aIsDefaultClient)
+ IsDefaultClientVista(nsIShellService::MAIL, aIsDefaultClient);
+ }
+ if (aApps & nsIShellService::NEWS) {
+ *aIsDefaultClient &=
+ TestForDefault(gNewsSettings, sizeof(gNewsSettings) / sizeof(SETTING));
+ // Only check if this app is default on Vista if the previous checks
+ // indicate that this app is the default.
+ if (*aIsDefaultClient)
+ IsDefaultClientVista(nsIShellService::NEWS, aIsDefaultClient);
+ }
+ if (aApps & nsIShellService::CALENDAR) {
+ *aIsDefaultClient &= TestForDefault(
+ gCalendarSettings, sizeof(gCalendarSettings) / sizeof(SETTING));
+ // Only check if this app is default on Vista if the previous checks
+ // indicate that this app is the default.
+ if (*aIsDefaultClient)
+ IsDefaultClientVista(nsIShellService::CALENDAR, aIsDefaultClient);
+ }
+ // RSS / feed protocol shell integration is not working so return true
+ // until it is fixed (bug 445823).
+ if (aApps & nsIShellService::RSS) *aIsDefaultClient &= true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowsShellService::SetDefaultClient(bool aForAllUsers, uint16_t aApps) {
+ nsAutoString appHelperPath;
+ if (NS_FAILED(GetHelperPath(appHelperPath))) return NS_ERROR_FAILURE;
+
+ nsAutoString params;
+ if (aForAllUsers) {
+ params.AppendLiteral(" /SetAsDefaultAppGlobal");
+ } else {
+ params.AppendLiteral(" /SetAsDefaultAppUser");
+ if (aApps & nsIShellService::MAIL) params.AppendLiteral(" Mail");
+
+ if (aApps & nsIShellService::NEWS) params.AppendLiteral(" News");
+
+ if (aApps & nsIShellService::CALENDAR) params.AppendLiteral(" Calendar");
+ }
+
+ return LaunchHelper(appHelperPath, params);
+}
+
+NS_IMETHODIMP
+nsWindowsShellService::GetShouldCheckDefaultClient(bool* aResult) {
+ if (mCheckedThisSession) {
+ *aResult = false;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ return prefs->GetBoolPref("mail.shell.checkDefaultClient", aResult);
+}
+
+NS_IMETHODIMP
+nsWindowsShellService::SetShouldCheckDefaultClient(bool aShouldCheck) {
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ return prefs->SetBoolPref("mail.shell.checkDefaultClient", aShouldCheck);
+}
+
+/* helper routine. Iterate over the passed in settings object. */
+bool nsWindowsShellService::TestForDefault(SETTING aSettings[], int32_t aSize) {
+ bool isDefault = true;
+ char16_t currValue[MAX_BUF];
+ SETTING* end = aSettings + aSize;
+ for (SETTING* settings = aSettings; settings < end; ++settings) {
+ NS_ConvertUTF8toUTF16 dataLongPath(settings->valueData);
+ NS_ConvertUTF8toUTF16 key(settings->keyName);
+ NS_ConvertUTF8toUTF16 value(settings->valueName);
+ if (settings->flags & APP_PATH_SUBSTITUTION) {
+ int32_t offset = dataLongPath.Find(u"%APPPATH%");
+ dataLongPath.Replace(offset, 9, mAppLongPath);
+ }
+
+ ::ZeroMemory(currValue, sizeof(currValue));
+ HKEY theKey;
+ nsresult rv = OpenKeyForReading(HKEY_CLASSES_ROOT, key, &theKey);
+ if (NS_FAILED(rv)) {
+ // Key doesn't exist
+ isDefault = false;
+ break;
+ }
+
+ DWORD len = sizeof currValue;
+ DWORD result = ::RegQueryValueExW(theKey, value.get(), NULL, NULL,
+ (LPBYTE)currValue, &len);
+ // Close the key we opened.
+ ::RegCloseKey(theKey);
+ if (REG_FAILED(result) ||
+ !dataLongPath.Equals(currValue, nsCaseInsensitiveStringComparator)) {
+ // Key wasn't set, or was set to something else (something else became the
+ // default client)
+ isDefault = false;
+ break;
+ }
+ } // for each registry key we want to look at
+
+ return isDefault;
+}
+
+bool nsWindowsShellService::IsDefaultClientVista(uint16_t aApps,
+ bool* aIsDefaultClient) {
+ IApplicationAssociationRegistration* pAAR;
+
+ HRESULT hr = CoCreateInstance(
+ CLSID_ApplicationAssociationRegistration, NULL, CLSCTX_INPROC,
+ IID_IApplicationAssociationRegistration, (void**)&pAAR);
+
+ if (SUCCEEDED(hr)) {
+ BOOL isDefaultMail = true;
+ BOOL isDefaultNews = true;
+ BOOL isDefaultCalendar = true;
+ if (aApps & nsIShellService::MAIL)
+ pAAR->QueryAppIsDefaultAll(AL_EFFECTIVE, APP_REG_NAME_MAIL,
+ &isDefaultMail);
+ if (aApps & nsIShellService::NEWS)
+ pAAR->QueryAppIsDefaultAll(AL_EFFECTIVE, APP_REG_NAME_NEWS,
+ &isDefaultNews);
+ if (aApps & nsIShellService::CALENDAR)
+ pAAR->QueryAppIsDefaultAll(AL_EFFECTIVE, APP_REG_NAME_CALENDAR,
+ &isDefaultCalendar);
+
+ *aIsDefaultClient = isDefaultNews && isDefaultMail && isDefaultCalendar;
+
+ pAAR->Release();
+ return true;
+ }
+ return false;
+}
diff --git a/comm/mail/components/shell/nsWindowsShellService.h b/comm/mail/components/shell/nsWindowsShellService.h
new file mode 100644
index 0000000000..0dd1f760a8
--- /dev/null
+++ b/comm/mail/components/shell/nsWindowsShellService.h
@@ -0,0 +1,51 @@
+/* -*- 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 "nsIShellService.h"
+#include "nsIObserver.h"
+#include "nsString.h"
+#include "nsToolkitShellService.h"
+
+#include <ole2.h>
+#include <windows.h>
+
+#define NS_MAILWININTEGRATION_CID \
+ { \
+ 0x2ebbe84, 0xc179, 0x4598, { \
+ 0xaf, 0x18, 0x1b, 0xf2, 0xc4, 0xbc, 0x1d, 0xf9 \
+ } \
+ }
+
+typedef struct {
+ const char* keyName;
+ const char* valueName;
+ const char* valueData;
+
+ int32_t flags;
+} SETTING;
+
+class nsWindowsShellService : public nsIShellService,
+ public nsToolkitShellService {
+ public:
+ nsWindowsShellService();
+ nsresult Init();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISHELLSERVICE
+
+ protected:
+ bool TestForDefault(SETTING aSettings[], int32_t aSize);
+ bool IsDefaultClientVista(uint16_t aApps, bool* aIsDefaultClient);
+
+ private:
+ virtual ~nsWindowsShellService(){};
+ bool mCheckedThisSession;
+ nsAutoString mAppLongPath;
+};
+
+#endif
diff --git a/comm/mail/components/shell/test/unit/test_shellService.js b/comm/mail/components/shell/test/unit/test_shellService.js
new file mode 100644
index 0000000000..ebd9f85532
--- /dev/null
+++ b/comm/mail/components/shell/test/unit/test_shellService.js
@@ -0,0 +1,22 @@
+/* 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/. */
+
+/**
+ * Test setDefaultClient works for all supported types.
+ */
+add_task(function test_setDefaultClient() {
+ let shellSvc = Cc["@mozilla.org/mail/shell-service;1"].getService(
+ Ci.nsIShellService
+ );
+
+ let types = ["MAIL", "NEWS", "RSS", "CALENDAR"];
+
+ for (let type of types) {
+ shellSvc.setDefaultClient(false, shellSvc[type]);
+ ok(
+ shellSvc.isDefaultClient(false, shellSvc[type]),
+ `setDefaultClient works for type ${type}`
+ );
+ }
+});
diff --git a/comm/mail/components/shell/test/unit/xpcshell.ini b/comm/mail/components/shell/test/unit/xpcshell.ini
new file mode 100644
index 0000000000..0983e89525
--- /dev/null
+++ b/comm/mail/components/shell/test/unit/xpcshell.ini
@@ -0,0 +1,2 @@
+[test_shellService.js]
+skip-if = os == 'win' # setDefaultClient requires user confirmation on Windows.