diff options
Diffstat (limited to 'application/basilisk/components/shell/nsWindowsShellService.cpp')
-rw-r--r-- | application/basilisk/components/shell/nsWindowsShellService.cpp | 1280 |
1 files changed, 1280 insertions, 0 deletions
diff --git a/application/basilisk/components/shell/nsWindowsShellService.cpp b/application/basilisk/components/shell/nsWindowsShellService.cpp new file mode 100644 index 000000000..d569796d3 --- /dev/null +++ b/application/basilisk/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 BasiliskHTML +// +// as aliases to the class: +// +// HKCU\SOFTWARE\Classes\BasiliskHTML\ +// 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\BasiliskURL\ (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 Basilisk appear in the Start Menu as the +// browser: +// +// HKCU\SOFTWARE\Clients\StartMenuInternet\BASILISK.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"Basilisk" +#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 +// Basilisk 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 Basilisk 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("BasiliskHTML", SOC), VAL_OPEN, OLD_VAL_OPEN }, + + // Protocol Handler Class - for Vista and above + { MAKE_KEY_NAME1("BasiliskURL", 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 Basilisk 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\\BasiliskHTML", SOD) }, + + // Protocol Handler Class - for Vista and above + { MAKE_KEY_NAME1("Software\\Classes\\BasiliskURL", SOD) }, + + // Protocol Handlers + { MAKE_KEY_NAME1("Software\\Classes\\FTP", SOD) }, + { MAKE_KEY_NAME1("Software\\Classes\\HTTP", SOD) }, + { MAKE_KEY_NAME1("Software\\Classes\\HTTPS", SOD) } +}; + +nsresult +GetHelperPath(nsAutoString& aPath) +{ + nsresult rv; + nsCOMPtr<nsIProperties> directoryService = + do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> appHelper; + rv = directoryService->Get(XRE_EXECUTABLE_FILE, + NS_GET_IID(nsIFile), + getter_AddRefs(appHelper)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = appHelper->SetNativeLeafName(NS_LITERAL_CSTRING("uninstall")); + NS_ENSURE_SUCCESS(rv, rv); + + rv = appHelper->AppendNative(NS_LITERAL_CSTRING("helper.exe")); + NS_ENSURE_SUCCESS(rv, rv); + + rv = appHelper->GetPath(aPath); + + aPath.Insert(L'"', 0); + aPath.Append(L'"'); + return rv; +} + +nsresult +LaunchHelper(nsAutoString& aPath) +{ + STARTUPINFOW si = {sizeof(si), 0}; + PROCESS_INFORMATION pi = {0}; + + if (!CreateProcessW(nullptr, (LPWSTR)aPath.get(), nullptr, nullptr, FALSE, + 0, nullptr, nullptr, &si, &pi)) { + return NS_ERROR_FAILURE; + } + + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsShellService::ShortcutMaintenance() +{ + nsresult rv; + + // XXX App ids were updated to a constant install path hash, + // XXX this code can be removed after a few upgrade cycles. + + // Launch helper.exe so it can update the application user model ids on + // shortcuts in the user's taskbar and start menu. This keeps older pinned + // shortcuts grouped correctly after major updates. Note, we also do this + // through the upgrade installer script, however, this is the only place we + // have a chance to trap links created by users who do control the install/ + // update process of the browser. + + nsCOMPtr<nsIWinTaskbar> taskbarInfo = + do_GetService(NS_TASKBAR_CONTRACTID); + if (!taskbarInfo) // If we haven't built with win7 sdk features, this fails. + return NS_OK; + + // Avoid if this isn't Win7+ + bool isSupported = false; + taskbarInfo->GetAvailable(&isSupported); + if (!isSupported) + return NS_OK; + + nsAutoString appId; + if (NS_FAILED(taskbarInfo->GetDefaultGroupId(appId))) + return NS_ERROR_UNEXPECTED; + + NS_NAMED_LITERAL_CSTRING(prefName, "browser.taskbar.lastgroupid"); + nsCOMPtr<nsIPrefBranch> prefs = + do_GetService(NS_PREFSERVICE_CONTRACTID); + if (!prefs) + return NS_ERROR_UNEXPECTED; + + nsCOMPtr<nsISupportsString> prefString; + rv = prefs->GetComplexValue(prefName.get(), + NS_GET_IID(nsISupportsString), + getter_AddRefs(prefString)); + if (NS_SUCCEEDED(rv)) { + nsAutoString version; + prefString->GetData(version); + if (!version.IsEmpty() && version.Equals(appId)) { + // We're all good, get out of here. + return NS_OK; + } + } + // Update the version in prefs + prefString = + do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return rv; + + prefString->SetData(appId); + rv = prefs->SetComplexValue(prefName.get(), + NS_GET_IID(nsISupportsString), + prefString); + if (NS_FAILED(rv)) { + NS_WARNING("Couldn't set last user model id!"); + return NS_ERROR_UNEXPECTED; + } + + nsAutoString appHelperPath; + if (NS_FAILED(GetHelperPath(appHelperPath))) + return NS_ERROR_UNEXPECTED; + + appHelperPath.AppendLiteral(" /UpdateShortcutAppUserModelIds"); + + return LaunchHelper(appHelperPath); +} + +static bool +IsAARDefault(const RefPtr<IApplicationAssociationRegistration>& pAAR, + LPCWSTR aClassName) +{ + // Make sure the Prog ID matches what we have + LPWSTR registeredApp; + bool isProtocol = *aClassName != L'.'; + ASSOCIATIONTYPE queryType = isProtocol ? AT_URLPROTOCOL : AT_FILEEXTENSION; + HRESULT hr = pAAR->QueryCurrentDefault(aClassName, queryType, AL_EFFECTIVE, + ®isteredApp); + if (FAILED(hr)) { + return false; + } + + LPCWSTR progID = isProtocol ? L"BasiliskURL" : L"BasiliskHTML"; + 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 BasiliskURL and if aCheckAllTypes is set, then + * it also checks for BasiliskHTML. Note that those ProgIDs are shared + * by all Basilisk 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 Basilisk 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 Basilisk 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 Basilisk as the default browser. + *aIsDefaultBrowser = false; + return NS_OK; + } + } + } + + // Only check if Basilisk is the default browser on Vista and above if the + // previous checks show that Basilisk 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 Basilisk update this will check if Basilisk is the + // default browser and if dde is disabled for each handler + // and if it isn't disable it. When Basilisk 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 Basilisk 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 Basilisk 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 Basilisk 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 Basilisk 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\Basilisk\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; +} |