Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sts=2 sw=2 et cin: */
/* 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 "WinUtils.h"
#include <knownfolders.h>
#include <winioctl.h>
#include "gfxPlatform.h"
#include "gfxUtils.h"
#include "nsWindow.h"
#include "nsWindowDefs.h"
#include "InputDeviceUtils.h"
#include "KeyboardLayout.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/BackgroundHangMonitor.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/dom/MouseEventBinding.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/DataSurfaceHelpers.h"
#include "mozilla/gfx/DisplayConfigWindows.h"
#include "mozilla/gfx/Logging.h"
#include "mozilla/Preferences.h"
#include "mozilla/ProfilerThreadSleep.h"
#include "mozilla/RefPtr.h"
#include "mozilla/SchedulerGroup.h"
#include "mozilla/WinHeaderOnlyUtils.h"
#include "mozilla/Unused.h"
#include "nsIContentPolicy.h"
#include "WindowsUIUtils.h"
#include "nsContentUtils.h"
#include "mozilla/Logging.h"
#include "nsString.h"
#include "nsDirectoryServiceUtils.h"
#include "imgIContainer.h"
#include "imgITools.h"
#include "nsNetUtil.h"
#include "nsIOutputStream.h"
#include "nsNetCID.h"
#include "prtime.h"
#ifdef MOZ_PLACES
# include "nsIFaviconService.h"
#endif
#include "nsIDownloader.h"
#include "nsIChannel.h"
#include "nsIThread.h"
#include "MainThreadUtils.h"
#include "nsLookAndFeel.h"
#include "nsUnicharUtils.h"
#include "nsWindowsHelpers.h"
#include "WinWindowOcclusionTracker.h"
#include <textstor.h>
#include "TSFTextStore.h"
#include <shellscalingapi.h>
#include <shlobj.h>
#include <shlwapi.h>
mozilla::LazyLogModule gWindowsLog("Widget");
#define LOG_E(...) MOZ_LOG(gWindowsLog, LogLevel::Error, (__VA_ARGS__))
#define LOG_D(...) MOZ_LOG(gWindowsLog, LogLevel::Debug, (__VA_ARGS__))
using namespace mozilla::gfx;
namespace mozilla {
namespace widget {
#ifdef MOZ_PLACES
NS_IMPL_ISUPPORTS(myDownloadObserver, nsIDownloadObserver)
NS_IMPL_ISUPPORTS(AsyncFaviconDataReady, nsIFaviconDataCallback)
#endif
NS_IMPL_ISUPPORTS(AsyncEncodeAndWriteIcon, nsIRunnable)
NS_IMPL_ISUPPORTS(AsyncDeleteAllFaviconsFromDisk, nsIRunnable)
const char FaviconHelper::kJumpListCacheDir[] = "jumpListCache";
const char FaviconHelper::kShortcutCacheDir[] = "shortcutCache";
struct CoTaskMemFreePolicy {
void operator()(void* aPtr) { ::CoTaskMemFree(aPtr); }
};
SetThreadDpiAwarenessContextProc WinUtils::sSetThreadDpiAwarenessContext = NULL;
EnableNonClientDpiScalingProc WinUtils::sEnableNonClientDpiScaling = NULL;
GetSystemMetricsForDpiProc WinUtils::sGetSystemMetricsForDpi = NULL;
bool WinUtils::sHasPackageIdentity = false;
using GetDpiForWindowProc = UINT(WINAPI*)(HWND);
static GetDpiForWindowProc sGetDpiForWindow = NULL;
/* static */
void WinUtils::Initialize() {
// Dpi-Awareness is not supported with Win32k Lockdown enabled, so we don't
// initialize DPI-related members and assert later that nothing accidently
// uses these static members
if (!IsWin32kLockedDown()) {
HMODULE user32Dll = ::GetModuleHandleW(L"user32");
if (user32Dll) {
auto getThreadDpiAwarenessContext =
(decltype(GetThreadDpiAwarenessContext)*)::GetProcAddress(
user32Dll, "GetThreadDpiAwarenessContext");
auto areDpiAwarenessContextsEqual =
(decltype(AreDpiAwarenessContextsEqual)*)::GetProcAddress(
user32Dll, "AreDpiAwarenessContextsEqual");
if (getThreadDpiAwarenessContext && areDpiAwarenessContextsEqual &&
areDpiAwarenessContextsEqual(
getThreadDpiAwarenessContext(),
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE)) {
// Only per-monitor v1 requires these workarounds.
sEnableNonClientDpiScaling =
(EnableNonClientDpiScalingProc)::GetProcAddress(
user32Dll, "EnableNonClientDpiScaling");
sSetThreadDpiAwarenessContext =
(SetThreadDpiAwarenessContextProc)::GetProcAddress(
user32Dll, "SetThreadDpiAwarenessContext");
}
sGetSystemMetricsForDpi = (GetSystemMetricsForDpiProc)::GetProcAddress(
user32Dll, "GetSystemMetricsForDpi");
sGetDpiForWindow =
(GetDpiForWindowProc)::GetProcAddress(user32Dll, "GetDpiForWindow");
}
}
sHasPackageIdentity = mozilla::HasPackageIdentity();
}
// static
LRESULT WINAPI WinUtils::NonClientDpiScalingDefWindowProcW(HWND hWnd, UINT msg,
WPARAM wParam,
LPARAM lParam) {
MOZ_DIAGNOSTIC_ASSERT(!IsWin32kLockedDown());
// NOTE: this function was copied out into the body of the pre-XUL skeleton
// UI window proc (PreXULSkeletonUI.cpp). If this function changes at any
// point, we should probably factor this out and use it from both locations.
if (msg == WM_NCCREATE && sEnableNonClientDpiScaling) {
sEnableNonClientDpiScaling(hWnd);
}
return ::DefWindowProcW(hWnd, msg, wParam, lParam);
}
// static
void WinUtils::LogW(const wchar_t* fmt, ...) {
va_list args = nullptr;
if (!lstrlenW(fmt)) {
return;
}
va_start(args, fmt);
int buflen = _vscwprintf(fmt, args);
wchar_t* buffer = new wchar_t[buflen + 1];
if (!buffer) {
va_end(args);
return;
}
vswprintf(buffer, buflen, fmt, args);
va_end(args);
// MSVC, including remote debug sessions
OutputDebugStringW(buffer);
OutputDebugStringW(L"\n");
int len =
WideCharToMultiByte(CP_ACP, 0, buffer, -1, nullptr, 0, nullptr, nullptr);
if (len) {
char* utf8 = new char[len];
if (WideCharToMultiByte(CP_ACP, 0, buffer, -1, utf8, len, nullptr,
nullptr) > 0) {
// desktop console
printf("%s\n", utf8);
NS_ASSERTION(gWindowsLog,
"Called WinUtils Log() but Widget "
"log module doesn't exist!");
MOZ_LOG(gWindowsLog, LogLevel::Error, ("%s", utf8));
}
delete[] utf8;
}
delete[] buffer;
}
// static
void WinUtils::Log(const char* fmt, ...) {
va_list args = nullptr;
if (!strlen(fmt)) {
return;
}
va_start(args, fmt);
int buflen = _vscprintf(fmt, args);
char* buffer = new char[buflen + 1];
if (!buffer) {
va_end(args);
return;
}
vsprintf(buffer, fmt, args);
va_end(args);
// MSVC, including remote debug sessions
OutputDebugStringA(buffer);
OutputDebugStringW(L"\n");
// desktop console
printf("%s\n", buffer);
NS_ASSERTION(gWindowsLog,
"Called WinUtils Log() but Widget "
"log module doesn't exist!");
MOZ_LOG(gWindowsLog, LogLevel::Error, ("%s", buffer));
delete[] buffer;
}
// static
float WinUtils::SystemDPI() {
// The result of GetDeviceCaps won't change dynamically, as it predates
// per-monitor DPI and support for on-the-fly resolution changes.
// Therefore, we only need to look it up once.
static float dpi = 0;
if (dpi <= 0) {
HDC screenDC = GetDC(nullptr);
dpi = GetDeviceCaps(screenDC, LOGPIXELSY);
ReleaseDC(nullptr, screenDC);
}
// Bug 1012487 - dpi can be 0 when the Screen DC is used off the
// main thread on windows. For now just assume a 100% DPI for this
// drawing call.
// XXX - fixme!
return dpi > 0 ? dpi : 96;
}
// static
double WinUtils::SystemScaleFactor() { return SystemDPI() / 96.0; }
typedef HRESULT(WINAPI* GETDPIFORMONITORPROC)(HMONITOR, MONITOR_DPI_TYPE, UINT*,
UINT*);
typedef HRESULT(WINAPI* GETPROCESSDPIAWARENESSPROC)(HANDLE,
PROCESS_DPI_AWARENESS*);
GETDPIFORMONITORPROC sGetDpiForMonitor;
GETPROCESSDPIAWARENESSPROC sGetProcessDpiAwareness;
static bool SlowIsPerMonitorDPIAware() {
// Intentionally leak the handle.
HMODULE shcore = LoadLibraryEx(L"shcore", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
if (shcore) {
sGetDpiForMonitor =
(GETDPIFORMONITORPROC)GetProcAddress(shcore, "GetDpiForMonitor");
sGetProcessDpiAwareness = (GETPROCESSDPIAWARENESSPROC)GetProcAddress(
shcore, "GetProcessDpiAwareness");
}
PROCESS_DPI_AWARENESS dpiAwareness;
return sGetDpiForMonitor && sGetProcessDpiAwareness &&
SUCCEEDED(
sGetProcessDpiAwareness(GetCurrentProcess(), &dpiAwareness)) &&
dpiAwareness == PROCESS_PER_MONITOR_DPI_AWARE;
}
/* static */
bool WinUtils::IsPerMonitorDPIAware() {
static bool perMonitorDPIAware = SlowIsPerMonitorDPIAware();
return perMonitorDPIAware;
}
/* static */
float WinUtils::MonitorDPI(HMONITOR aMonitor) {
if (IsPerMonitorDPIAware()) {
UINT dpiX, dpiY = 96;
sGetDpiForMonitor(aMonitor ? aMonitor : GetPrimaryMonitor(),
MDT_EFFECTIVE_DPI, &dpiX, &dpiY);
return dpiY;
}
// We're not per-monitor aware, use system DPI instead.
return SystemDPI();
}
/* static */
double WinUtils::LogToPhysFactor(HMONITOR aMonitor) {
return MonitorDPI(aMonitor) / 96.0;
}
/* static */
int32_t WinUtils::LogToPhys(HMONITOR aMonitor, double aValue) {
return int32_t(NS_round(aValue * LogToPhysFactor(aMonitor)));
}
/* static */
double WinUtils::LogToPhysFactor(HWND aWnd) {
// if there's an ancestor window, we want to share its DPI setting
HWND ancestor = ::GetAncestor(aWnd, GA_ROOTOWNER);
// The GetDpiForWindow api is not available everywhere where we run as
// per-monitor, but if it is available rely on it to tell us the scale
// factor of the window. See bug 1722085.
if (sGetDpiForWindow) {
UINT dpi = sGetDpiForWindow(ancestor ? ancestor : aWnd);
if (dpi > 0) {
return static_cast<double>(dpi) / 96.0;
}
}
return LogToPhysFactor(::MonitorFromWindow(ancestor ? ancestor : aWnd,
MONITOR_DEFAULTTOPRIMARY));
}
/* static */
HMONITOR
WinUtils::GetPrimaryMonitor() {
const POINT pt = {0, 0};
return ::MonitorFromPoint(pt, MONITOR_DEFAULTTOPRIMARY);
}
/* static */
HMONITOR
WinUtils::MonitorFromRect(const gfx::Rect& rect) {
// convert coordinates from desktop to device pixels for MonitorFromRect
double dpiScale =
IsPerMonitorDPIAware() ? 1.0 : LogToPhysFactor(GetPrimaryMonitor());
RECT globalWindowBounds = {NSToIntRound(dpiScale * rect.X()),
NSToIntRound(dpiScale * rect.Y()),
NSToIntRound(dpiScale * (rect.XMost())),
NSToIntRound(dpiScale * (rect.YMost()))};
return ::MonitorFromRect(&globalWindowBounds, MONITOR_DEFAULTTONEAREST);
}
/* static */
bool WinUtils::HasSystemMetricsForDpi() {
MOZ_DIAGNOSTIC_ASSERT(!IsWin32kLockedDown());
return (sGetSystemMetricsForDpi != NULL);
}
/* static */
int WinUtils::GetSystemMetricsForDpi(int nIndex, UINT dpi) {
MOZ_DIAGNOSTIC_ASSERT(!IsWin32kLockedDown());
if (HasSystemMetricsForDpi()) {
return sGetSystemMetricsForDpi(nIndex, dpi);
} else {
double scale = IsPerMonitorDPIAware() ? dpi / SystemDPI() : 1.0;
return NSToIntRound(::GetSystemMetrics(nIndex) * scale);
}
}
/* static */
gfx::MarginDouble WinUtils::GetUnwriteableMarginsForDeviceInInches(HDC aHdc) {
if (!aHdc) {
return gfx::MarginDouble();
}
int pixelsPerInchY = ::GetDeviceCaps(aHdc, LOGPIXELSY);
int marginTop = ::GetDeviceCaps(aHdc, PHYSICALOFFSETY);
int printableAreaHeight = ::GetDeviceCaps(aHdc, VERTRES);
int physicalHeight = ::GetDeviceCaps(aHdc, PHYSICALHEIGHT);
double marginTopInch = double(marginTop) / pixelsPerInchY;
double printableAreaHeightInch = double(printableAreaHeight) / pixelsPerInchY;
double physicalHeightInch = double(physicalHeight) / pixelsPerInchY;
double marginBottomInch =
physicalHeightInch - printableAreaHeightInch - marginTopInch;
int pixelsPerInchX = ::GetDeviceCaps(aHdc, LOGPIXELSX);
int marginLeft = ::GetDeviceCaps(aHdc, PHYSICALOFFSETX);
int printableAreaWidth = ::GetDeviceCaps(aHdc, HORZRES);
int physicalWidth = ::GetDeviceCaps(aHdc, PHYSICALWIDTH);
double marginLeftInch = double(marginLeft) / pixelsPerInchX;
double printableAreaWidthInch = double(printableAreaWidth) / pixelsPerInchX;
double physicalWidthInch = double(physicalWidth) / pixelsPerInchX;
double marginRightInch =
physicalWidthInch - printableAreaWidthInch - marginLeftInch;
return gfx::MarginDouble(marginTopInch, marginRightInch, marginBottomInch,
marginLeftInch);
}
#ifdef ACCESSIBILITY
/* static */
a11y::LocalAccessible* WinUtils::GetRootAccessibleForHWND(HWND aHwnd) {
nsWindow* window = GetNSWindowPtr(aHwnd);
if (!window) {
return nullptr;
}
return window->GetAccessible();
}
#endif // ACCESSIBILITY
/* static */
bool WinUtils::PeekMessage(LPMSG aMsg, HWND aWnd, UINT aFirstMessage,
UINT aLastMessage, UINT aOption) {
RefPtr<ITfMessagePump> msgPump = TSFTextStore::GetMessagePump();
if (msgPump) {
BOOL ret = FALSE;
HRESULT hr = msgPump->PeekMessageW(aMsg, aWnd, aFirstMessage, aLastMessage,
aOption, &ret);
NS_ENSURE_TRUE(SUCCEEDED(hr), false);
return ret;
}
return ::PeekMessageW(aMsg, aWnd, aFirstMessage, aLastMessage, aOption);
}
/* static */
bool WinUtils::GetMessage(LPMSG aMsg, HWND aWnd, UINT aFirstMessage,
UINT aLastMessage) {
RefPtr<ITfMessagePump> msgPump = TSFTextStore::GetMessagePump();
if (msgPump) {
BOOL ret = FALSE;
HRESULT hr =
msgPump->GetMessageW(aMsg, aWnd, aFirstMessage, aLastMessage, &ret);
NS_ENSURE_TRUE(SUCCEEDED(hr), false);
return ret;
}
return ::GetMessageW(aMsg, aWnd, aFirstMessage, aLastMessage);
}
#if defined(ACCESSIBILITY)
static DWORD GetWaitFlags() {
DWORD result = MWMO_INPUTAVAILABLE;
if (XRE_IsContentProcess()) {
result |= MWMO_ALERTABLE;
}
return result;
}
#endif
/* static */
void WinUtils::WaitForMessage(DWORD aTimeoutMs) {
#if defined(ACCESSIBILITY)
static const DWORD waitFlags = GetWaitFlags();
#else
const DWORD waitFlags = MWMO_INPUTAVAILABLE;
#endif
const DWORD waitStart = ::GetTickCount();
DWORD elapsed = 0;
while (true) {
if (aTimeoutMs != INFINITE) {
elapsed = ::GetTickCount() - waitStart;
}
if (elapsed >= aTimeoutMs) {
break;
}
DWORD result;
{
AUTO_PROFILER_THREAD_SLEEP;
result = ::MsgWaitForMultipleObjectsEx(0, NULL, aTimeoutMs - elapsed,
MOZ_QS_ALLEVENT, waitFlags);
}
NS_WARNING_ASSERTION(result != WAIT_FAILED, "Wait failed");
if (result == WAIT_TIMEOUT) {
break;
}
#if defined(ACCESSIBILITY)
if (result == WAIT_IO_COMPLETION) {
if (NS_IsMainThread()) {
// We executed an APC that would have woken up the hang monitor. Since
// there are no more APCs pending and we are now going to sleep again,
// we should notify the hang monitor.
mozilla::BackgroundHangMonitor().NotifyWait();
}
continue;
}
#endif // defined(ACCESSIBILITY)
// Sent messages (via SendMessage and friends) are processed differently
// than queued messages (via PostMessage); the destination window procedure
// of the sent message is called during (Get|Peek)Message. Since PeekMessage
// does not tell us whether it processed any sent messages, we need to query
// this ahead of time.
bool haveSentMessagesPending =
(HIWORD(::GetQueueStatus(QS_SENDMESSAGE)) & QS_SENDMESSAGE) != 0;
MSG msg = {0};
if (haveSentMessagesPending ||
::PeekMessageW(&msg, nullptr, 0, 0, PM_NOREMOVE)) {
break;
}
// The message is intended for another thread that has been synchronized
// with our input queue; yield to give other threads an opportunity to
// process the message. This should prevent busy waiting if resumed due
// to another thread's message.
::SwitchToThread();
}
}
/* static */
HWND WinUtils::GetTopLevelHWND(HWND aWnd, bool aStopIfNotChild,
bool aStopIfNotPopup) {
HWND curWnd = aWnd;
HWND topWnd = nullptr;
while (curWnd) {
topWnd = curWnd;
if (aStopIfNotChild) {
DWORD_PTR style = ::GetWindowLongPtrW(curWnd, GWL_STYLE);
VERIFY_WINDOW_STYLE(style);
if (!(style & WS_CHILD)) // first top-level window
break;
}
HWND upWnd = ::GetParent(curWnd); // Parent or owner (if has no parent)
// GetParent will only return the owner if the passed in window
// has the WS_POPUP style.
if (!upWnd && !aStopIfNotPopup) {
upWnd = ::GetWindow(curWnd, GW_OWNER);
}
curWnd = upWnd;
}
return topWnd;
}
// Map from native window handles to nsWindow structures. Does not AddRef.
// Inherently unsafe to access outside the main thread.
static nsTHashMap<HWND, nsWindow*> sExtantNSWindows;
/* static */
void WinUtils::SetNSWindowPtr(HWND aWnd, nsWindow* aWindow) {
MOZ_ASSERT(NS_IsMainThread());
if (!aWindow) {
sExtantNSWindows.Remove(aWnd);
} else {
sExtantNSWindows.InsertOrUpdate(aWnd, aWindow);
}
}
/* static */
nsWindow* WinUtils::GetNSWindowPtr(HWND aWnd) {
MOZ_ASSERT(NS_IsMainThread());
return sExtantNSWindows.Get(aWnd); // or nullptr
}
/* static */
bool WinUtils::IsOurProcessWindow(HWND aWnd) {
if (!aWnd) {
return false;
}
DWORD processId = 0;
::GetWindowThreadProcessId(aWnd, &processId);
return (processId == ::GetCurrentProcessId());
}
/* static */
HWND WinUtils::FindOurProcessWindow(HWND aWnd) {
for (HWND wnd = ::GetParent(aWnd); wnd; wnd = ::GetParent(wnd)) {
if (IsOurProcessWindow(wnd)) {
return wnd;
}
}
return nullptr;
}
static bool IsPointInWindow(HWND aWnd, const POINT& aPointInScreen) {
RECT bounds;
if (!::GetWindowRect(aWnd, &bounds)) {
return false;
}
return (aPointInScreen.x >= bounds.left && aPointInScreen.x < bounds.right &&
aPointInScreen.y >= bounds.top && aPointInScreen.y < bounds.bottom);
}
/**
* FindTopmostWindowAtPoint() returns the topmost child window (topmost means
* forground in this context) of aWnd.
*/
static HWND FindTopmostWindowAtPoint(HWND aWnd, const POINT& aPointInScreen) {
if (!::IsWindowVisible(aWnd) || !IsPointInWindow(aWnd, aPointInScreen)) {
return nullptr;
}
HWND childWnd = ::GetTopWindow(aWnd);
while (childWnd) {
HWND topmostWnd = FindTopmostWindowAtPoint(childWnd, aPointInScreen);
if (topmostWnd) {
return topmostWnd;
}
childWnd = ::GetNextWindow(childWnd, GW_HWNDNEXT);
}
return aWnd;
}
struct FindOurWindowAtPointInfo {
POINT mInPointInScreen;
HWND mOutWnd;
};
static BOOL CALLBACK FindOurWindowAtPointCallback(HWND aWnd, LPARAM aLPARAM) {
if (!WinUtils::IsOurProcessWindow(aWnd)) {
// This isn't one of our top-level windows; continue enumerating.
return TRUE;
}
// Get the top-most child window under the point. If there's no child
// window, and the point is within the top-level window, then the top-level
// window will be returned. (This is the usual case. A child window
// would be returned for plugins.)
FindOurWindowAtPointInfo* info =
reinterpret_cast<FindOurWindowAtPointInfo*>(aLPARAM);
HWND childWnd = FindTopmostWindowAtPoint(aWnd, info->mInPointInScreen);
if (!childWnd) {
// This window doesn't contain the point; continue enumerating.
return TRUE;
}
// Return the HWND and stop enumerating.
info->mOutWnd = childWnd;
return FALSE;
}
/* static */
HWND WinUtils::FindOurWindowAtPoint(const POINT& aPointInScreen) {
FindOurWindowAtPointInfo info;
info.mInPointInScreen = aPointInScreen;
info.mOutWnd = nullptr;
// This will enumerate all top-level windows in order from top to bottom.
EnumWindows(FindOurWindowAtPointCallback, reinterpret_cast<LPARAM>(&info));
return info.mOutWnd;
}
/* static */
UINT WinUtils::GetInternalMessage(UINT aNativeMessage) {
switch (aNativeMessage) {
case WM_MOUSEWHEEL:
return MOZ_WM_MOUSEVWHEEL;
case WM_MOUSEHWHEEL:
return MOZ_WM_MOUSEHWHEEL;
case WM_VSCROLL:
return MOZ_WM_VSCROLL;
case WM_HSCROLL:
return MOZ_WM_HSCROLL;
default:
return aNativeMessage;
}
}
/* static */
UINT WinUtils::GetNativeMessage(UINT aInternalMessage) {
switch (aInternalMessage) {
case MOZ_WM_MOUSEVWHEEL:
return WM_MOUSEWHEEL;
case MOZ_WM_MOUSEHWHEEL:
return WM_MOUSEHWHEEL;
case MOZ_WM_VSCROLL:
return WM_VSCROLL;
case MOZ_WM_HSCROLL:
return WM_HSCROLL;
default:
return aInternalMessage;
}
}
/* static */
uint16_t WinUtils::GetMouseInputSource() {
int32_t inputSource = dom::MouseEvent_Binding::MOZ_SOURCE_MOUSE;
LPARAM lParamExtraInfo = ::GetMessageExtraInfo();
if ((lParamExtraInfo & TABLET_INK_SIGNATURE) == TABLET_INK_CHECK) {
inputSource = (lParamExtraInfo & TABLET_INK_TOUCH)
? dom::MouseEvent_Binding::MOZ_SOURCE_TOUCH
: dom::MouseEvent_Binding::MOZ_SOURCE_PEN;
}
return static_cast<uint16_t>(inputSource);
}
/* static */
uint16_t WinUtils::GetMousePointerID() {
LPARAM lParamExtraInfo = ::GetMessageExtraInfo();
return lParamExtraInfo & TABLET_INK_ID_MASK;
}
/* static */
bool WinUtils::GetIsMouseFromTouch(EventMessage aEventMessage) {
const uint32_t MOZ_T_I_SIGNATURE = TABLET_INK_TOUCH | TABLET_INK_SIGNATURE;
const uint32_t MOZ_T_I_CHECK_TCH = TABLET_INK_TOUCH | TABLET_INK_CHECK;
return ((aEventMessage == eMouseMove || aEventMessage == eMouseDown ||
aEventMessage == eMouseUp || aEventMessage == eMouseAuxClick ||
aEventMessage == eMouseDoubleClick) &&
(GetMessageExtraInfo() & MOZ_T_I_SIGNATURE) == MOZ_T_I_CHECK_TCH);
}
/* static */
MSG WinUtils::InitMSG(UINT aMessage, WPARAM wParam, LPARAM lParam, HWND aWnd) {
MSG msg;
msg.message = aMessage;
msg.wParam = wParam;
msg.lParam = lParam;
msg.hwnd = aWnd;
return msg;
}
#ifdef MOZ_PLACES
/************************************************************************
* Constructs as AsyncFaviconDataReady Object
* @param aIOThread : the thread which performs the action
* @param aURLShortcut : Differentiates between (false)Jumplistcache and
* (true)Shortcutcache
* @param aRunnable : Executed in the aIOThread when the favicon cache is
* avaiable
************************************************************************/
AsyncFaviconDataReady::AsyncFaviconDataReady(
nsIURI* aNewURI, RefPtr<LazyIdleThread>& aIOThread, const bool aURLShortcut,
already_AddRefed<nsIRunnable> aRunnable)
: mNewURI(aNewURI),
mIOThread(aIOThread),
mRunnable(aRunnable),
mURLShortcut(aURLShortcut) {}
NS_IMETHODIMP
myDownloadObserver::OnDownloadComplete(nsIDownloader* downloader,
nsIRequest* request, nsresult status,
nsIFile* result) {
return NS_OK;
}
nsresult AsyncFaviconDataReady::OnFaviconDataNotAvailable(void) {
if (!mURLShortcut) {
return NS_OK;
}
nsCOMPtr<nsIFile> icoFile;
nsresult rv =
FaviconHelper::GetOutputIconPath(mNewURI, icoFile, mURLShortcut);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIURI> mozIconURI;
rv = NS_NewURI(getter_AddRefs(mozIconURI), "moz-icon://.html?size=32");
if (NS_FAILED(rv)) {
return rv;
}
nsCOMPtr<nsIChannel> channel;
rv = NS_NewChannel(getter_AddRefs(channel), mozIconURI,
nsContentUtils::GetSystemPrincipal(),
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
nsIContentPolicy::TYPE_INTERNAL_IMAGE);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIDownloadObserver> downloadObserver = new myDownloadObserver;
nsCOMPtr<nsIStreamListener> listener;
rv = NS_NewDownloader(getter_AddRefs(listener), downloadObserver, icoFile);
NS_ENSURE_SUCCESS(rv, rv);
return channel->AsyncOpen(listener);
}
NS_IMETHODIMP
AsyncFaviconDataReady::OnComplete(nsIURI* aFaviconURI, uint32_t aDataLen,
const uint8_t* aData,
const nsACString& aMimeType,
uint16_t aWidth) {
if (!aDataLen || !aData) {
if (mURLShortcut) {
OnFaviconDataNotAvailable();
}
return NS_OK;
}
nsCOMPtr<nsIFile> icoFile;
nsresult rv =
FaviconHelper::GetOutputIconPath(mNewURI, icoFile, mURLShortcut);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoString path;
rv = icoFile->GetPath(path);
NS_ENSURE_SUCCESS(rv, rv);
// Decode the image from the format it was returned to us in (probably PNG)
nsCOMPtr<imgIContainer> container;
nsCOMPtr<imgITools> imgtool = do_CreateInstance("@mozilla.org/image/tools;1");
rv = imgtool->DecodeImageFromBuffer(reinterpret_cast<const char*>(aData),
aDataLen, aMimeType,
getter_AddRefs(container));
NS_ENSURE_SUCCESS(rv, rv);
RefPtr<SourceSurface> surface = container->GetFrame(
imgIContainer::FRAME_FIRST,
imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY);
NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE);
RefPtr<DataSourceSurface> dataSurface;
IntSize size;
if (mURLShortcut &&
(surface->GetSize().width < 48 || surface->GetSize().height < 48)) {
// Create a 48x48 surface and paint the icon into the central rect.
size.width = std::max(surface->GetSize().width, 48);
size.height = std::max(surface->GetSize().height, 48);
dataSurface =
Factory::CreateDataSourceSurface(size, SurfaceFormat::B8G8R8A8);
NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);
DataSourceSurface::MappedSurface map;
if (!dataSurface->Map(DataSourceSurface::MapType::WRITE, &map)) {
return NS_ERROR_FAILURE;
}
RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData(
BackendType::CAIRO, map.mData, dataSurface->GetSize(), map.mStride,
dataSurface->GetFormat());
if (!dt) {
gfxWarning() << "AsyncFaviconDataReady::OnComplete failed in "
"CreateDrawTargetForData";
return NS_ERROR_OUT_OF_MEMORY;
}
dt->FillRect(Rect(0, 0, size.width, size.height),
ColorPattern(ToDeviceColor(sRGBColor::OpaqueWhite())));
IntPoint point;
point.x = (size.width - surface->GetSize().width) / 2;
point.y = (size.height - surface->GetSize().height) / 2;
dt->DrawSurface(surface,
Rect(point.x, point.y, surface->GetSize().width,
surface->GetSize().height),
Rect(Point(0, 0), Size(surface->GetSize().width,
surface->GetSize().height)));
dataSurface->Unmap();
} else {
// By using the input image surface's size, we may end up encoding
// to a different size than a 16x16 (or bigger for higher DPI) ICO, but
// Windows will resize appropriately for us. If we want to encode ourselves
// one day because we like our resizing better, we'd have to manually
// resize the image here and use GetSystemMetrics w/ SM_CXSMICON and
// SM_CYSMICON. We don't support resizing images asynchronously at the
// moment anyway so getting the DPI aware icon size won't help.
size.width = surface->GetSize().width;
size.height = surface->GetSize().height;
dataSurface = surface->GetDataSurface();
NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);
}
// Allocate a new buffer that we own and can use out of line in
// another thread.
UniquePtr<uint8_t[]> data = SurfaceToPackedBGRA(dataSurface);
if (!data) {
return NS_ERROR_OUT_OF_MEMORY;
}
int32_t stride = 4 * size.width;
// AsyncEncodeAndWriteIcon takes ownership of the heap allocated buffer
nsCOMPtr<nsIRunnable> event =
new AsyncEncodeAndWriteIcon(path, std::move(data), stride, size.width,
size.height, mRunnable.forget());
mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
return NS_OK;
}
#endif
// Warning: AsyncEncodeAndWriteIcon assumes ownership of the aData buffer passed
// in
AsyncEncodeAndWriteIcon::AsyncEncodeAndWriteIcon(
const nsAString& aIconPath, UniquePtr<uint8_t[]> aBuffer, uint32_t aStride,
uint32_t aWidth, uint32_t aHeight, already_AddRefed<nsIRunnable> aRunnable)
: mIconPath(aIconPath),
mBuffer(std::move(aBuffer)),
mRunnable(aRunnable),
mStride(aStride),
mWidth(aWidth),
mHeight(aHeight) {}
NS_IMETHODIMP AsyncEncodeAndWriteIcon::Run() {
MOZ_ASSERT(!NS_IsMainThread(), "Should not be called on the main thread.");
// Note that since we're off the main thread we can't use
// gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget()
RefPtr<DataSourceSurface> surface = Factory::CreateWrappingDataSourceSurface(
mBuffer.get(), mStride, IntSize(mWidth, mHeight),
SurfaceFormat::B8G8R8A8);
FILE* file = _wfopen(mIconPath.get(), L"wb");
if (!file) {
// Maybe the directory doesn't exist; try creating it, then fopen again.
nsresult rv = NS_ERROR_FAILURE;
nsCOMPtr<nsIFile> comFile = do_CreateInstance("@mozilla.org/file/local;1");
if (comFile) {
rv = comFile->InitWithPath(mIconPath);
if (NS_SUCCEEDED(rv)) {
nsCOMPtr<nsIFile> dirPath;
comFile->GetParent(getter_AddRefs(dirPath));
if (dirPath) {
rv = dirPath->Create(nsIFile::DIRECTORY_TYPE, 0777);
if (NS_SUCCEEDED(rv) || rv == NS_ERROR_FILE_ALREADY_EXISTS) {
file = _wfopen(mIconPath.get(), L"wb");
if (!file) {
rv = NS_ERROR_FAILURE;
}
}
}
}
}
if (!file) {
return rv;
}
}
nsresult rv = gfxUtils::EncodeSourceSurface(surface, ImageType::ICO, u""_ns,
gfxUtils::eBinaryEncode, file);
fclose(file);
NS_ENSURE_SUCCESS(rv, rv);
if (mRunnable) {
mRunnable->Run();
}
return rv;
}
AsyncEncodeAndWriteIcon::~AsyncEncodeAndWriteIcon() {}
AsyncDeleteAllFaviconsFromDisk::AsyncDeleteAllFaviconsFromDisk(
bool aIgnoreRecent)
: mIgnoreRecent(aIgnoreRecent) {
// We can't call FaviconHelper::GetICOCacheSecondsTimeout() on non-main
// threads, as it reads a pref, so cache its value here.
mIcoNoDeleteSeconds = FaviconHelper::GetICOCacheSecondsTimeout() + 600;
// Prepare the profile directory cache on the main thread, to ensure we wont
// do this on non-main threads.
Unused << NS_GetSpecialDirectory("ProfLDS",
getter_AddRefs(mJumpListCacheDir));
}
NS_IMETHODIMP AsyncDeleteAllFaviconsFromDisk::Run() {
if (!mJumpListCacheDir) {
return NS_ERROR_FAILURE;
}
// Construct the path of our jump list cache
nsresult rv = mJumpListCacheDir->AppendNative(
nsDependentCString(FaviconHelper::kJumpListCacheDir));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIDirectoryEnumerator> entries;
rv = mJumpListCacheDir->GetDirectoryEntries(getter_AddRefs(entries));
NS_ENSURE_SUCCESS(rv, rv);
// Loop through each directory entry and remove all ICO files found
do {
nsCOMPtr<nsIFile> currFile;
if (NS_FAILED(entries->GetNextFile(getter_AddRefs(currFile))) || !currFile)
break;
nsAutoString path;
if (NS_FAILED(currFile->GetPath(path))) continue;
if (StringTail(path, 4).LowerCaseEqualsASCII(".ico")) {
// Check if the cached ICO file exists
bool exists;
if (NS_FAILED(currFile->Exists(&exists)) || !exists) continue;
if (mIgnoreRecent) {
// Check to make sure the icon wasn't just recently created.
// If it was created recently, don't delete it yet.
int64_t fileModTime = 0;
rv = currFile->GetLastModifiedTime(&fileModTime);
fileModTime /= PR_MSEC_PER_SEC;
// If the icon is older than the regeneration time (+ 10 min to be
// safe), then it's old and we can get rid of it.
// This code is only hit directly after a regeneration.
int64_t nowTime = PR_Now() / int64_t(PR_USEC_PER_SEC);
if (NS_FAILED(rv) || (nowTime - fileModTime) < mIcoNoDeleteSeconds) {
continue;
}
}
// We found an ICO file that exists, so we should remove it
currFile->Remove(false);
}
} while (true);
return NS_OK;
}
AsyncDeleteAllFaviconsFromDisk::~AsyncDeleteAllFaviconsFromDisk() {}
/*
* (static) If the data is available, will return the path on disk where
* the favicon for page aFaviconPageURI is stored. If the favicon does not
* exist, or its cache is expired, this method will kick off an async request
* for the icon so that next time the method is called it will be available.
* @param aFaviconPageURI The URI of the page to obtain
* @param aICOFilePath The path of the icon file
* @param aIOThread The thread to perform the Fetch on
* @param aURLShortcut to distinguish between jumplistcache(false) and
* shortcutcache(true)
* @param aRunnable Executed in the aIOThread when the favicon cache is
* avaiable
*/
nsresult FaviconHelper::ObtainCachedIconFile(
nsCOMPtr<nsIURI> aFaviconPageURI, nsString& aICOFilePath,
RefPtr<LazyIdleThread>& aIOThread, bool aURLShortcut,
already_AddRefed<nsIRunnable> aRunnable) {
nsCOMPtr<nsIRunnable> runnable = aRunnable;
// Obtain the ICO file path
nsCOMPtr<nsIFile> icoFile;
nsresult rv = GetOutputIconPath(aFaviconPageURI, icoFile, aURLShortcut);
NS_ENSURE_SUCCESS(rv, rv);
// Check if the cached ICO file already exists
bool exists;
rv = icoFile->Exists(&exists);
NS_ENSURE_SUCCESS(rv, rv);
if (exists) {
// Obtain the file's last modification date in seconds
int64_t fileModTime = 0;
rv = icoFile->GetLastModifiedTime(&fileModTime);
fileModTime /= PR_MSEC_PER_SEC;
int32_t icoReCacheSecondsTimeout = GetICOCacheSecondsTimeout();
int64_t nowTime = PR_Now() / int64_t(PR_USEC_PER_SEC);
// If the last mod call failed or the icon is old then re-cache it
// This check is in case the favicon of a page changes
// the next time we try to build the jump list, the data will be available.
if (NS_FAILED(rv) || (nowTime - fileModTime) > icoReCacheSecondsTimeout) {
CacheIconFileFromFaviconURIAsync(aFaviconPageURI, icoFile, aIOThread,
aURLShortcut, runnable.forget());
return NS_ERROR_NOT_AVAILABLE;
}
} else {
// The file does not exist yet, obtain it async from the favicon service so
// that the next time we try to build the jump list it'll be available.
CacheIconFileFromFaviconURIAsync(aFaviconPageURI, icoFile, aIOThread,
aURLShortcut, runnable.forget());
return NS_ERROR_NOT_AVAILABLE;
}
// The icoFile is filled with a path that exists, get its path
rv = icoFile->GetPath(aICOFilePath);
return rv;
}
// Hash a URI using a cryptographic hash function (currently SHA-256)
// Output will be a base64-encoded string of the hash.
static nsresult HashURI(nsIURI* aUri, nsACString& aUriHash) {
nsAutoCString spec;
nsresult rv = aUri->GetSpec(spec);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsICryptoHash> cryptoHash =
do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = cryptoHash->Init(nsICryptoHash::SHA256);
NS_ENSURE_SUCCESS(rv, rv);
// Add some context to the hash to even further reduce the chances of
// collision. Note that we are hashing this string with its null-terminator.
const char kHashUriContext[] = "firefox-uri";
rv = cryptoHash->Update(reinterpret_cast<const uint8_t*>(kHashUriContext),
sizeof(kHashUriContext));
NS_ENSURE_SUCCESS(rv, rv);
rv = cryptoHash->Update(reinterpret_cast<const uint8_t*>(spec.BeginReading()),
spec.Length());
NS_ENSURE_SUCCESS(rv, rv);
rv = cryptoHash->Finish(true, aUriHash);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
// (static) Obtains the ICO file for the favicon at page aFaviconPageURI
// If successful, the file path on disk is in the format:
// <ProfLDS>\jumpListCache\<hash(aFaviconPageURI)>.ico
//
// We generate the name with a cryptographically secure hash function in order
// to ensure that malicious websites can't intentionally craft URLs to collide
// with legitimate websites.
nsresult FaviconHelper::GetOutputIconPath(nsCOMPtr<nsIURI> aFaviconPageURI,
nsCOMPtr<nsIFile>& aICOFile,
bool aURLShortcut) {
nsAutoCString inputURIHash;
nsresult rv = HashURI(aFaviconPageURI, inputURIHash);
NS_ENSURE_SUCCESS(rv, rv);
char* cur = inputURIHash.BeginWriting();
char* end = inputURIHash.EndWriting();
for (; cur < end; ++cur) {
if ('/' == *cur) {
*cur = '_';
}
}
// Obtain the local profile directory and construct the output icon file path
rv = NS_GetSpecialDirectory("ProfLDS", getter_AddRefs(aICOFile));
NS_ENSURE_SUCCESS(rv, rv);
if (!aURLShortcut)
rv = aICOFile->AppendNative(nsDependentCString(kJumpListCacheDir));
else
rv = aICOFile->AppendNative(nsDependentCString(kShortcutCacheDir));
NS_ENSURE_SUCCESS(rv, rv);
// Append the icon extension
inputURIHash.AppendLiteral(".ico");
rv = aICOFile->AppendNative(inputURIHash);
return rv;
}
// (static) Asynchronously creates a cached ICO file on disk for the favicon of
// page aFaviconPageURI and stores it to disk at the path of aICOFile.
nsresult FaviconHelper::CacheIconFileFromFaviconURIAsync(
nsCOMPtr<nsIURI> aFaviconPageURI, nsCOMPtr<nsIFile> aICOFile,
RefPtr<LazyIdleThread>& aIOThread, bool aURLShortcut,
already_AddRefed<nsIRunnable> aRunnable) {
nsCOMPtr<nsIRunnable> runnable = aRunnable;
#ifdef MOZ_PLACES
// Obtain the favicon service and get the favicon for the specified page
nsCOMPtr<nsIFaviconService> favIconSvc(
do_GetService("@mozilla.org/browser/favicon-service;1"));
NS_ENSURE_TRUE(favIconSvc, NS_ERROR_FAILURE);
nsCOMPtr<nsIFaviconDataCallback> callback =
new mozilla::widget::AsyncFaviconDataReady(
aFaviconPageURI, aIOThread, aURLShortcut, runnable.forget());
favIconSvc->GetFaviconDataForPage(aFaviconPageURI, callback, 0);
#endif
return NS_OK;
}
// Obtains the jump list 'ICO cache timeout in seconds' pref
int32_t FaviconHelper::GetICOCacheSecondsTimeout() {
// Only obtain the setting at most once from the pref service.
// In the rare case that 2 threads call this at the same
// time it is no harm and we will simply obtain the pref twice.
// None of the taskbar list prefs are currently updated via a
// pref observer so I think this should suffice.
const int32_t kSecondsPerDay = 86400;
static bool alreadyObtained = false;
static int32_t icoReCacheSecondsTimeout = kSecondsPerDay;
if (alreadyObtained) {
return icoReCacheSecondsTimeout;
}
// Obtain the pref
const char PREF_ICOTIMEOUT[] = "browser.taskbar.lists.icoTimeoutInSeconds";
icoReCacheSecondsTimeout =
Preferences::GetInt(PREF_ICOTIMEOUT, kSecondsPerDay);
alreadyObtained = true;
return icoReCacheSecondsTimeout;
}
/* static */
LayoutDeviceIntRegion WinUtils::ConvertHRGNToRegion(HRGN aRgn) {
NS_ASSERTION(aRgn, "Don't pass NULL region here");
LayoutDeviceIntRegion rgn;
DWORD size = ::GetRegionData(aRgn, 0, nullptr);
AutoTArray<uint8_t, 100> buffer;
buffer.SetLength(size);
RGNDATA* data = reinterpret_cast<RGNDATA*>(buffer.Elements());
if (!::GetRegionData(aRgn, size, data)) return rgn;
if (data->rdh.nCount > MAX_RECTS_IN_REGION) {
rgn = ToIntRect(data->rdh.rcBound);
return rgn;
}
RECT* rects = reinterpret_cast<RECT*>(data->Buffer);
for (uint32_t i = 0; i < data->rdh.nCount; ++i) {
RECT* r = rects + i;
rgn.Or(rgn, ToIntRect(*r));
}
return rgn;
}
LayoutDeviceIntRect WinUtils::ToIntRect(const RECT& aRect) {
return LayoutDeviceIntRect(aRect.left, aRect.top, aRect.right - aRect.left,
aRect.bottom - aRect.top);
}
/* static */
bool WinUtils::IsIMEEnabled(const InputContext& aInputContext) {
return IsIMEEnabled(aInputContext.mIMEState.mEnabled);
}
/* static */
bool WinUtils::IsIMEEnabled(IMEEnabled aIMEState) {
return aIMEState == IMEEnabled::Enabled;
}
/* static */
void WinUtils::SetupKeyModifiersSequence(nsTArray<KeyPair>* aArray,
uint32_t aModifiers, UINT aMessage) {
MOZ_ASSERT(!(aModifiers & nsIWidget::ALTGRAPH) ||
!(aModifiers & (nsIWidget::CTRL_L | nsIWidget::ALT_R)));
if (aMessage == WM_KEYUP) {
// If AltGr is released, ControlLeft key is released first, then,
// AltRight key is released.
if (aModifiers & nsIWidget::ALTGRAPH) {
aArray->AppendElement(
KeyPair(VK_CONTROL, VK_LCONTROL, ScanCode::eControlLeft));
aArray->AppendElement(KeyPair(VK_MENU, VK_RMENU, ScanCode::eAltRight));
}
for (uint32_t i = ArrayLength(sModifierKeyMap); i; --i) {
const uint32_t* map = sModifierKeyMap[i - 1];
if (aModifiers & map[0]) {
aArray->AppendElement(KeyPair(map[1], map[2], map[3]));
}
}
} else {
for (uint32_t i = 0; i < ArrayLength(sModifierKeyMap); ++i) {
const uint32_t* map = sModifierKeyMap[i];
if (aModifiers & map[0]) {
aArray->AppendElement(KeyPair(map[1], map[2], map[3]));
}
}
// If AltGr is pressed, ControlLeft key is pressed first, then,
// AltRight key is pressed.
if (aModifiers & nsIWidget::ALTGRAPH) {
aArray->AppendElement(
KeyPair(VK_CONTROL, VK_LCONTROL, ScanCode::eControlLeft));
aArray->AppendElement(KeyPair(VK_MENU, VK_RMENU, ScanCode::eAltRight));
}
}
}
/* static */
nsresult WinUtils::WriteBitmap(nsIFile* aFile, imgIContainer* aImage) {
RefPtr<SourceSurface> surface = aImage->GetFrame(
imgIContainer::FRAME_FIRST,
imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY);
NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE);
return WriteBitmap(aFile, surface);
}
/* static */
nsresult WinUtils::WriteBitmap(nsIFile* aFile, SourceSurface* surface) {
nsresult rv;
// 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;
bool hasAlpha = surface->GetFormat() == SurfaceFormat::B8G8R8A8;
// initialize these bitmap structs which we will later
// serialize directly to the head of the bitmap file
BITMAPV4HEADER bmi;
memset(&bmi, 0, sizeof(BITMAPV4HEADER));
bmi.bV4Size = sizeof(BITMAPV4HEADER);
bmi.bV4Width = width;
bmi.bV4Height = height;
bmi.bV4Planes = 1;
bmi.bV4BitCount = (WORD)bytesPerPixel * 8;
bmi.bV4V4Compression = hasAlpha ? BI_BITFIELDS : BI_RGB;
bmi.bV4SizeImage = bytesPerRow * height;
bmi.bV4CSType = LCS_sRGB;
if (hasAlpha) {
bmi.bV4RedMask = 0x00FF0000;
bmi.bV4GreenMask = 0x0000FF00;
bmi.bV4BlueMask = 0x000000FF;
bmi.bV4AlphaMask = 0xFF000000;
}
BITMAPFILEHEADER bf;
DWORD colormask[3];
bf.bfType = 0x4D42; // 'BM'
bf.bfReserved1 = 0;
bf.bfReserved2 = 0;
bf.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPV4HEADER) +
(hasAlpha ? sizeof(colormask) : 0);
bf.bfSize = bf.bfOffBits + bmi.bV4SizeImage;
// 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(BITMAPV4HEADER), &written);
if (written == sizeof(BITMAPV4HEADER)) {
if (hasAlpha) {
// color mask
colormask[0] = 0x00FF0000;
colormask[1] = 0x0000FF00;
colormask[2] = 0x000000FF;
stream->Write((const char*)colormask, sizeof(colormask), &written);
}
if (!hasAlpha || written == sizeof(colormask)) {
// 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;
}
// This is in use here and in dom/events/TouchEvent.cpp
/* static */
uint32_t WinUtils::IsTouchDeviceSupportPresent() {
int32_t touchCapabilities = ::GetSystemMetrics(SM_DIGITIZER);
int32_t touchFlags = NID_EXTERNAL_TOUCH | NID_INTEGRATED_TOUCH;
if (StaticPrefs::dom_w3c_pointer_events_scroll_by_pen_enabled()) {
touchFlags |= NID_EXTERNAL_PEN | NID_INTEGRATED_PEN;
}
return (touchCapabilities & NID_READY) && (touchCapabilities & touchFlags);
}
/* static */
uint32_t WinUtils::GetMaxTouchPoints() {
if (IsTouchDeviceSupportPresent()) {
return GetSystemMetrics(SM_MAXIMUMTOUCHES);
}
return 0;
}
/* static */
POWER_PLATFORM_ROLE
WinUtils::GetPowerPlatformRole() {
typedef POWER_PLATFORM_ROLE(WINAPI *
PowerDeterminePlatformRoleEx)(ULONG Version);
static PowerDeterminePlatformRoleEx power_determine_platform_role =
reinterpret_cast<PowerDeterminePlatformRoleEx>(::GetProcAddress(
::LoadLibraryW(L"PowrProf.dll"), "PowerDeterminePlatformRoleEx"));
POWER_PLATFORM_ROLE powerPlatformRole = PlatformRoleUnspecified;
if (!power_determine_platform_role) {
return powerPlatformRole;
}
return power_determine_platform_role(POWER_PLATFORM_ROLE_V2);
}
// static
bool WinUtils::GetAutoRotationState(AR_STATE* aRotationState) {
typedef BOOL(WINAPI * GetAutoRotationStateFunc)(PAR_STATE pState);
static GetAutoRotationStateFunc get_auto_rotation_state_func =
reinterpret_cast<GetAutoRotationStateFunc>(::GetProcAddress(
GetModuleHandleW(L"user32.dll"), "GetAutoRotationState"));
if (get_auto_rotation_state_func) {
ZeroMemory(aRotationState, sizeof(AR_STATE));
return get_auto_rotation_state_func(aRotationState);
}
return false;
}
// static
void WinUtils::GetClipboardFormatAsString(UINT aFormat, nsAString& aOutput) {
wchar_t buf[256] = {};
// Get registered format name and ensure the existence of a terminating '\0'
// if the registered name is more than 256 characters.
if (::GetClipboardFormatNameW(aFormat, buf, ARRAYSIZE(buf) - 1)) {
aOutput.Append(buf);
return;
}
// Standard clipboard formats
switch (aFormat) {
case CF_TEXT: // 1
aOutput.Append(u"CF_TEXT"_ns);
break;
case CF_BITMAP: // 2
aOutput.Append(u"CF_BITMAP"_ns);
break;
case CF_DIB: // 8
aOutput.Append(u"CF_DIB"_ns);
break;
case CF_UNICODETEXT: // 13
aOutput.Append(u"CF_UNICODETEXT"_ns);
break;
case CF_HDROP: // 15
aOutput.Append(u"CF_HDROP"_ns);
break;
case CF_DIBV5: // 17
aOutput.Append(u"CF_DIBV5"_ns);
break;
default:
aOutput.AppendPrintf("%u", aFormat);
break;
}
}
static bool IsTabletDevice() {
// Guarantees that:
// - The device has a touch screen.
// - It is used as a tablet which means that it has no keyboard connected.
// On Windows 10 it means that it is verifying with ConvertibleSlateMode.
if (WindowsUIUtils::GetInTabletMode()) {
return true;
}
if (!GetSystemMetrics(SM_MAXIMUMTOUCHES)) {
return false;
}
// If the device is docked, the user is treating the device as a PC.
if (GetSystemMetrics(SM_SYSTEMDOCKED)) {
return false;
}
// If the device is not supporting rotation, it's unlikely to be a tablet,
// a convertible or a detachable. See:
AR_STATE rotation_state;
if (WinUtils::GetAutoRotationState(&rotation_state) &&
(rotation_state & (AR_NOT_SUPPORTED | AR_LAPTOP | AR_NOSENSOR))) {
return false;
}
// PlatformRoleSlate was added in Windows 8+.
POWER_PLATFORM_ROLE role = WinUtils::GetPowerPlatformRole();
if (role == PlatformRoleMobile || role == PlatformRoleSlate) {
return !GetSystemMetrics(SM_CONVERTIBLESLATEMODE);
}
return false;
}
static bool SystemHasMouse() {
// As per MSDN, this value is rarely false because of virtual mice, and
// some machines report the existance of a mouse port as a mouse.
//
// We probably could try to distinguish if we wanted, but a virtual mouse
// might be there for a reason, and maybe we shouldn't assume we know
// better.
return !!::GetSystemMetrics(SM_MOUSEPRESENT);
}
/* static */
PointerCapabilities WinUtils::GetPrimaryPointerCapabilities() {
if (IsTabletDevice()) {
return PointerCapabilities::Coarse;
}
if (SystemHasMouse()) {
return PointerCapabilities::Fine | PointerCapabilities::Hover;
}
if (IsTouchDeviceSupportPresent()) {
return PointerCapabilities::Coarse;
}
return PointerCapabilities::None;
}
static bool SystemHasTouchscreen() {
int digitizerMetrics = ::GetSystemMetrics(SM_DIGITIZER);
return (digitizerMetrics & NID_INTEGRATED_TOUCH) ||
(digitizerMetrics & NID_EXTERNAL_TOUCH);
}
static bool SystemHasPenDigitizer() {
int digitizerMetrics = ::GetSystemMetrics(SM_DIGITIZER);
return (digitizerMetrics & NID_INTEGRATED_PEN) ||
(digitizerMetrics & NID_EXTERNAL_PEN);
}
/* static */
PointerCapabilities WinUtils::GetAllPointerCapabilities() {
PointerCapabilities pointerCapabilities = PointerCapabilities::None;
if (SystemHasTouchscreen()) {
pointerCapabilities |= PointerCapabilities::Coarse;
}
if (SystemHasPenDigitizer() || SystemHasMouse()) {
pointerCapabilities |=
PointerCapabilities::Fine | PointerCapabilities::Hover;
}
return pointerCapabilities;
}
void WinUtils::GetPointerExplanation(nsAString* aExplanation) {
// To support localization, we will return a comma-separated list of
// Fluent IDs
*aExplanation = u"pointing-device-none";
bool first = true;
auto append = [&](const char16_t* str) {
if (first) {
aExplanation->Truncate();
first = false;
} else {
aExplanation->Append(u",");
}
aExplanation->Append(str);
};
if (SystemHasTouchscreen()) {
append(u"pointing-device-touchscreen");
}
if (SystemHasPenDigitizer()) {
append(u"pointing-device-pen-digitizer");
}
if (SystemHasMouse()) {
append(u"pointing-device-mouse");
}
}
/* static */
bool WinUtils::ResolveJunctionPointsAndSymLinks(std::wstring& aPath) {
LOG_D("ResolveJunctionPointsAndSymLinks: Resolving path: %S", aPath.c_str());
wchar_t path[MAX_PATH] = {0};
nsAutoHandle handle(::CreateFileW(
aPath.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr));
if (handle == INVALID_HANDLE_VALUE) {
LOG_E("Failed to open file handle to resolve path. GetLastError=%lu",
GetLastError());
return false;
}
DWORD pathLen = GetFinalPathNameByHandleW(
handle, path, MAX_PATH, FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
if (pathLen == 0 || pathLen >= MAX_PATH) {
LOG_E("GetFinalPathNameByHandleW failed. GetLastError=%lu", GetLastError());
return false;
}
aPath = path;
// GetFinalPathNameByHandle sticks a '\\?\' in front of the path,
// but that confuses some APIs so strip it off. It will also put
// '\\?\UNC\' in front of network paths, we convert that to '\\'.
if (aPath.compare(0, 7, L"\\\\?\\UNC") == 0) {
aPath.erase(2, 6);
} else if (aPath.compare(0, 4, L"\\\\?\\") == 0) {
aPath.erase(0, 4);
}
LOG_D("ResolveJunctionPointsAndSymLinks: Resolved path to: %S",
aPath.c_str());
return true;
}
/* static */
bool WinUtils::ResolveJunctionPointsAndSymLinks(nsIFile* aPath) {
MOZ_ASSERT(aPath);
nsAutoString filePath;
nsresult rv = aPath->GetPath(filePath);
if (NS_WARN_IF(NS_FAILED(rv))) {
return false;
}
std::wstring resolvedPath(filePath.get());
if (!ResolveJunctionPointsAndSymLinks(resolvedPath)) {
return false;
}
rv = aPath->InitWithPath(nsDependentString(resolvedPath.c_str()));
if (NS_WARN_IF(NS_FAILED(rv))) {
return false;
}
return true;
}
/* static */
bool WinUtils::RunningFromANetworkDrive() {
wchar_t exePath[MAX_PATH];
if (!::GetModuleFileNameW(nullptr, exePath, MAX_PATH)) {
return false;
}
std::wstring exeString(exePath);
if (!widget::WinUtils::ResolveJunctionPointsAndSymLinks(exeString)) {
return false;
}
wchar_t volPath[MAX_PATH];
if (!::GetVolumePathNameW(exeString.c_str(), volPath, MAX_PATH)) {
return false;
}
return (::GetDriveTypeW(volPath) == DRIVE_REMOTE);
}
/* static */
bool WinUtils::CanonicalizePath(nsAString& aPath) {
wchar_t tempPath[MAX_PATH + 1];
if (!PathCanonicalizeW(tempPath,
(char16ptr_t)PromiseFlatString(aPath).get())) {
return false;
}
aPath = tempPath;
MOZ_ASSERT(aPath.Length() <= MAX_PATH);
return true;
}
/* static */
bool WinUtils::MakeLongPath(nsAString& aPath) {
wchar_t tempPath[MAX_PATH + 1];
DWORD longResult =
GetLongPathNameW((char16ptr_t)PromiseFlatString(aPath).get(), tempPath,
ArrayLength(tempPath));
if (longResult > ArrayLength(tempPath)) {
// Our buffer is too short, and we're guaranteeing <= MAX_PATH results.
return false;
} else if (longResult) {
// Success.
aPath = tempPath;
MOZ_ASSERT(aPath.Length() <= MAX_PATH);
}
// GetLongPathNameW returns 0 if the path is not found or is not rooted,
// but we shouldn't consider that a failure condition.
return true;
}
/* static */
bool WinUtils::UnexpandEnvVars(nsAString& aPath) {
wchar_t tempPath[MAX_PATH + 1];
// PathUnExpandEnvStringsW returns false if it doesn't make any
// substitutions. Silently continue using the unaltered path.
if (PathUnExpandEnvStringsW((char16ptr_t)PromiseFlatString(aPath).get(),
tempPath, ArrayLength(tempPath))) {
aPath = tempPath;
MOZ_ASSERT(aPath.Length() <= MAX_PATH);
}
return true;
}
/* static */
WinUtils::WhitelistVec WinUtils::BuildWhitelist() {
WhitelistVec result;
Unused << result.emplaceBack(
std::make_pair(nsString(u"%ProgramFiles%"_ns), nsDependentString()));
// When no substitution is required, set the void flag
result.back().second.SetIsVoid(true);
Unused << result.emplaceBack(
std::make_pair(nsString(u"%SystemRoot%"_ns), nsDependentString()));
result.back().second.SetIsVoid(true);
wchar_t tmpPath[MAX_PATH + 1] = {};
if (GetTempPath(MAX_PATH, tmpPath)) {
// GetTempPath's result always ends with a backslash, which we don't want
uint32_t tmpPathLen = wcslen(tmpPath);
if (tmpPathLen) {
tmpPath[tmpPathLen - 1] = 0;
}
nsAutoString cleanTmpPath(tmpPath);
if (UnexpandEnvVars(cleanTmpPath)) {
constexpr auto tempVar = u"%TEMP%"_ns;
Unused << result.emplaceBack(std::make_pair(
nsString(cleanTmpPath), nsDependentString(tempVar, 0)));
}
}
// If we add more items to the whitelist, ensure we still don't invoke an
// unnecessary heap allocation.
MOZ_ASSERT(result.length() <= kMaxWhitelistedItems);
return result;
}
/**
* This function provides an array of (system path, substitution) pairs that are
* considered to be acceptable with respect to privacy, for the purposes of
* submitting within telemetry or crash reports.
*
* The substitution string's void flag may be set. If it is, no subsitution is
* necessary. Otherwise, the consumer should replace the system path with the
* substitution.
*
* @see PreparePathForTelemetry for an example of its usage.
*/
/* static */
const WinUtils::WhitelistVec& WinUtils::GetWhitelistedPaths() {
static WhitelistVec sWhitelist([]() -> WhitelistVec {
auto setClearFn = [ptr = &sWhitelist]() -> void {
RunOnShutdown([ptr]() -> void { ptr->clear(); },
ShutdownPhase::XPCOMShutdownFinal);
};
if (NS_IsMainThread()) {
setClearFn();
} else {
SchedulerGroup::Dispatch(NS_NewRunnableFunction(
"WinUtils::GetWhitelistedPaths", std::move(setClearFn)));
}
return BuildWhitelist();
}());
return sWhitelist;
}
/**
* This function is located here (as opposed to nsSystemInfo or elsewhere)
* because we need to gather this information as early as possible during
* startup.
*/
/* static */
bool WinUtils::GetAppInitDLLs(nsAString& aOutput) {
aOutput.Truncate();
HKEY hkey = NULL;
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows",
0, KEY_QUERY_VALUE, &hkey)) {
return false;
}
nsAutoRegKey key(hkey);
LONG status;
const wchar_t kLoadAppInitDLLs[] = L"LoadAppInit_DLLs";
DWORD loadAppInitDLLs = 0;
DWORD loadAppInitDLLsLen = sizeof(loadAppInitDLLs);
status = RegQueryValueExW(hkey, kLoadAppInitDLLs, nullptr, nullptr,
(LPBYTE)&loadAppInitDLLs, &loadAppInitDLLsLen);
if (status != ERROR_SUCCESS) {
return false;
}
if (!loadAppInitDLLs) {
// If loadAppInitDLLs is zero then AppInit_DLLs is disabled.
// In this case we'll return true along with an empty output string.
return true;
}
DWORD numBytes = 0;
const wchar_t kAppInitDLLs[] = L"AppInit_DLLs";
// Query for required buffer size
status = RegQueryValueExW(hkey, kAppInitDLLs, nullptr, nullptr, nullptr,
&numBytes);
if (status != ERROR_SUCCESS) {
return false;
}
// Allocate the buffer and query for the actual data
mozilla::UniquePtr<wchar_t[]> data =
mozilla::MakeUnique<wchar_t[]>(numBytes / sizeof(wchar_t));
status = RegQueryValueExW(hkey, kAppInitDLLs, nullptr, nullptr,
(LPBYTE)data.get(), &numBytes);
if (status != ERROR_SUCCESS) {
return false;
}
// For each token, split up the filename components and then check the
// name of the file.
const wchar_t kDelimiters[] = L", ";
wchar_t* tokenContext = nullptr;
wchar_t* token = wcstok_s(data.get(), kDelimiters, &tokenContext);
while (token) {
nsAutoString cleanPath(token);
// Since these paths are short paths originating from the registry, we need
// to canonicalize them, lengthen them, and sanitize them before we can
// check them against the whitelist
if (PreparePathForTelemetry(cleanPath)) {
if (!aOutput.IsEmpty()) {
aOutput += L";";
}
aOutput += cleanPath;
}
token = wcstok_s(nullptr, kDelimiters, &tokenContext);
}
return true;
}
/* static */
bool WinUtils::PreparePathForTelemetry(nsAString& aPath,
PathTransformFlags aFlags) {
if (aFlags & PathTransformFlags::Canonicalize) {
if (!CanonicalizePath(aPath)) {
return false;
}
}
if (aFlags & PathTransformFlags::Lengthen) {
if (!MakeLongPath(aPath)) {
return false;
}
}
if (aFlags & PathTransformFlags::UnexpandEnvVars) {
if (!UnexpandEnvVars(aPath)) {
return false;
}
}
const WhitelistVec& whitelistedPaths = GetWhitelistedPaths();
for (uint32_t i = 0; i < whitelistedPaths.length(); ++i) {
const nsString& testPath = whitelistedPaths[i].first;
const nsDependentString& substitution = whitelistedPaths[i].second;
if (StringBeginsWith(aPath, testPath, nsCaseInsensitiveStringComparator)) {
if (!substitution.IsVoid()) {
aPath.Replace(0, testPath.Length(), substitution);
}
return true;
}
}
// For non-whitelisted paths, we strip the path component and just leave
// the filename. We can't use nsLocalFile to do this because these paths may
// begin with environment variables, and nsLocalFile doesn't like
// non-absolute paths.
const nsString& flatPath = PromiseFlatString(aPath);
LPCWSTR leafStart = ::PathFindFileNameW(flatPath.get());
ptrdiff_t cutLen = leafStart - flatPath.get();
if (cutLen) {
aPath.Cut(0, cutLen);
} else if (aFlags & PathTransformFlags::RequireFilePath) {
return false;
}
return true;
}
nsString WinUtils::GetPackageFamilyName() {
nsString rv;
UniquePtr<wchar_t[]> packageIdentity = mozilla::GetPackageFamilyName();
if (packageIdentity) {
rv = packageIdentity.get();
}
return rv;
}
bool WinUtils::GetClassName(HWND aHwnd, nsAString& aClassName) {
const int bufferLength = 256;
aClassName.SetLength(bufferLength);
int length = ::GetClassNameW(aHwnd, (char16ptr_t)aClassName.BeginWriting(),
bufferLength);
if (length == 0) {
return false;
}
MOZ_RELEASE_ASSERT(length <= (bufferLength - 1));
aClassName.Truncate(length);
return true;
}
static BOOL CALLBACK EnumUpdateWindowOcclusionProc(HWND aHwnd, LPARAM aLParam) {
const bool* const enable = reinterpret_cast<bool*>(aLParam);
nsWindow* window = WinUtils::GetNSWindowPtr(aHwnd);
if (window) {
window->MaybeEnableWindowOcclusion(*enable);
}
return TRUE;
}
void WinUtils::EnableWindowOcclusion(const bool aEnable) {
if (aEnable) {
WinWindowOcclusionTracker::Ensure();
}
::EnumWindows(EnumUpdateWindowOcclusionProc,
reinterpret_cast<LPARAM>(&aEnable));
}
bool WinUtils::GetTimezoneName(wchar_t* aBuffer) {
DYNAMIC_TIME_ZONE_INFORMATION tzInfo;
DWORD tzid = GetDynamicTimeZoneInformation(&tzInfo);
if (tzid == TIME_ZONE_ID_INVALID) {
return false;
}
wcscpy_s(aBuffer, 128, tzInfo.TimeZoneKeyName);
return true;
}
// There are undocumented APIs to query/change the system DPI settings found by
// https://github.com/lihas/ . We use those APIs only for testing purpose, i.e.
// in mochitests or some such. To avoid exposing them in our official release
// builds unexpectedly we restrict them only in debug builds.
#ifdef DEBUG
# define DISPLAYCONFIG_DEVICE_INFO_SET_SOURCE_DPI_SCALE (int)-4
# define DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_DPI_SCALE (int)-3
// Following two struts are copied from
/*
* struct DISPLAYCONFIG_SOURCE_DPI_SCALE_GET
* @brief used to fetch min, max, suggested, and currently applied DPI scaling
* values. All values are relative to the recommended DPI scaling value Note
* that DPI scaling is a property of the source, and not of target.
*/
struct DISPLAYCONFIG_SOURCE_DPI_SCALE_GET {
DISPLAYCONFIG_DEVICE_INFO_HEADER header;
/*
* @brief min value of DPI scaling is always 100, minScaleRel gives no. of
* steps down from recommended scaling eg. if minScaleRel is -3 => 100 is 3
* steps down from recommended scaling => recommended scaling is 175%
*/
int32_t minScaleRel;
/*
* @brief currently applied DPI scaling value wrt the recommended value. eg.
* if recommended value is 175%,
* => if curScaleRel == 0 the current scaling is 175%, if curScaleRel == -1,
* then current scale is 150%
*/
int32_t curScaleRel;
/*
* @brief maximum supported DPI scaling wrt recommended value
*/
int32_t maxScaleRel;
};
/*
* struct DISPLAYCONFIG_SOURCE_DPI_SCALE_SET
* @brief set DPI scaling value of a source
* Note that DPI scaling is a property of the source, and not of target.
*/
struct DISPLAYCONFIG_SOURCE_DPI_SCALE_SET {
DISPLAYCONFIG_DEVICE_INFO_HEADER header;
/*
* @brief The value we want to set. The value should be relative to the
* recommended DPI scaling value of source. eg. if scaleRel == 1, and
* recommended value is 175% => we are trying to set 200% scaling for the
* source
*/
int32_t scaleRel;
};
static int32_t sCurRelativeScaleStep = std::numeric_limits<int32_t>::max();
static LONG SetRelativeScaleStep(LUID aAdapterId, int32_t aRelativeScaleStep) {
DISPLAYCONFIG_SOURCE_DPI_SCALE_SET setDPIScale = {};
setDPIScale.header.adapterId = aAdapterId;
setDPIScale.header.type = (DISPLAYCONFIG_DEVICE_INFO_TYPE)
DISPLAYCONFIG_DEVICE_INFO_SET_SOURCE_DPI_SCALE;
setDPIScale.header.size = sizeof(setDPIScale);
setDPIScale.scaleRel = aRelativeScaleStep;
return DisplayConfigSetDeviceInfo(&setDPIScale.header);
}
nsresult WinUtils::SetHiDPIMode(bool aHiDPI) {
auto config = GetDisplayConfig();
if (!config) {
return NS_ERROR_NOT_AVAILABLE;
}
if (config->mPaths.empty()) {
return NS_ERROR_NOT_AVAILABLE;
}
DISPLAYCONFIG_SOURCE_DPI_SCALE_GET dpiScale = {};
dpiScale.header.adapterId = config->mPaths[0].targetInfo.adapterId;
dpiScale.header.type = (DISPLAYCONFIG_DEVICE_INFO_TYPE)
DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_DPI_SCALE;
dpiScale.header.size = sizeof(dpiScale);
LONG result = ::DisplayConfigGetDeviceInfo(&dpiScale.header);
if (result != ERROR_SUCCESS) {
return NS_ERROR_FAILURE;
}
if (dpiScale.minScaleRel == dpiScale.maxScaleRel) {
// We can't change the setting at all.
return NS_ERROR_NOT_AVAILABLE;
}
if (aHiDPI && dpiScale.curScaleRel == dpiScale.maxScaleRel) {
// We've already at the maximum level.
if (sCurRelativeScaleStep == std::numeric_limits<int32_t>::max()) {
sCurRelativeScaleStep = dpiScale.curScaleRel;
}
return NS_OK;
}
if (!aHiDPI && dpiScale.curScaleRel == dpiScale.minScaleRel) {
// We've already at the minimum level.
if (sCurRelativeScaleStep == std::numeric_limits<int32_t>::max()) {
sCurRelativeScaleStep = dpiScale.curScaleRel;
}
return NS_OK;
}
result = SetRelativeScaleStep(
config->mPaths[0].targetInfo.adapterId,
aHiDPI ? dpiScale.maxScaleRel : dpiScale.minScaleRel);
if (result != ERROR_SUCCESS) {
return NS_ERROR_FAILURE;
}
if (sCurRelativeScaleStep == std::numeric_limits<int>::max()) {
sCurRelativeScaleStep = dpiScale.curScaleRel;
}
return NS_OK;
}
nsresult WinUtils::RestoreHiDPIMode() {
if (sCurRelativeScaleStep == std::numeric_limits<int>::max()) {
// The DPI setting hasn't been changed.
return NS_ERROR_UNEXPECTED;
}
auto config = GetDisplayConfig();
if (!config) {
return NS_ERROR_NOT_AVAILABLE;
}
if (config->mPaths.empty()) {
return NS_ERROR_NOT_AVAILABLE;
}
LONG result = SetRelativeScaleStep(config->mPaths[0].targetInfo.adapterId,
sCurRelativeScaleStep);
sCurRelativeScaleStep = std::numeric_limits<int32_t>::max();
if (result != ERROR_SUCCESS) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
#endif
/* static */
const char* WinUtils::WinEventToEventName(UINT msg) {
const auto eventMsgInfo = mozilla::widget::gAllEvents.find(msg);
return eventMsgInfo != mozilla::widget::gAllEvents.end()
? eventMsgInfo->second.mStr
: nullptr;
}
// Note to testers and/or test-authors: on Windows 10, and possibly on other
// versions as well, supplying the `WS_EX_LAYOUTRTL` flag here has no effect
// whatsoever on child common-dialogs **unless the system UI locale is also set
// to an RTL language**.
//
// If it is, the flag is still required; otherwise, the picker dialog will be
// presented in English (or possibly some other LTR language) as a fallback.
ScopedRtlShimWindow::ScopedRtlShimWindow(nsIWidget* aParent) : mWnd(nullptr) {
NS_ENSURE_TRUE_VOID(aParent);
// Headless windows don't have HWNDs, but also probably shouldn't be launching
// print dialogs.
HWND const hwnd = (HWND)aParent->GetNativeData(NS_NATIVE_WINDOW);
NS_ENSURE_TRUE_VOID(hwnd);
nsWindow* const win = WinUtils::GetNSWindowPtr(hwnd);
NS_ENSURE_TRUE_VOID(win);
ATOM const wclass = ::GetClassWord(hwnd, GCW_ATOM);
mWnd = ::CreateWindowExW(
win->IsRTL() ? WS_EX_LAYOUTRTL : 0, (LPCWSTR)(uintptr_t)wclass, L"",
WS_CHILD, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
hwnd, nullptr, nsToolkit::mDllInstance, nullptr);
MOZ_ASSERT(mWnd);
}
ScopedRtlShimWindow::~ScopedRtlShimWindow() {
if (mWnd) {
::DestroyWindow(mWnd);
}
}
} // namespace widget
} // namespace mozilla