diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/mail/components/shell | |
parent | Initial commit. (diff) | |
download | thunderbird-upstream.tar.xz thunderbird-upstream.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | comm/mail/components/shell/components.conf | 37 | ||||
-rw-r--r-- | comm/mail/components/shell/moz.build | 43 | ||||
-rw-r--r-- | comm/mail/components/shell/nsGNOMEShellService.cpp | 341 | ||||
-rw-r--r-- | comm/mail/components/shell/nsGNOMEShellService.h | 49 | ||||
-rw-r--r-- | comm/mail/components/shell/nsIShellService.idl | 52 | ||||
-rw-r--r-- | comm/mail/components/shell/nsMacShellService.cpp | 156 | ||||
-rw-r--r-- | comm/mail/components/shell/nsMacShellService.h | 36 | ||||
-rw-r--r-- | comm/mail/components/shell/nsToolkitShellService.h | 23 | ||||
-rw-r--r-- | comm/mail/components/shell/nsWindowsShellService.cpp | 329 | ||||
-rw-r--r-- | comm/mail/components/shell/nsWindowsShellService.h | 51 | ||||
-rw-r--r-- | comm/mail/components/shell/test/unit/test_shellService.js | 22 | ||||
-rw-r--r-- | comm/mail/components/shell/test/unit/xpcshell.ini | 2 |
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. |