зеркало из https://github.com/mozilla/gecko-dev.git
2227 строки
76 KiB
C++
2227 строки
76 KiB
C++
/* -*- 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/Preferences.h"
|
|
#include "mozilla/RefPtr.h"
|
|
#include "mozilla/WindowsVersion.h"
|
|
#include "mozilla/Unused.h"
|
|
#include "nsIContentPolicy.h"
|
|
#include "nsIWindowsUIUtils.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 "nsIIconURI.h"
|
|
#include "nsIDownloader.h"
|
|
#include "nsINetUtil.h"
|
|
#include "nsIChannel.h"
|
|
#include "nsIObserver.h"
|
|
#include "imgIEncoder.h"
|
|
#include "nsIThread.h"
|
|
#include "MainThreadUtils.h"
|
|
#include "nsLookAndFeel.h"
|
|
#include "nsUnicharUtils.h"
|
|
#include "nsWindowsHelpers.h"
|
|
|
|
#ifdef NS_ENABLE_TSF
|
|
# include <textstor.h>
|
|
# include "TSFTextStore.h"
|
|
#endif // #ifdef NS_ENABLE_TSF
|
|
|
|
#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 {
|
|
|
|
#define ENTRY(_msg) \
|
|
{ #_msg, _msg }
|
|
EventMsgInfo gAllEvents[] = {ENTRY(WM_NULL),
|
|
ENTRY(WM_CREATE),
|
|
ENTRY(WM_DESTROY),
|
|
ENTRY(WM_MOVE),
|
|
ENTRY(WM_SIZE),
|
|
ENTRY(WM_ACTIVATE),
|
|
ENTRY(WM_SETFOCUS),
|
|
ENTRY(WM_KILLFOCUS),
|
|
ENTRY(WM_ENABLE),
|
|
ENTRY(WM_SETREDRAW),
|
|
ENTRY(WM_SETTEXT),
|
|
ENTRY(WM_GETTEXT),
|
|
ENTRY(WM_GETTEXTLENGTH),
|
|
ENTRY(WM_PAINT),
|
|
ENTRY(WM_CLOSE),
|
|
ENTRY(WM_QUERYENDSESSION),
|
|
ENTRY(WM_QUIT),
|
|
ENTRY(WM_QUERYOPEN),
|
|
ENTRY(WM_ERASEBKGND),
|
|
ENTRY(WM_SYSCOLORCHANGE),
|
|
ENTRY(WM_ENDSESSION),
|
|
ENTRY(WM_SHOWWINDOW),
|
|
ENTRY(WM_SETTINGCHANGE),
|
|
ENTRY(WM_DEVMODECHANGE),
|
|
ENTRY(WM_ACTIVATEAPP),
|
|
ENTRY(WM_FONTCHANGE),
|
|
ENTRY(WM_TIMECHANGE),
|
|
ENTRY(WM_CANCELMODE),
|
|
ENTRY(WM_SETCURSOR),
|
|
ENTRY(WM_MOUSEACTIVATE),
|
|
ENTRY(WM_CHILDACTIVATE),
|
|
ENTRY(WM_QUEUESYNC),
|
|
ENTRY(WM_GETMINMAXINFO),
|
|
ENTRY(WM_PAINTICON),
|
|
ENTRY(WM_ICONERASEBKGND),
|
|
ENTRY(WM_NEXTDLGCTL),
|
|
ENTRY(WM_SPOOLERSTATUS),
|
|
ENTRY(WM_DRAWITEM),
|
|
ENTRY(WM_MEASUREITEM),
|
|
ENTRY(WM_DELETEITEM),
|
|
ENTRY(WM_VKEYTOITEM),
|
|
ENTRY(WM_CHARTOITEM),
|
|
ENTRY(WM_SETFONT),
|
|
ENTRY(WM_GETFONT),
|
|
ENTRY(WM_SETHOTKEY),
|
|
ENTRY(WM_GETHOTKEY),
|
|
ENTRY(WM_QUERYDRAGICON),
|
|
ENTRY(WM_COMPAREITEM),
|
|
ENTRY(WM_GETOBJECT),
|
|
ENTRY(WM_COMPACTING),
|
|
ENTRY(WM_COMMNOTIFY),
|
|
ENTRY(WM_WINDOWPOSCHANGING),
|
|
ENTRY(WM_WINDOWPOSCHANGED),
|
|
ENTRY(WM_POWER),
|
|
ENTRY(WM_COPYDATA),
|
|
ENTRY(WM_CANCELJOURNAL),
|
|
ENTRY(WM_NOTIFY),
|
|
ENTRY(WM_INPUTLANGCHANGEREQUEST),
|
|
ENTRY(WM_INPUTLANGCHANGE),
|
|
ENTRY(WM_TCARD),
|
|
ENTRY(WM_HELP),
|
|
ENTRY(WM_USERCHANGED),
|
|
ENTRY(WM_NOTIFYFORMAT),
|
|
ENTRY(WM_CONTEXTMENU),
|
|
ENTRY(WM_STYLECHANGING),
|
|
ENTRY(WM_STYLECHANGED),
|
|
ENTRY(WM_DISPLAYCHANGE),
|
|
ENTRY(WM_GETICON),
|
|
ENTRY(WM_SETICON),
|
|
ENTRY(WM_NCCREATE),
|
|
ENTRY(WM_NCDESTROY),
|
|
ENTRY(WM_NCCALCSIZE),
|
|
ENTRY(WM_NCHITTEST),
|
|
ENTRY(WM_NCPAINT),
|
|
ENTRY(WM_NCACTIVATE),
|
|
ENTRY(WM_GETDLGCODE),
|
|
ENTRY(WM_SYNCPAINT),
|
|
ENTRY(WM_NCMOUSEMOVE),
|
|
ENTRY(WM_NCLBUTTONDOWN),
|
|
ENTRY(WM_NCLBUTTONUP),
|
|
ENTRY(WM_NCLBUTTONDBLCLK),
|
|
ENTRY(WM_NCRBUTTONDOWN),
|
|
ENTRY(WM_NCRBUTTONUP),
|
|
ENTRY(WM_NCRBUTTONDBLCLK),
|
|
ENTRY(WM_NCMBUTTONDOWN),
|
|
ENTRY(WM_NCMBUTTONUP),
|
|
ENTRY(WM_NCMBUTTONDBLCLK),
|
|
ENTRY(EM_GETSEL),
|
|
ENTRY(EM_SETSEL),
|
|
ENTRY(EM_GETRECT),
|
|
ENTRY(EM_SETRECT),
|
|
ENTRY(EM_SETRECTNP),
|
|
ENTRY(EM_SCROLL),
|
|
ENTRY(EM_LINESCROLL),
|
|
ENTRY(EM_SCROLLCARET),
|
|
ENTRY(EM_GETMODIFY),
|
|
ENTRY(EM_SETMODIFY),
|
|
ENTRY(EM_GETLINECOUNT),
|
|
ENTRY(EM_LINEINDEX),
|
|
ENTRY(EM_SETHANDLE),
|
|
ENTRY(EM_GETHANDLE),
|
|
ENTRY(EM_GETTHUMB),
|
|
ENTRY(EM_LINELENGTH),
|
|
ENTRY(EM_REPLACESEL),
|
|
ENTRY(EM_GETLINE),
|
|
ENTRY(EM_LIMITTEXT),
|
|
ENTRY(EM_CANUNDO),
|
|
ENTRY(EM_UNDO),
|
|
ENTRY(EM_FMTLINES),
|
|
ENTRY(EM_LINEFROMCHAR),
|
|
ENTRY(EM_SETTABSTOPS),
|
|
ENTRY(EM_SETPASSWORDCHAR),
|
|
ENTRY(EM_EMPTYUNDOBUFFER),
|
|
ENTRY(EM_GETFIRSTVISIBLELINE),
|
|
ENTRY(EM_SETREADONLY),
|
|
ENTRY(EM_SETWORDBREAKPROC),
|
|
ENTRY(EM_GETWORDBREAKPROC),
|
|
ENTRY(EM_GETPASSWORDCHAR),
|
|
ENTRY(EM_SETMARGINS),
|
|
ENTRY(EM_GETMARGINS),
|
|
ENTRY(EM_GETLIMITTEXT),
|
|
ENTRY(EM_POSFROMCHAR),
|
|
ENTRY(EM_CHARFROMPOS),
|
|
ENTRY(EM_SETIMESTATUS),
|
|
ENTRY(EM_GETIMESTATUS),
|
|
ENTRY(SBM_SETPOS),
|
|
ENTRY(SBM_GETPOS),
|
|
ENTRY(SBM_SETRANGE),
|
|
ENTRY(SBM_SETRANGEREDRAW),
|
|
ENTRY(SBM_GETRANGE),
|
|
ENTRY(SBM_ENABLE_ARROWS),
|
|
ENTRY(SBM_SETSCROLLINFO),
|
|
ENTRY(SBM_GETSCROLLINFO),
|
|
ENTRY(WM_KEYDOWN),
|
|
ENTRY(WM_KEYUP),
|
|
ENTRY(WM_CHAR),
|
|
ENTRY(WM_DEADCHAR),
|
|
ENTRY(WM_SYSKEYDOWN),
|
|
ENTRY(WM_SYSKEYUP),
|
|
ENTRY(WM_SYSCHAR),
|
|
ENTRY(WM_SYSDEADCHAR),
|
|
ENTRY(WM_KEYLAST),
|
|
ENTRY(WM_IME_STARTCOMPOSITION),
|
|
ENTRY(WM_IME_ENDCOMPOSITION),
|
|
ENTRY(WM_IME_COMPOSITION),
|
|
ENTRY(WM_INITDIALOG),
|
|
ENTRY(WM_COMMAND),
|
|
ENTRY(WM_SYSCOMMAND),
|
|
ENTRY(WM_TIMER),
|
|
ENTRY(WM_HSCROLL),
|
|
ENTRY(WM_VSCROLL),
|
|
ENTRY(WM_INITMENU),
|
|
ENTRY(WM_INITMENUPOPUP),
|
|
ENTRY(WM_MENUSELECT),
|
|
ENTRY(WM_MENUCHAR),
|
|
ENTRY(WM_ENTERIDLE),
|
|
ENTRY(WM_MENURBUTTONUP),
|
|
ENTRY(WM_MENUDRAG),
|
|
ENTRY(WM_MENUGETOBJECT),
|
|
ENTRY(WM_UNINITMENUPOPUP),
|
|
ENTRY(WM_MENUCOMMAND),
|
|
ENTRY(WM_CHANGEUISTATE),
|
|
ENTRY(WM_UPDATEUISTATE),
|
|
ENTRY(WM_CTLCOLORMSGBOX),
|
|
ENTRY(WM_CTLCOLOREDIT),
|
|
ENTRY(WM_CTLCOLORLISTBOX),
|
|
ENTRY(WM_CTLCOLORBTN),
|
|
ENTRY(WM_CTLCOLORDLG),
|
|
ENTRY(WM_CTLCOLORSCROLLBAR),
|
|
ENTRY(WM_CTLCOLORSTATIC),
|
|
ENTRY(CB_GETEDITSEL),
|
|
ENTRY(CB_LIMITTEXT),
|
|
ENTRY(CB_SETEDITSEL),
|
|
ENTRY(CB_ADDSTRING),
|
|
ENTRY(CB_DELETESTRING),
|
|
ENTRY(CB_DIR),
|
|
ENTRY(CB_GETCOUNT),
|
|
ENTRY(CB_GETCURSEL),
|
|
ENTRY(CB_GETLBTEXT),
|
|
ENTRY(CB_GETLBTEXTLEN),
|
|
ENTRY(CB_INSERTSTRING),
|
|
ENTRY(CB_RESETCONTENT),
|
|
ENTRY(CB_FINDSTRING),
|
|
ENTRY(CB_SELECTSTRING),
|
|
ENTRY(CB_SETCURSEL),
|
|
ENTRY(CB_SHOWDROPDOWN),
|
|
ENTRY(CB_GETITEMDATA),
|
|
ENTRY(CB_SETITEMDATA),
|
|
ENTRY(CB_GETDROPPEDCONTROLRECT),
|
|
ENTRY(CB_SETITEMHEIGHT),
|
|
ENTRY(CB_GETITEMHEIGHT),
|
|
ENTRY(CB_SETEXTENDEDUI),
|
|
ENTRY(CB_GETEXTENDEDUI),
|
|
ENTRY(CB_GETDROPPEDSTATE),
|
|
ENTRY(CB_FINDSTRINGEXACT),
|
|
ENTRY(CB_SETLOCALE),
|
|
ENTRY(CB_GETLOCALE),
|
|
ENTRY(CB_GETTOPINDEX),
|
|
ENTRY(CB_SETTOPINDEX),
|
|
ENTRY(CB_GETHORIZONTALEXTENT),
|
|
ENTRY(CB_SETHORIZONTALEXTENT),
|
|
ENTRY(CB_GETDROPPEDWIDTH),
|
|
ENTRY(CB_SETDROPPEDWIDTH),
|
|
ENTRY(CB_INITSTORAGE),
|
|
ENTRY(CB_MSGMAX),
|
|
ENTRY(LB_ADDSTRING),
|
|
ENTRY(LB_INSERTSTRING),
|
|
ENTRY(LB_DELETESTRING),
|
|
ENTRY(LB_SELITEMRANGEEX),
|
|
ENTRY(LB_RESETCONTENT),
|
|
ENTRY(LB_SETSEL),
|
|
ENTRY(LB_SETCURSEL),
|
|
ENTRY(LB_GETSEL),
|
|
ENTRY(LB_GETCURSEL),
|
|
ENTRY(LB_GETTEXT),
|
|
ENTRY(LB_GETTEXTLEN),
|
|
ENTRY(LB_GETCOUNT),
|
|
ENTRY(LB_SELECTSTRING),
|
|
ENTRY(LB_DIR),
|
|
ENTRY(LB_GETTOPINDEX),
|
|
ENTRY(LB_FINDSTRING),
|
|
ENTRY(LB_GETSELCOUNT),
|
|
ENTRY(LB_GETSELITEMS),
|
|
ENTRY(LB_SETTABSTOPS),
|
|
ENTRY(LB_GETHORIZONTALEXTENT),
|
|
ENTRY(LB_SETHORIZONTALEXTENT),
|
|
ENTRY(LB_SETCOLUMNWIDTH),
|
|
ENTRY(LB_ADDFILE),
|
|
ENTRY(LB_SETTOPINDEX),
|
|
ENTRY(LB_GETITEMRECT),
|
|
ENTRY(LB_GETITEMDATA),
|
|
ENTRY(LB_SETITEMDATA),
|
|
ENTRY(LB_SELITEMRANGE),
|
|
ENTRY(LB_SETANCHORINDEX),
|
|
ENTRY(LB_GETANCHORINDEX),
|
|
ENTRY(LB_SETCARETINDEX),
|
|
ENTRY(LB_GETCARETINDEX),
|
|
ENTRY(LB_SETITEMHEIGHT),
|
|
ENTRY(LB_GETITEMHEIGHT),
|
|
ENTRY(LB_FINDSTRINGEXACT),
|
|
ENTRY(LB_SETLOCALE),
|
|
ENTRY(LB_GETLOCALE),
|
|
ENTRY(LB_SETCOUNT),
|
|
ENTRY(LB_INITSTORAGE),
|
|
ENTRY(LB_ITEMFROMPOINT),
|
|
ENTRY(LB_MSGMAX),
|
|
ENTRY(WM_MOUSEMOVE),
|
|
ENTRY(WM_LBUTTONDOWN),
|
|
ENTRY(WM_LBUTTONUP),
|
|
ENTRY(WM_LBUTTONDBLCLK),
|
|
ENTRY(WM_RBUTTONDOWN),
|
|
ENTRY(WM_RBUTTONUP),
|
|
ENTRY(WM_RBUTTONDBLCLK),
|
|
ENTRY(WM_MBUTTONDOWN),
|
|
ENTRY(WM_MBUTTONUP),
|
|
ENTRY(WM_MBUTTONDBLCLK),
|
|
ENTRY(WM_MOUSEWHEEL),
|
|
ENTRY(WM_MOUSEHWHEEL),
|
|
ENTRY(WM_PARENTNOTIFY),
|
|
ENTRY(WM_ENTERMENULOOP),
|
|
ENTRY(WM_EXITMENULOOP),
|
|
ENTRY(WM_NEXTMENU),
|
|
ENTRY(WM_SIZING),
|
|
ENTRY(WM_CAPTURECHANGED),
|
|
ENTRY(WM_MOVING),
|
|
ENTRY(WM_POWERBROADCAST),
|
|
ENTRY(WM_DEVICECHANGE),
|
|
ENTRY(WM_MDICREATE),
|
|
ENTRY(WM_MDIDESTROY),
|
|
ENTRY(WM_MDIACTIVATE),
|
|
ENTRY(WM_MDIRESTORE),
|
|
ENTRY(WM_MDINEXT),
|
|
ENTRY(WM_MDIMAXIMIZE),
|
|
ENTRY(WM_MDITILE),
|
|
ENTRY(WM_MDICASCADE),
|
|
ENTRY(WM_MDIICONARRANGE),
|
|
ENTRY(WM_MDIGETACTIVE),
|
|
ENTRY(WM_MDISETMENU),
|
|
ENTRY(WM_ENTERSIZEMOVE),
|
|
ENTRY(WM_EXITSIZEMOVE),
|
|
ENTRY(WM_DROPFILES),
|
|
ENTRY(WM_MDIREFRESHMENU),
|
|
ENTRY(WM_IME_SETCONTEXT),
|
|
ENTRY(WM_IME_NOTIFY),
|
|
ENTRY(WM_IME_CONTROL),
|
|
ENTRY(WM_IME_COMPOSITIONFULL),
|
|
ENTRY(WM_IME_SELECT),
|
|
ENTRY(WM_IME_CHAR),
|
|
ENTRY(WM_IME_REQUEST),
|
|
ENTRY(WM_IME_KEYDOWN),
|
|
ENTRY(WM_IME_KEYUP),
|
|
ENTRY(WM_NCMOUSEHOVER),
|
|
ENTRY(WM_MOUSEHOVER),
|
|
ENTRY(WM_MOUSELEAVE),
|
|
ENTRY(WM_CUT),
|
|
ENTRY(WM_COPY),
|
|
ENTRY(WM_PASTE),
|
|
ENTRY(WM_CLEAR),
|
|
ENTRY(WM_UNDO),
|
|
ENTRY(WM_RENDERFORMAT),
|
|
ENTRY(WM_RENDERALLFORMATS),
|
|
ENTRY(WM_DESTROYCLIPBOARD),
|
|
ENTRY(WM_DRAWCLIPBOARD),
|
|
ENTRY(WM_PAINTCLIPBOARD),
|
|
ENTRY(WM_VSCROLLCLIPBOARD),
|
|
ENTRY(WM_SIZECLIPBOARD),
|
|
ENTRY(WM_ASKCBFORMATNAME),
|
|
ENTRY(WM_CHANGECBCHAIN),
|
|
ENTRY(WM_HSCROLLCLIPBOARD),
|
|
ENTRY(WM_QUERYNEWPALETTE),
|
|
ENTRY(WM_PALETTEISCHANGING),
|
|
ENTRY(WM_PALETTECHANGED),
|
|
ENTRY(WM_HOTKEY),
|
|
ENTRY(WM_PRINT),
|
|
ENTRY(WM_PRINTCLIENT),
|
|
ENTRY(WM_THEMECHANGED),
|
|
ENTRY(WM_HANDHELDFIRST),
|
|
ENTRY(WM_HANDHELDLAST),
|
|
ENTRY(WM_AFXFIRST),
|
|
ENTRY(WM_AFXLAST),
|
|
ENTRY(WM_PENWINFIRST),
|
|
ENTRY(WM_PENWINLAST),
|
|
ENTRY(WM_APP),
|
|
ENTRY(WM_DWMCOMPOSITIONCHANGED),
|
|
ENTRY(WM_DWMNCRENDERINGCHANGED),
|
|
ENTRY(WM_DWMCOLORIZATIONCOLORCHANGED),
|
|
ENTRY(WM_DWMWINDOWMAXIMIZEDCHANGE),
|
|
ENTRY(WM_DWMSENDICONICTHUMBNAIL),
|
|
ENTRY(WM_DWMSENDICONICLIVEPREVIEWBITMAP),
|
|
ENTRY(WM_TABLET_QUERYSYSTEMGESTURESTATUS),
|
|
ENTRY(WM_GESTURE),
|
|
ENTRY(WM_GESTURENOTIFY),
|
|
ENTRY(WM_GETTITLEBARINFOEX),
|
|
{nullptr, 0x0}};
|
|
#undef ENTRY
|
|
|
|
#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";
|
|
|
|
// Prefix for path used by NT calls.
|
|
const wchar_t kNTPrefix[] = L"\\??\\";
|
|
const size_t kNTPrefixLen = ArrayLength(kNTPrefix) - 1;
|
|
|
|
struct CoTaskMemFreePolicy {
|
|
void operator()(void* aPtr) { ::CoTaskMemFree(aPtr); }
|
|
};
|
|
|
|
SetThreadDpiAwarenessContextProc WinUtils::sSetThreadDpiAwarenessContext = NULL;
|
|
EnableNonClientDpiScalingProc WinUtils::sEnableNonClientDpiScaling = NULL;
|
|
GetSystemMetricsForDpiProc WinUtils::sGetSystemMetricsForDpi = NULL;
|
|
|
|
/* static */
|
|
void WinUtils::Initialize() {
|
|
if (IsWin10OrLater()) {
|
|
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");
|
|
}
|
|
}
|
|
}
|
|
|
|
// static
|
|
LRESULT WINAPI WinUtils::NonClientDpiScalingDefWindowProcW(HWND hWnd, UINT msg,
|
|
WPARAM wParam,
|
|
LPARAM lParam) {
|
|
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, (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, (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; }
|
|
|
|
#if WINVER < 0x603
|
|
typedef enum {
|
|
MDT_EFFECTIVE_DPI = 0,
|
|
MDT_ANGULAR_DPI = 1,
|
|
MDT_RAW_DPI = 2,
|
|
MDT_DEFAULT = MDT_EFFECTIVE_DPI
|
|
} MONITOR_DPI_TYPE;
|
|
|
|
typedef enum {
|
|
PROCESS_DPI_UNAWARE = 0,
|
|
PROCESS_SYSTEM_DPI_AWARE = 1,
|
|
PROCESS_PER_MONITOR_DPI_AWARE = 2
|
|
} PROCESS_DPI_AWARENESS;
|
|
#endif
|
|
|
|
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 */
|
|
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() {
|
|
return (sGetSystemMetricsForDpi != NULL);
|
|
}
|
|
|
|
/* static */
|
|
int WinUtils::GetSystemMetricsForDpi(int nIndex, UINT dpi) {
|
|
if (HasSystemMetricsForDpi()) {
|
|
return sGetSystemMetricsForDpi(nIndex, dpi);
|
|
} else {
|
|
double scale = IsPerMonitorDPIAware() ? dpi / SystemDPI() : 1.0;
|
|
return NSToIntRound(::GetSystemMetrics(nIndex) * scale);
|
|
}
|
|
}
|
|
|
|
#ifdef ACCESSIBILITY
|
|
/* static */
|
|
a11y::Accessible* 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) {
|
|
#ifdef NS_ENABLE_TSF
|
|
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;
|
|
}
|
|
#endif // #ifdef NS_ENABLE_TSF
|
|
return ::PeekMessageW(aMsg, aWnd, aFirstMessage, aLastMessage, aOption);
|
|
}
|
|
|
|
/* static */
|
|
bool WinUtils::GetMessage(LPMSG aMsg, HWND aWnd, UINT aFirstMessage,
|
|
UINT aLastMessage) {
|
|
#ifdef NS_ENABLE_TSF
|
|
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;
|
|
}
|
|
#endif // #ifdef NS_ENABLE_TSF
|
|
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 = ::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 */
|
|
bool WinUtils::GetRegistryKey(HKEY aRoot, char16ptr_t aKeyName,
|
|
char16ptr_t aValueName, wchar_t* aBuffer,
|
|
DWORD aBufferLength) {
|
|
MOZ_ASSERT(aKeyName, "The key name is NULL");
|
|
|
|
HKEY key;
|
|
LONG result =
|
|
::RegOpenKeyExW(aRoot, aKeyName, 0, KEY_READ | KEY_WOW64_32KEY, &key);
|
|
if (result != ERROR_SUCCESS) {
|
|
result =
|
|
::RegOpenKeyExW(aRoot, aKeyName, 0, KEY_READ | KEY_WOW64_64KEY, &key);
|
|
if (result != ERROR_SUCCESS) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
DWORD type;
|
|
result = ::RegQueryValueExW(key, aValueName, nullptr, &type, (BYTE*)aBuffer,
|
|
&aBufferLength);
|
|
::RegCloseKey(key);
|
|
if (result != ERROR_SUCCESS || (type != REG_SZ && type != REG_EXPAND_SZ)) {
|
|
return false;
|
|
}
|
|
if (aBuffer) {
|
|
aBuffer[aBufferLength / sizeof(*aBuffer) - 1] = 0;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* static */
|
|
bool WinUtils::HasRegistryKey(HKEY aRoot, char16ptr_t aKeyName) {
|
|
MOZ_ASSERT(aRoot, "aRoot must not be NULL");
|
|
MOZ_ASSERT(aKeyName, "aKeyName must not be NULL");
|
|
HKEY key;
|
|
LONG result =
|
|
::RegOpenKeyExW(aRoot, aKeyName, 0, KEY_READ | KEY_WOW64_32KEY, &key);
|
|
if (result != ERROR_SUCCESS) {
|
|
result =
|
|
::RegOpenKeyExW(aRoot, aKeyName, 0, KEY_READ | KEY_WOW64_64KEY, &key);
|
|
if (result != ERROR_SUCCESS) {
|
|
return false;
|
|
}
|
|
}
|
|
::RegCloseKey(key);
|
|
return true;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
static const wchar_t* GetNSWindowPropName() {
|
|
static wchar_t sPropName[40] = L"";
|
|
if (!*sPropName) {
|
|
_snwprintf(sPropName, 39, L"MozillansIWidgetPtr%u",
|
|
::GetCurrentProcessId());
|
|
sPropName[39] = '\0';
|
|
}
|
|
return sPropName;
|
|
}
|
|
|
|
/* static */
|
|
bool WinUtils::SetNSWindowBasePtr(HWND aWnd, nsWindowBase* aWidget) {
|
|
if (!aWidget) {
|
|
::RemovePropW(aWnd, GetNSWindowPropName());
|
|
return true;
|
|
}
|
|
return ::SetPropW(aWnd, GetNSWindowPropName(), (HANDLE)aWidget);
|
|
}
|
|
|
|
/* static */
|
|
nsWindowBase* WinUtils::GetNSWindowBasePtr(HWND aWnd) {
|
|
return static_cast<nsWindowBase*>(::GetPropW(aWnd, GetNSWindowPropName()));
|
|
}
|
|
|
|
/* static */
|
|
nsWindow* WinUtils::GetNSWindowPtr(HWND aWnd) {
|
|
return static_cast<nsWindow*>(::GetPropW(aWnd, GetNSWindowPropName()));
|
|
}
|
|
|
|
static BOOL CALLBACK AddMonitor(HMONITOR, HDC, LPRECT, LPARAM aParam) {
|
|
(*(int32_t*)aParam)++;
|
|
return TRUE;
|
|
}
|
|
|
|
/* static */
|
|
int32_t WinUtils::GetMonitorCount() {
|
|
int32_t monitorCount = 0;
|
|
EnumDisplayMonitors(nullptr, nullptr, AddMonitor, (LPARAM)&monitorCount);
|
|
return monitorCount;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
static BOOL WINAPI EnumFirstChild(HWND hwnd, LPARAM lParam) {
|
|
*((HWND*)lParam) = hwnd;
|
|
return FALSE;
|
|
}
|
|
|
|
/* static */
|
|
void WinUtils::InvalidatePluginAsWorkaround(nsIWidget* aWidget,
|
|
const LayoutDeviceIntRect& aRect) {
|
|
aWidget->Invalidate(aRect);
|
|
|
|
// XXX - Even more evil workaround!! See bug 762948, flash's bottom
|
|
// level sandboxed window doesn't seem to get our invalidate. We send
|
|
// an invalidate to it manually. This is totally specialized for this
|
|
// bug, for other child window structures this will just be a more or
|
|
// less bogus invalidate but since that should not have any bad
|
|
// side-effects this will have to do for now.
|
|
HWND current = (HWND)aWidget->GetNativeData(NS_NATIVE_WINDOW);
|
|
|
|
RECT windowRect;
|
|
RECT parentRect;
|
|
|
|
::GetWindowRect(current, &parentRect);
|
|
|
|
HWND next = current;
|
|
do {
|
|
current = next;
|
|
::EnumChildWindows(current, &EnumFirstChild, (LPARAM)&next);
|
|
::GetWindowRect(next, &windowRect);
|
|
// This is relative to the screen, adjust it to be relative to the
|
|
// window we're reconfiguring.
|
|
windowRect.left -= parentRect.left;
|
|
windowRect.top -= parentRect.top;
|
|
} while (next != current && windowRect.top == 0 && windowRect.left == 0);
|
|
|
|
if (windowRect.top == 0 && windowRect.left == 0) {
|
|
RECT rect;
|
|
rect.left = aRect.X();
|
|
rect.top = aRect.Y();
|
|
rect.right = aRect.XMost();
|
|
rect.bottom = aRect.YMost();
|
|
|
|
::InvalidateRect(next, &rect, FALSE);
|
|
}
|
|
}
|
|
|
|
#ifdef MOZ_PLACES
|
|
/************************************************************************
|
|
* Constructs as AsyncFaviconDataReady Object
|
|
* @param aIOThread : the thread which performs the action
|
|
* @param aURLShortcut : Differentiates between (false)Jumplistcache and
|
|
* (true)Shortcutcache
|
|
************************************************************************/
|
|
|
|
AsyncFaviconDataReady::AsyncFaviconDataReady(nsIURI* aNewURI,
|
|
nsCOMPtr<nsIThread>& aIOThread,
|
|
const bool aURLShortcut)
|
|
: mNewURI(aNewURI), mIOThread(aIOThread), mURLShortcut(aURLShortcut) {}
|
|
|
|
NS_IMETHODIMP
|
|
myDownloadObserver::OnDownloadComplete(nsIDownloader* downloader,
|
|
nsIRequest* request, nsISupports* ctxt,
|
|
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_DATA_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(Color(1.0f, 1.0f, 1.0f, 1.0f)));
|
|
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, mURLShortcut);
|
|
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, const bool aURLShortcut)
|
|
: mURLShortcut(aURLShortcut),
|
|
mIconPath(aIconPath),
|
|
mBuffer(std::move(aBuffer)),
|
|
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, EmptyString(), gfxUtils::eBinaryEncode, file);
|
|
fclose(file);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (mURLShortcut) {
|
|
SendNotifyMessage(HWND_BROADCAST, WM_SETTINGCHANGE, SPI_SETNONCLIENTMETRICS,
|
|
0);
|
|
}
|
|
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)
|
|
*/
|
|
nsresult FaviconHelper::ObtainCachedIconFile(nsCOMPtr<nsIURI> aFaviconPageURI,
|
|
nsString& aICOFilePath,
|
|
nsCOMPtr<nsIThread>& aIOThread,
|
|
bool aURLShortcut) {
|
|
// 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);
|
|
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);
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
// The icoFile is filled with a path that exists, get its path
|
|
rv = icoFile->GetPath(aICOFilePath);
|
|
return rv;
|
|
}
|
|
|
|
nsresult FaviconHelper::HashURI(nsCOMPtr<nsICryptoHash>& aCryptoHash,
|
|
nsIURI* aUri, nsACString& aUriHash) {
|
|
if (!aUri) return NS_ERROR_INVALID_ARG;
|
|
|
|
nsAutoCString spec;
|
|
nsresult rv = aUri->GetSpec(spec);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (!aCryptoHash) {
|
|
aCryptoHash = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
rv = aCryptoHash->Init(nsICryptoHash::MD5);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
rv = aCryptoHash->Update(
|
|
reinterpret_cast<const uint8_t*>(spec.BeginReading()), spec.Length());
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
rv = aCryptoHash->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
|
|
nsresult FaviconHelper::GetOutputIconPath(nsCOMPtr<nsIURI> aFaviconPageURI,
|
|
nsCOMPtr<nsIFile>& aICOFile,
|
|
bool aURLShortcut) {
|
|
// Hash the input URI and replace any / with _
|
|
nsAutoCString inputURIHash;
|
|
nsCOMPtr<nsICryptoHash> cryptoHash;
|
|
nsresult rv = HashURI(cryptoHash, 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,
|
|
nsCOMPtr<nsIThread>& aIOThread, bool aURLShortcut) {
|
|
#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);
|
|
|
|
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 */
|
|
bool WinUtils::GetShellItemPath(IShellItem* aItem, nsString& aResultString) {
|
|
NS_ENSURE_TRUE(aItem, false);
|
|
LPWSTR str = nullptr;
|
|
if (FAILED(aItem->GetDisplayName(SIGDN_FILESYSPATH, &str))) return false;
|
|
aResultString.Assign(str);
|
|
CoTaskMemFree(str);
|
|
return !aResultString.IsEmpty();
|
|
}
|
|
|
|
/* 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) {
|
|
if (!IsIMEEnabled(aInputContext.mIMEState.mEnabled)) {
|
|
return false;
|
|
}
|
|
if (aInputContext.mIMEState.mEnabled == IMEState::PLUGIN &&
|
|
aInputContext.mHTMLInputType.EqualsLiteral("password")) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* static */
|
|
bool WinUtils::IsIMEEnabled(IMEState::Enabled aIMEState) {
|
|
return (aIMEState == IMEState::ENABLED || aIMEState == IMEState::PLUGIN);
|
|
}
|
|
|
|
/* 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);
|
|
return (touchCapabilities & NID_READY) &&
|
|
(touchCapabilities & (NID_EXTERNAL_TOUCH | NID_INTEGRATED_TOUCH));
|
|
}
|
|
|
|
/* 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 IsWindows10TabletMode() {
|
|
nsCOMPtr<nsIWindowsUIUtils> uiUtils(
|
|
do_GetService("@mozilla.org/windows-ui-utils;1"));
|
|
if (NS_WARN_IF(!uiUtils)) {
|
|
return false;
|
|
}
|
|
bool isInTabletMode = false;
|
|
uiUtils->GetInTabletMode(&isInTabletMode);
|
|
return isInTabletMode;
|
|
}
|
|
|
|
static bool CallGetAutoRotationState(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 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 (!IsWin8OrLater()) {
|
|
return false;
|
|
}
|
|
|
|
if (IsWindows10TabletMode()) {
|
|
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:
|
|
// https://msdn.microsoft.com/en-us/library/windows/desktop/dn629263(v=vs.85).aspx
|
|
AR_STATE rotation_state;
|
|
if (CallGetAutoRotationState(&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 IsMousePresent() {
|
|
if (!::GetSystemMetrics(SM_MOUSEPRESENT)) {
|
|
return false;
|
|
}
|
|
|
|
DWORD count = InputDeviceUtils::CountMouseDevices();
|
|
if (!count) {
|
|
return false;
|
|
}
|
|
|
|
// If there is a mouse device and if this machine is a tablet or has a
|
|
// digitizer, that's counted as the mouse device.
|
|
// FIXME: Bug 1495938: We should drop this heuristic way once we find out a
|
|
// reliable way to tell there is no mouse or not.
|
|
if (count == 1 &&
|
|
(WinUtils::IsTouchDeviceSupportPresent() || IsTabletDevice())) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* static */
|
|
PointerCapabilities WinUtils::GetPrimaryPointerCapabilities() {
|
|
if (IsTabletDevice()) {
|
|
return PointerCapabilities::Coarse;
|
|
}
|
|
|
|
if (IsMousePresent()) {
|
|
return PointerCapabilities::Fine | PointerCapabilities::Hover;
|
|
}
|
|
|
|
if (IsTouchDeviceSupportPresent()) {
|
|
return PointerCapabilities::Coarse;
|
|
}
|
|
|
|
return PointerCapabilities::None;
|
|
}
|
|
|
|
/* static */
|
|
PointerCapabilities WinUtils::GetAllPointerCapabilities() {
|
|
PointerCapabilities result = PointerCapabilities::None;
|
|
|
|
if (IsTabletDevice() || IsTouchDeviceSupportPresent()) {
|
|
result |= PointerCapabilities::Coarse;
|
|
}
|
|
|
|
if (IsMousePresent()) {
|
|
result |= PointerCapabilities::Fine | PointerCapabilities::Hover;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/* 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=%d",
|
|
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=%d", 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::GetModuleFullPath(HMODULE aModuleHandle, nsAString& aPath) {
|
|
size_t bufferSize = MAX_PATH;
|
|
size_t len = 0;
|
|
while (true) {
|
|
aPath.SetLength(bufferSize);
|
|
len = (size_t)::GetModuleFileNameW(
|
|
aModuleHandle, (char16ptr_t)aPath.BeginWriting(), bufferSize);
|
|
if (!len) {
|
|
return false;
|
|
}
|
|
if (len == bufferSize && ::GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
|
|
bufferSize *= 2;
|
|
continue;
|
|
}
|
|
aPath.Truncate(len);
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* 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(mozilla::MakePair(
|
|
nsString(NS_LITERAL_STRING("%ProgramFiles%")), nsDependentString()));
|
|
|
|
// When no substitution is required, set the void flag
|
|
result.back().second().SetIsVoid(true);
|
|
|
|
Unused << result.emplaceBack(mozilla::MakePair(
|
|
nsString(NS_LITERAL_STRING("%SystemRoot%")), 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)) {
|
|
NS_NAMED_LITERAL_STRING(tempVar, "%TEMP%");
|
|
Unused << result.emplaceBack(mozilla::MakePair(
|
|
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::Shutdown);
|
|
};
|
|
|
|
if (NS_IsMainThread()) {
|
|
setClearFn();
|
|
} else {
|
|
SystemGroup::Dispatch(
|
|
TaskCategory::Other,
|
|
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);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace widget
|
|
} // namespace mozilla
|