diff options
Diffstat (limited to '')
-rw-r--r-- | browser/components/shell/nsWindowsShellService.cpp | 626 |
1 files changed, 626 insertions, 0 deletions
diff --git a/browser/components/shell/nsWindowsShellService.cpp b/browser/components/shell/nsWindowsShellService.cpp new file mode 100644 index 0000000000..87f10cb80f --- /dev/null +++ b/browser/components/shell/nsWindowsShellService.cpp @@ -0,0 +1,626 @@ +/* -*- 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 "BinaryPath.h" +#include "imgIContainer.h" +#include "imgIRequest.h" +#include "mozilla/RefPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsIContent.h" +#include "nsIImageLoadingContent.h" +#include "nsIOutputStream.h" +#include "nsIPrefService.h" +#include "nsIStringBundle.h" +#include "nsNetUtil.h" +#include "nsServiceManagerUtils.h" +#include "nsShellService.h" +#include "nsDirectoryServiceUtils.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIWindowsRegKey.h" +#include "nsUnicharUtils.h" +#include "nsIURLFormatter.h" +#include "nsXULAppAPI.h" +#include "mozilla/WindowsVersion.h" +#include "mozilla/dom/Element.h" +#include "mozilla/gfx/2D.h" +#include "WindowsDefaultBrowser.h" + +#include "windows.h" +#include "shellapi.h" +#include <propvarutil.h> +#include <propkey.h> + +#include <shlobj.h> +#include "WinUtils.h" + +#include <mbstring.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) + +#ifdef DEBUG +# define NS_ENSURE_HRESULT(hres, ret) \ + do { \ + HRESULT result = hres; \ + if (MOZ_UNLIKELY(FAILED(result))) { \ + mozilla::SmprintfPointer msg = mozilla::Smprintf( \ + "NS_ENSURE_HRESULT(%s, %s) failed with " \ + "result 0x%" PRIX32, \ + #hres, #ret, static_cast<uint32_t>(result)); \ + NS_WARNING(msg.get()); \ + return ret; \ + } \ + } while (false) +#else +# define NS_ENSURE_HRESULT(hres, ret) \ + if (MOZ_UNLIKELY(FAILED(hres))) return ret +#endif + +using mozilla::IsWin8OrLater; +using namespace mozilla; + +NS_IMPL_ISUPPORTS(nsWindowsShellService, nsIToolkitShellService, + nsIShellService, nsIWindowsShellService) + +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; +} + +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("uninstall"_ns); + NS_ENSURE_SUCCESS(rv, rv); + + rv = appHelper->AppendNative("helper.exe"_ns); + 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; +} + +static bool IsPathDefaultForClass( + const RefPtr<IApplicationAssociationRegistration>& pAAR, wchar_t* exePath, + LPCWSTR aClassName) { + // Make sure the Prog ID matches what we have + LPWSTR registeredApp; + bool isProtocol = *aClassName != L'.'; + ASSOCIATIONTYPE queryType = isProtocol ? AT_URLPROTOCOL : AT_FILEEXTENSION; + HRESULT hr = pAAR->QueryCurrentDefault(aClassName, queryType, AL_EFFECTIVE, + ®isteredApp); + if (FAILED(hr)) { + return false; + } + + LPCWSTR progID = isProtocol ? L"FirefoxURL" : L"FirefoxHTML"; + bool isDefault = !wcsnicmp(registeredApp, progID, wcslen(progID)); + + nsAutoString regAppName(registeredApp); + CoTaskMemFree(registeredApp); + + if (isDefault) { + // Make sure the application path for this progID is this installation. + regAppName.AppendLiteral("\\shell\\open\\command"); + HKEY theKey; + nsresult rv = OpenKeyForReading(HKEY_CLASSES_ROOT, regAppName, &theKey); + if (NS_FAILED(rv)) { + return false; + } + + wchar_t cmdFromReg[MAX_BUF] = L""; + DWORD len = sizeof(cmdFromReg); + DWORD res = ::RegQueryValueExW(theKey, nullptr, nullptr, nullptr, + (LPBYTE)cmdFromReg, &len); + ::RegCloseKey(theKey); + if (REG_FAILED(res)) { + return false; + } + + wchar_t fullCmd[MAX_BUF] = L""; + _snwprintf(fullCmd, MAX_BUF, L"\"%s\" -osint -url \"%%1\"", exePath); + + isDefault = _wcsicmp(fullCmd, cmdFromReg) == 0; + } + + return isDefault; +} + +NS_IMETHODIMP +nsWindowsShellService::IsDefaultBrowser(bool aForAllTypes, + bool* aIsDefaultBrowser) { + *aIsDefaultBrowser = false; + + RefPtr<IApplicationAssociationRegistration> pAAR; + HRESULT hr = CoCreateInstance( + CLSID_ApplicationAssociationRegistration, nullptr, CLSCTX_INPROC, + IID_IApplicationAssociationRegistration, getter_AddRefs(pAAR)); + if (FAILED(hr)) { + return NS_OK; + } + + wchar_t exePath[MAXPATHLEN] = L""; + nsresult rv = BinaryPath::GetLong(exePath); + + if (NS_FAILED(rv)) { + return NS_OK; + } + + *aIsDefaultBrowser = IsPathDefaultForClass(pAAR, exePath, L"http"); + if (*aIsDefaultBrowser && aForAllTypes) { + *aIsDefaultBrowser = IsPathDefaultForClass(pAAR, exePath, L".html"); + } + return NS_OK; +} + +nsresult nsWindowsShellService::LaunchControlPanelDefaultsSelectionUI() { + IApplicationAssociationRegistrationUI* pAARUI; + HRESULT hr = CoCreateInstance( + CLSID_ApplicationAssociationRegistrationUI, NULL, CLSCTX_INPROC, + IID_IApplicationAssociationRegistrationUI, (void**)&pAARUI); + if (SUCCEEDED(hr)) { + mozilla::UniquePtr<wchar_t[]> appRegName; + GetAppRegName(appRegName); + hr = pAARUI->LaunchAdvancedAssociationUI(appRegName.get()); + pAARUI->Release(); + } + return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE; +} + +nsresult nsWindowsShellService::LaunchControlPanelDefaultPrograms() { + return ::LaunchControlPanelDefaultPrograms() ? NS_OK : NS_ERROR_FAILURE; +} + +nsresult nsWindowsShellService::LaunchModernSettingsDialogDefaultApps() { + return ::LaunchModernSettingsDialogDefaultApps() ? NS_OK : NS_ERROR_FAILURE; +} + +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(u"app.support.baseURL"_ns, urlStr); + if (NS_FAILED(rv)) { + return rv; + } + if (!StringBeginsWith(urlStr, u"https://"_ns)) { + 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; + + HRESULT hr = SHOpenWithDialog(nullptr, &info); + if (SUCCEEDED(hr) || (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED))) { + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +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<gfx::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() == gfx::SurfaceFormat::B8G8R8A8 || + surface->GetFormat() == gfx::SurfaceFormat::B8G8R8X8); + + RefPtr<gfx::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); + + gfx::DataSourceSurface::MappedSurface map; + if (!dataSurface->Map(gfx::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(dom::Element* aElement, + int32_t aPosition, + const nsACString& aImageName) { + if (!aElement || !aElement->IsHTMLElement(nsGkAtoms::img)) { + // XXX write background loading stuff! + return NS_ERROR_NOT_AVAILABLE; + } + + nsresult rv; + nsCOMPtr<nsIImageLoadingContent> imageContent = + do_QueryInterface(aElement, &rv); + if (!imageContent) return rv; + + // get the image container + nsCOMPtr<imgIRequest> request; + rv = imageContent->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, + getter_AddRefs(request)); + if (!request) return rv; + + nsCOMPtr<imgIContainer> container; + rv = request->GetImage(getter_AddRefs(container)); + if (!container) return 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" + nsAutoString fileLeafName; + rv = shellBundle->GetStringFromName("desktopBackgroundLeafNameWin", + fileLeafName); + NS_ENSURE_SUCCESS(rv, rv); + + // get the profile root directory + nsCOMPtr<nsIFile> file; + rv = NS_GetSpecialDirectory(NS_APP_APPLICATION_REGISTRY_DIR, + getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + + // eventually, the path is "%APPDATA%\Mozilla\Firefox\Desktop Background.bmp" + rv = file->Append(fileLeafName); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString path; + rv = file->GetPath(path); + NS_ENSURE_SUCCESS(rv, rv); + + // write the bitmap to a file in the profile directory. + // We have to write old bitmap format for Windows 7 wallpapar support. + 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, + u"Control Panel\\Desktop"_ns, + 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; + case BACKGROUND_SPAN: + style.AssignLiteral("22"); + tile.Assign('0'); + break; + } + + rv = regKey->WriteStringValue(u"TileWallpaper"_ns, tile); + NS_ENSURE_SUCCESS(rv, rv); + rv = regKey->WriteStringValue(u"WallpaperStyle"_ns, 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::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, + u"Control Panel\\Colors"_ns, + 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(u"Background"_ns, nsDependentString(rgb)); + NS_ENSURE_SUCCESS(rv, rv); + + return regKey->Close(); +} + +NS_IMETHODIMP +nsWindowsShellService::CreateShortcut(nsIFile* aBinary, + const nsTArray<nsString>& aArguments, + const nsAString& aDescription, + nsIFile* aIconFile, + const nsAString& aAppUserModelId, + nsIFile* aTarget) { + NS_ENSURE_ARG(aBinary); + NS_ENSURE_ARG(aTarget); + + RefPtr<IShellLinkW> link; + HRESULT hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, + IID_IShellLinkW, getter_AddRefs(link)); + NS_ENSURE_HRESULT(hr, NS_ERROR_FAILURE); + + nsString path(aBinary->NativePath()); + link->SetPath(path.get()); + + if (!aDescription.IsEmpty()) { + link->SetDescription(PromiseFlatString(aDescription).get()); + } + + // TODO: Properly escape quotes in the string, see bug 1604287. + nsString arguments; + for (auto& arg : aArguments) { + arguments.AppendPrintf("\"%S\" ", arg.get()); + } + + link->SetArguments(arguments.get()); + + if (aIconFile) { + nsString icon(aIconFile->NativePath()); + link->SetIconLocation(icon.get(), 0); + } + + if (!aAppUserModelId.IsEmpty()) { + RefPtr<IPropertyStore> propStore; + hr = link->QueryInterface(IID_IPropertyStore, getter_AddRefs(propStore)); + NS_ENSURE_HRESULT(hr, NS_ERROR_FAILURE); + + PROPVARIANT pv; + if (FAILED(InitPropVariantFromString( + PromiseFlatString(aAppUserModelId).get(), &pv))) { + return NS_ERROR_FAILURE; + } + + hr = propStore->SetValue(PKEY_AppUserModel_ID, pv); + PropVariantClear(&pv); + NS_ENSURE_HRESULT(hr, NS_ERROR_FAILURE); + + hr = propStore->Commit(); + NS_ENSURE_HRESULT(hr, NS_ERROR_FAILURE); + } + + RefPtr<IPersistFile> persist; + hr = link->QueryInterface(IID_IPersistFile, getter_AddRefs(persist)); + NS_ENSURE_HRESULT(hr, NS_ERROR_FAILURE); + + nsString target(aTarget->NativePath()); + hr = persist->Save(target.get(), TRUE); + NS_ENSURE_HRESULT(hr, NS_ERROR_FAILURE); + + return NS_OK; +} + +nsWindowsShellService::nsWindowsShellService() {} + +nsWindowsShellService::~nsWindowsShellService() {} |