in widget/windows/nsWindow.cpp [4666:5876]
bool nsWindow::ProcessMessageInternal(UINT msg, WPARAM& wParam, LPARAM& lParam,
LRESULT* aRetValue) {
MSGResult msgResult(aRetValue);
if (ExternalHandlerProcessMessage(msg, wParam, lParam, msgResult)) {
return (msgResult.mConsumed || !mWnd);
}
bool result = false; // call the default nsWindow proc
*aRetValue = 0;
// The DWM resize hack (see bug 1763981) causes us to process a number of
// messages, notably including some WM_WINDOWPOSCHANG{ING,ED} messages which
// would ordinarily result in a whole lot of internal state being updated.
//
// Since we're supposed to end in the same state we started in (and since the
// content shouldn't know about any of this nonsense), just discard any
// messages synchronously dispatched from within the hack.
if (MOZ_UNLIKELY(mIsPerformingDwmFlushHack)) {
return true;
}
// Glass hit testing w/custom transparent margins.
//
// FIXME(emilio): is this needed? We deal with titlebar buttons non-natively
// now.
LRESULT dwmHitResult;
if (mCustomNonClient &&
DwmDefWindowProc(mWnd, msg, wParam, lParam, &dwmHitResult)) {
*aRetValue = dwmHitResult;
return true;
}
// The preference whether to use a different keyboard layout for each
// window is cached, and updating it will not take effect until the
// next restart. We read the preference here and not upon WM_ACTIVATE to make
// sure that this behavior is consistent. Otherwise, if the user changed the
// preference before having ever lowered the window, the preference would take
// effect immediately.
static const bool sSwitchKeyboardLayout =
Preferences::GetBool("intl.keyboard.per_window_layout", false);
AppShutdownReason shutdownReason = AppShutdownReason::Unknown;
// (Large blocks of code should be broken out into OnEvent handlers.)
switch (msg) {
// WM_QUERYENDSESSION must be handled by all windows.
// Otherwise Windows thinks the window can just be killed at will.
case WM_QUERYENDSESSION: {
// Ask around if it's ok to quit.
nsCOMPtr<nsIObserverService> obsServ =
mozilla::services::GetObserverService();
nsCOMPtr<nsISupportsPRBool> cancelQuitWrapper =
do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID);
cancelQuitWrapper->SetData(false);
const char16_t* quitType = GetQuitType();
obsServ->NotifyObservers(cancelQuitWrapper, "quit-application-requested",
quitType);
bool shouldCancelQuit;
cancelQuitWrapper->GetData(&shouldCancelQuit);
*aRetValue = !shouldCancelQuit;
result = true;
} break;
case MOZ_WM_STARTA11Y:
#if defined(ACCESSIBILITY)
Unused << GetAccessible();
result = true;
#else
result = false;
#endif
break;
case WM_ENDSESSION: {
// For WM_ENDSESSION, wParam indicates whether we need to shutdown
// (TRUE) or not (FALSE).
if (!wParam) {
result = true;
break;
}
// According to WM_ENDSESSION lParam documentation:
// 0 -> OS shutdown or restart (no way to distinguish)
// ENDSESSION_LOGOFF -> User is logging off
// ENDSESSION_CLOSEAPP -> Application must shutdown
// ENDSESSION_CRITICAL -> Application is forced to shutdown
// The difference of the last two is not very clear.
if (lParam == 0) {
shutdownReason = AppShutdownReason::OSShutdown;
} else if (lParam & ENDSESSION_LOGOFF) {
shutdownReason = AppShutdownReason::OSSessionEnd;
} else if (lParam & (ENDSESSION_CLOSEAPP | ENDSESSION_CRITICAL)) {
shutdownReason = AppShutdownReason::OSForceClose;
} else {
MOZ_DIAGNOSTIC_CRASH("Received WM_ENDSESSION with unknown flags.");
shutdownReason = AppShutdownReason::OSForceClose;
}
// Let's fake a shutdown sequence without actually closing windows etc.
// to avoid Windows killing us in the middle. A proper shutdown would
// require having a chance to pump some messages. Unfortunately
// Windows won't let us do that. Bug 212316.
nsCOMPtr<nsIObserverService> obsServ =
mozilla::services::GetObserverService();
const char16_t* syncShutdown = u"syncShutdown";
const char16_t* quitType = GetQuitType();
AppShutdown::Init(AppShutdownMode::Normal, 0, shutdownReason);
obsServ->NotifyObservers(nullptr, "quit-application-granted",
syncShutdown);
obsServ->NotifyObservers(nullptr, "quit-application-forced", nullptr);
AppShutdown::OnShutdownConfirmed();
AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownConfirmed,
quitType);
AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownNetTeardown,
nullptr);
AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownTeardown,
nullptr);
AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdown, nullptr);
AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownQM, nullptr);
AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownTelemetry,
nullptr);
AppShutdown::DoImmediateExit();
MOZ_ASSERT_UNREACHABLE("Our process was supposed to exit.");
} break;
case WM_THEMECHANGED: {
// Update non-client margin offsets
UpdateNonClientMargins();
// Invalidate the window so that the repaint will pick up the new theme.
Invalidate(true, true, true);
} break;
case WM_WTSSESSION_CHANGE: {
switch (wParam) {
case WTS_CONSOLE_CONNECT:
case WTS_REMOTE_CONNECT:
case WTS_SESSION_UNLOCK:
// When a session becomes visible, we should invalidate.
Invalidate(true, true, true);
break;
default:
break;
}
} break;
case WM_NCCALCSIZE: {
// NOTE: the following block is mirrored in PreXULSkeletonUI.cpp, and
// will need to be kept in sync.
if (mCustomNonClient) {
// If `wParam` is `FALSE`, `lParam` points to a `RECT` that contains
// the proposed window rectangle for our window. During our
// processing of the `WM_NCCALCSIZE` message, we are expected to
// modify the `RECT` that `lParam` points to, so that its value upon
// our return is the new client area. We must return 0 if `wParam`
// is `FALSE`.
//
// If `wParam` is `TRUE`, `lParam` points to a `NCCALCSIZE_PARAMS`
// struct. This struct contains an array of 3 `RECT`s, the first of
// which has the exact same meaning as the `RECT` that is pointed to
// by `lParam` when `wParam` is `FALSE`. The remaining `RECT`s, in
// conjunction with our return value, can
// be used to specify portions of the source and destination window
// rectangles that are valid and should be preserved. We opt not to
// implement an elaborate client-area preservation technique, and
// simply return 0, which means "preserve the entire old client area
// and align it with the upper-left corner of our new client area".
RECT* clientRect =
wParam ? &(reinterpret_cast<NCCALCSIZE_PARAMS*>(lParam))->rgrc[0]
: (reinterpret_cast<RECT*>(lParam));
auto margin = NonClientSizeMargin();
clientRect->top += margin.top;
clientRect->left += margin.left;
clientRect->right -= margin.right;
clientRect->bottom -= margin.bottom;
// Make client rect's width and height more than 0 to
// avoid problems of webrender and angle.
clientRect->right = std::max(clientRect->right, clientRect->left + 1);
clientRect->bottom = std::max(clientRect->bottom, clientRect->top + 1);
result = true;
*aRetValue = 0;
}
break;
}
case WM_GETTITLEBARINFOEX: {
if (!mCustomNonClient) {
break;
}
auto* info = reinterpret_cast<TITLEBARINFOEX*>(lParam);
const LayoutDeviceIntPoint origin = WidgetToScreenOffset();
auto GeckoClientToWinScreenRect =
[&origin](LayoutDeviceIntRect aRect) -> RECT {
aRect.MoveBy(origin);
return WinUtils::ToWinRect(aRect);
};
auto SetButton = [&](size_t aIndex, WindowButtonType aType) {
info->rgrect[aIndex] =
GeckoClientToWinScreenRect(mWindowBtnRect[aType]);
DWORD& state = info->rgstate[aIndex];
if (mWindowBtnRect[aType].IsEmpty()) {
state = STATE_SYSTEM_INVISIBLE;
} else {
state = STATE_SYSTEM_FOCUSABLE;
}
};
info->rgrect[0] = info->rcTitleBar =
GeckoClientToWinScreenRect(mDraggableRegion.GetBounds());
info->rgstate[0] = 0;
SetButton(2, WindowButtonType::Minimize);
SetButton(3, WindowButtonType::Maximize);
SetButton(5, WindowButtonType::Close);
// We don't have a help button.
info->rgstate[4] = STATE_SYSTEM_INVISIBLE;
info->rgrect[4] = {0, 0, 0, 0};
result = true;
} break;
case WM_NCHITTEST: {
if (mInputRegion.mFullyTransparent) {
// Treat this window as transparent.
*aRetValue = HTTRANSPARENT;
result = true;
break;
}
if (mInputRegion.mMargin) {
const LayoutDeviceIntPoint screenPoint(GET_X_LPARAM(lParam),
GET_Y_LPARAM(lParam));
LayoutDeviceIntRect screenRect = GetScreenBounds();
screenRect.Deflate(mInputRegion.mMargin);
if (!screenRect.Contains(screenPoint)) {
*aRetValue = HTTRANSPARENT;
result = true;
break;
}
}
/*
* If an nc client area margin has been moved, we are responsible
* for calculating where the resize margins are and returning the
* appropriate set of hit test constants. DwmDefWindowProc (above)
* will handle hit testing on it's command buttons if we are on a
* composited desktop.
*/
if (!mCustomNonClient) {
break;
}
*aRetValue =
ClientMarginHitTestPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
result = true;
break;
}
case WM_SETTEXT:
/*
* WM_SETTEXT paints the titlebar area. Avoid this if we have a
* custom titlebar we paint ourselves, or if we're the ones
* sending the message with an updated title
*/
if (mSendingSetText || !mCustomNonClient) {
break;
}
{
// From msdn, the way around this is to disable the visible state
// temporarily. We need the text to be set but we don't want the
// redraw to occur. However, we need to make sure that we don't
// do this at the same time that a Present is happening.
//
// To do this we take mPresentLock in nsWindow::PreRender and
// if that lock is taken we wait before doing WM_SETTEXT
if (mCompositorWidgetDelegate) {
mCompositorWidgetDelegate->EnterPresentLock();
}
DWORD style = GetWindowLong(mWnd, GWL_STYLE);
SetWindowLong(mWnd, GWL_STYLE, style & ~WS_VISIBLE);
*aRetValue =
CallWindowProcW(GetPrevWindowProc(), mWnd, msg, wParam, lParam);
SetWindowLong(mWnd, GWL_STYLE, style);
if (mCompositorWidgetDelegate) {
mCompositorWidgetDelegate->LeavePresentLock();
}
return true;
}
case WM_NCACTIVATE: {
if (!mCustomNonClient) {
break;
}
// There is a case that rendered result is not kept. Bug 1237617
if (wParam == TRUE && !gfxEnv::MOZ_DISABLE_FORCE_PRESENT()) {
NS_DispatchToMainThread(NewRunnableMethod(
"nsWindow::ForcePresent", this, &nsWindow::ForcePresent));
}
// ::DefWindowProc would paint nc areas. Avoid this, since we just want
// dwm to take care of re-displaying the glass effect if any. Quoting the
// docs[1]:
//
// If this parameter is set to -1, DefWindowProc does not repaint the
// nonclient area to reflect the state change.
//
// [1]:
// https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-ncactivate
lParam = -1;
} break;
case WM_NCPAINT: {
// ClearType changes often don't send a WM_SETTINGCHANGE message. But
// they do seem to always send a WM_NCPAINT message, so let's update on
// that.
gfxDWriteFont::UpdateSystemTextVars();
} break;
case WM_POWERBROADCAST:
switch (wParam) {
case PBT_APMSUSPEND:
PostSleepWakeNotification(true);
break;
case PBT_APMRESUMEAUTOMATIC:
case PBT_APMRESUMECRITICAL:
case PBT_APMRESUMESUSPEND:
PostSleepWakeNotification(false);
break;
}
break;
case WM_CLOSE: // close request
if (mWidgetListener) mWidgetListener->RequestWindowClose(this);
result = true; // abort window closure
break;
case WM_DESTROY:
// clean up.
DestroyLayerManager();
OnDestroy();
result = true;
break;
case WM_PAINT:
*aRetValue = (int)OnPaint();
result = true;
break;
case WM_HOTKEY:
result = OnHotKey(wParam, lParam);
break;
case WM_SYSCHAR:
case WM_CHAR: {
MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd);
result = ProcessCharMessage(nativeMsg, nullptr);
DispatchPendingEvents();
} break;
case WM_SYSKEYUP:
case WM_KEYUP: {
MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd);
nativeMsg.time = ::GetMessageTime();
result = ProcessKeyUpMessage(nativeMsg, nullptr);
DispatchPendingEvents();
} break;
case WM_SYSKEYDOWN:
case WM_KEYDOWN: {
MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd);
result = ProcessKeyDownMessage(nativeMsg, nullptr);
DispatchPendingEvents();
} break;
// Say we've dealt with erasing the background. (This is actually handled in
// WM_PAINT or at window-creation time, as necessary.)
case WM_ERASEBKGND: {
*aRetValue = 1;
result = true;
} break;
case WM_MOUSEMOVE: {
LPARAM lParamScreen = lParamToScreen(lParam);
mSimulatedClientArea = IsSimulatedClientArea(GET_X_LPARAM(lParamScreen),
GET_Y_LPARAM(lParamScreen));
if (!mMousePresent && !sIsInMouseCapture) {
// First MOUSEMOVE over the client area. Ask for MOUSELEAVE
TRACKMOUSEEVENT mTrack;
mTrack.cbSize = sizeof(TRACKMOUSEEVENT);
mTrack.dwFlags = TME_LEAVE;
mTrack.dwHoverTime = 0;
mTrack.hwndTrack = mWnd;
TrackMouseEvent(&mTrack);
}
mMousePresent = true;
// Suppress dispatch of pending events
// when mouse moves are generated by widget
// creation instead of user input.
POINT mp;
mp.x = GET_X_LPARAM(lParamScreen);
mp.y = GET_Y_LPARAM(lParamScreen);
bool userMovedMouse = false;
if ((sLastMouseMovePoint.x != mp.x) || (sLastMouseMovePoint.y != mp.y)) {
userMovedMouse = true;
}
if (userMovedMouse) {
result = DispatchMouseEvent(
eMouseMove, wParam, lParam, false, MouseButton::ePrimary,
MOUSE_INPUT_SOURCE(),
mPointerEvents.GetCachedPointerInfo(msg, wParam));
DispatchPendingEvents();
}
} break;
case WM_NCMOUSEMOVE: {
LPARAM lParamClient = lParamToClient(lParam);
if (IsSimulatedClientArea(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))) {
if (!sIsInMouseCapture) {
TRACKMOUSEEVENT mTrack;
mTrack.cbSize = sizeof(TRACKMOUSEEVENT);
mTrack.dwFlags = TME_LEAVE | TME_NONCLIENT;
mTrack.dwHoverTime = 0;
mTrack.hwndTrack = mWnd;
TrackMouseEvent(&mTrack);
}
// If we noticed the mouse moving in our draggable region, forward the
// message as a normal WM_MOUSEMOVE.
SendMessage(mWnd, WM_MOUSEMOVE, 0, lParamClient);
} else {
// We've transitioned from a draggable area to somewhere else within
// the non-client area - perhaps one of the edges of the window for
// resizing.
mSimulatedClientArea = false;
}
if (mMousePresent && !sIsInMouseCapture && !mSimulatedClientArea) {
SendMessage(mWnd, WM_MOUSELEAVE, 0, 0);
}
} break;
case WM_LBUTTONDOWN: {
result =
DispatchMouseEvent(eMouseDown, wParam, lParam, false,
MouseButton::ePrimary, MOUSE_INPUT_SOURCE(),
mPointerEvents.GetCachedPointerInfo(msg, wParam));
DispatchPendingEvents();
} break;
case WM_LBUTTONUP: {
result =
DispatchMouseEvent(eMouseUp, wParam, lParam, false,
MouseButton::ePrimary, MOUSE_INPUT_SOURCE(),
mPointerEvents.GetCachedPointerInfo(msg, wParam));
DispatchPendingEvents();
} break;
case WM_NCMOUSELEAVE: {
mSimulatedClientArea = false;
if (EventIsInsideWindow(this)) {
// If we're handling WM_NCMOUSELEAVE and the mouse is still over the
// window, then by process of elimination, the mouse has moved from the
// non-client to client area, so no need to fall-through to the
// WM_MOUSELEAVE handler. We also need to re-register for the
// WM_MOUSELEAVE message, since according to the documentation at [1],
// all tracking requested via TrackMouseEvent is cleared once
// WM_NCMOUSELEAVE or WM_MOUSELEAVE fires.
// [1]:
// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-trackmouseevent
TRACKMOUSEEVENT mTrack;
mTrack.cbSize = sizeof(TRACKMOUSEEVENT);
mTrack.dwFlags = TME_LEAVE;
mTrack.dwHoverTime = 0;
mTrack.hwndTrack = mWnd;
TrackMouseEvent(&mTrack);
break;
}
// We've transitioned from non-client to outside of the window, so
// fall-through to the WM_MOUSELEAVE handler.
[[fallthrough]];
}
case WM_MOUSELEAVE: {
if (!mMousePresent) break;
if (mSimulatedClientArea) break;
mMousePresent = false;
// Check if the mouse is over the fullscreen transition window, if so
// clear sLastMouseMovePoint. This way the WM_MOUSEMOVE we get after the
// transition window disappears will not be ignored, even if the mouse
// hasn't moved.
if (mTransitionWnd && WindowAtMouse() == mTransitionWnd) {
sLastMouseMovePoint = {0};
}
// We need to check mouse button states and put them in for
// wParam.
WPARAM mouseState = (GetKeyState(VK_LBUTTON) ? MK_LBUTTON : 0) |
(GetKeyState(VK_MBUTTON) ? MK_MBUTTON : 0) |
(GetKeyState(VK_RBUTTON) ? MK_RBUTTON : 0);
// Synthesize an event position because we don't get one from
// WM_MOUSELEAVE.
LPARAM pos = lParamToClient(::GetMessagePos());
DispatchMouseEvent(eMouseExitFromWidget, mouseState, pos, false,
MouseButton::ePrimary, MOUSE_INPUT_SOURCE());
} break;
case WM_CONTEXTMENU: {
// If the context menu is brought up by a touch long-press, then
// the APZ code is responsible for dealing with this, so we don't
// need to do anything.
if (mTouchWindow &&
MOUSE_INPUT_SOURCE() == MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
MOZ_ASSERT(mAPZC); // since mTouchWindow is true, APZ must be enabled
result = true;
break;
}
// If this WM_CONTEXTMENU is triggered by a mouse's secondary button up
// event in overscroll gutter, we shouldn't open context menu.
if (MOUSE_INPUT_SOURCE() == MouseEvent_Binding::MOZ_SOURCE_MOUSE &&
mNeedsToPreventContextMenu) {
result = true;
break;
}
// if the context menu is brought up from the keyboard, |lParam|
// will be -1.
LPARAM pos;
bool contextMenukey = false;
if (lParam == -1) {
contextMenukey = true;
pos = lParamToClient(GetMessagePos());
} else {
pos = lParamToClient(lParam);
}
uint16_t inputSource = MOUSE_INPUT_SOURCE();
int16_t button =
(contextMenukey ||
inputSource == dom::MouseEvent_Binding::MOZ_SOURCE_TOUCH)
? MouseButton::ePrimary
: MouseButton::eSecondary;
result = DispatchMouseEvent(eContextMenu, wParam, pos, contextMenukey,
button, inputSource);
if (lParam != -1 && !result && mCustomNonClient &&
mDraggableRegion.Contains(GET_X_LPARAM(pos), GET_Y_LPARAM(pos))) {
// Blank area hit, throw up the system menu.
DisplaySystemMenu(mWnd, mFrameState->GetSizeMode(), mIsRTL,
GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
result = true;
}
} break;
case WM_POINTERLEAVE:
case WM_POINTERDOWN:
case WM_POINTERUP:
case WM_POINTERUPDATE:
result = OnPointerEvents(msg, wParam, lParam);
if (result) {
DispatchPendingEvents();
}
break;
case DM_POINTERHITTEST:
if (mDmOwner) {
UINT contactId = GET_POINTERID_WPARAM(wParam);
POINTER_INPUT_TYPE pointerType;
if (mPointerEvents.GetPointerType(contactId, &pointerType) &&
pointerType == PT_TOUCHPAD) {
mDmOwner->SetContact(contactId);
}
}
break;
case WM_LBUTTONDBLCLK:
result = DispatchMouseEvent(eMouseDoubleClick, wParam, lParam, false,
MouseButton::ePrimary, MOUSE_INPUT_SOURCE());
DispatchPendingEvents();
break;
case WM_MBUTTONDOWN:
result = DispatchMouseEvent(eMouseDown, wParam, lParam, false,
MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
DispatchPendingEvents();
break;
case WM_MBUTTONUP:
result = DispatchMouseEvent(eMouseUp, wParam, lParam, false,
MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
DispatchPendingEvents();
break;
case WM_MBUTTONDBLCLK:
result = DispatchMouseEvent(eMouseDoubleClick, wParam, lParam, false,
MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
DispatchPendingEvents();
break;
case WM_NCMBUTTONDOWN:
result = DispatchMouseEvent(eMouseDown, 0, lParamToClient(lParam), false,
MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
DispatchPendingEvents();
break;
case WM_NCMBUTTONUP:
result = DispatchMouseEvent(eMouseUp, 0, lParamToClient(lParam), false,
MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
DispatchPendingEvents();
break;
case WM_NCMBUTTONDBLCLK:
result =
DispatchMouseEvent(eMouseDoubleClick, 0, lParamToClient(lParam),
false, MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
DispatchPendingEvents();
break;
case WM_RBUTTONDOWN:
result =
DispatchMouseEvent(eMouseDown, wParam, lParam, false,
MouseButton::eSecondary, MOUSE_INPUT_SOURCE(),
mPointerEvents.GetCachedPointerInfo(msg, wParam));
DispatchPendingEvents();
break;
case WM_RBUTTONUP:
result =
DispatchMouseEvent(eMouseUp, wParam, lParam, false,
MouseButton::eSecondary, MOUSE_INPUT_SOURCE(),
mPointerEvents.GetCachedPointerInfo(msg, wParam));
DispatchPendingEvents();
break;
case WM_RBUTTONDBLCLK:
result =
DispatchMouseEvent(eMouseDoubleClick, wParam, lParam, false,
MouseButton::eSecondary, MOUSE_INPUT_SOURCE());
DispatchPendingEvents();
break;
case WM_NCRBUTTONDOWN:
result =
DispatchMouseEvent(eMouseDown, 0, lParamToClient(lParam), false,
MouseButton::eSecondary, MOUSE_INPUT_SOURCE());
DispatchPendingEvents();
break;
case WM_NCRBUTTONUP:
result =
DispatchMouseEvent(eMouseUp, 0, lParamToClient(lParam), false,
MouseButton::eSecondary, MOUSE_INPUT_SOURCE());
DispatchPendingEvents();
break;
case WM_NCRBUTTONDBLCLK:
result = DispatchMouseEvent(eMouseDoubleClick, 0, lParamToClient(lParam),
false, MouseButton::eSecondary,
MOUSE_INPUT_SOURCE());
DispatchPendingEvents();
break;
// Windows doesn't provide to customize the behavior of 4th nor 5th button
// of mouse. If 5-button mouse works with standard mouse deriver of
// Windows, users cannot disable 4th button (browser back) nor 5th button
// (browser forward). We should allow to do it with our prefs since we can
// prevent Windows to generate WM_APPCOMMAND message if WM_XBUTTONUP
// messages are not sent to DefWindowProc.
case WM_XBUTTONDOWN:
case WM_XBUTTONUP:
case WM_NCXBUTTONDOWN:
case WM_NCXBUTTONUP:
*aRetValue = TRUE;
switch (GET_XBUTTON_WPARAM(wParam)) {
case XBUTTON1:
result = !Preferences::GetBool("mousebutton.4th.enabled", true);
break;
case XBUTTON2:
result = !Preferences::GetBool("mousebutton.5th.enabled", true);
break;
default:
break;
}
break;
case WM_SIZING: {
if (mAspectRatio > 0) {
LPRECT rect = (LPRECT)lParam;
int32_t newWidth, newHeight;
// The following conditions and switch statement borrow heavily from the
// Chromium source code from
// https://chromium.googlesource.com/chromium/src/+/456d6e533cfb4531995e0ef52c279d4b5aa8a352/ui/views/window/window_resize_utils.cc#45
if (wParam == WMSZ_LEFT || wParam == WMSZ_RIGHT ||
wParam == WMSZ_TOPLEFT || wParam == WMSZ_BOTTOMLEFT) {
newWidth = rect->right - rect->left;
newHeight = newWidth / mAspectRatio;
if (newHeight < mSizeConstraints.mMinSize.height) {
newHeight = mSizeConstraints.mMinSize.height;
newWidth = newHeight * mAspectRatio;
} else if (newHeight > mSizeConstraints.mMaxSize.height) {
newHeight = mSizeConstraints.mMaxSize.height;
newWidth = newHeight * mAspectRatio;
}
} else {
newHeight = rect->bottom - rect->top;
newWidth = newHeight * mAspectRatio;
if (newWidth < mSizeConstraints.mMinSize.width) {
newWidth = mSizeConstraints.mMinSize.width;
newHeight = newWidth / mAspectRatio;
} else if (newWidth > mSizeConstraints.mMaxSize.width) {
newWidth = mSizeConstraints.mMaxSize.width;
newHeight = newWidth / mAspectRatio;
}
}
switch (wParam) {
case WMSZ_RIGHT:
case WMSZ_BOTTOM:
rect->right = newWidth + rect->left;
rect->bottom = rect->top + newHeight;
break;
case WMSZ_TOP:
rect->right = newWidth + rect->left;
rect->top = rect->bottom - newHeight;
break;
case WMSZ_LEFT:
case WMSZ_TOPLEFT:
rect->left = rect->right - newWidth;
rect->top = rect->bottom - newHeight;
break;
case WMSZ_TOPRIGHT:
rect->right = rect->left + newWidth;
rect->top = rect->bottom - newHeight;
break;
case WMSZ_BOTTOMLEFT:
rect->left = rect->right - newWidth;
rect->bottom = rect->top + newHeight;
break;
case WMSZ_BOTTOMRIGHT:
rect->right = rect->left + newWidth;
rect->bottom = rect->top + newHeight;
break;
}
}
// When we get WM_ENTERSIZEMOVE we don't know yet if we're in a live
// resize or move event. Instead we wait for first VM_SIZING message
// within a ENTERSIZEMOVE to consider this a live resize event.
if (mResizeState == IN_SIZEMOVE) {
mResizeState = RESIZING;
NotifyLiveResizeStarted();
}
break;
}
case WM_MOVING:
FinishLiveResizing(MOVING);
if (WinUtils::IsPerMonitorDPIAware()) {
// Sometimes, we appear to miss a WM_DPICHANGED message while moving
// a window around. Therefore, call ChangedDPI and ResetLayout here
// if it appears that the window's scaling is not what we expect.
// This causes the prescontext and appshell window management code to
// check the appUnitsPerDevPixel value and current widget size, and
// refresh them if necessary. If nothing has changed, these calls will
// return without actually triggering any extra reflow or painting.
if (WinUtils::LogToPhysFactor(mWnd) != mDefaultScale) {
ChangedDPI();
ResetLayout();
}
}
break;
case WM_ENTERSIZEMOVE: {
if (mResizeState == NOT_RESIZING) {
mResizeState = IN_SIZEMOVE;
}
break;
}
case WM_EXITSIZEMOVE: {
FinishLiveResizing(NOT_RESIZING);
if (!sIsInMouseCapture) {
NotifySizeMoveDone();
}
// Windows spins a separate hidden event loop when moving a window so we
// don't hear mouse events during this time and WM_EXITSIZEMOVE is fired
// when the hidden event loop exits. We set mDraggingWindowWithMouse to
// true in WM_NCLBUTTONDOWN when we started moving the window with the
// mouse so we know that if mDraggingWindowWithMouse is true, we can send
// a mouse up event.
if (mDraggingWindowWithMouse) {
mDraggingWindowWithMouse = false;
result = DispatchMouseEvent(
eMouseUp, wParam, lParam, false, MouseButton::ePrimary,
MOUSE_INPUT_SOURCE(),
mPointerEvents.GetCachedPointerInfo(msg, wParam));
}
break;
}
case WM_NCLBUTTONDBLCLK:
DispatchMouseEvent(eMouseDoubleClick, 0, lParamToClient(lParam), false,
MouseButton::ePrimary, MOUSE_INPUT_SOURCE());
result = DispatchMouseEvent(eMouseUp, 0, lParamToClient(lParam), false,
MouseButton::ePrimary, MOUSE_INPUT_SOURCE());
DispatchPendingEvents();
break;
case WM_NCLBUTTONDOWN: {
// TODO(rkraesig): do we really need this? It should be the same as
// wParam, and when it's not we probably don't want it here.
auto const hitTest =
ClientMarginHitTestPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
// Dispatch a custom event when this happens in the draggable region, so
// that non-popup-based panels can react to it. This doesn't send an
// actual mousedown event because that would break dragging or interfere
// with other mousedown handling in the caption area.
if (hitTest == HTCAPTION) {
DispatchCustomEvent(u"draggableregionleftmousedown"_ns);
mDraggingWindowWithMouse = true;
}
if (IsWindowButton(int32_t(wParam)) && mCustomNonClient) {
DispatchMouseEvent(eMouseDown, wParamFromGlobalMouseState(),
lParamToClient(lParam), false, MouseButton::ePrimary,
MOUSE_INPUT_SOURCE(), nullptr, IsNonclient::Yes);
DispatchPendingEvents();
result = true;
}
break;
}
case WM_NCLBUTTONUP: {
if (mCustomNonClient) {
result = DispatchMouseEvent(eMouseUp, wParamFromGlobalMouseState(),
lParamToClient(lParam), false,
MouseButton::ePrimary, MOUSE_INPUT_SOURCE(),
nullptr, IsNonclient::Yes);
DispatchPendingEvents();
} else {
result = false;
}
break;
}
case WM_APPCOMMAND: {
MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd);
result = HandleAppCommandMsg(nativeMsg, aRetValue);
break;
}
// The WM_ACTIVATE event is fired when a window is raised or lowered,
// and the loword of wParam specifies which. But we don't want to tell
// the focus system about this until the WM_SETFOCUS or WM_KILLFOCUS
// events are fired. Instead, set either the sJustGotActivate or
// gJustGotDeactivate flags and activate/deactivate once the focus
// events arrive.
case WM_ACTIVATE: {
int32_t fActive = LOWORD(wParam);
if (!mWidgetListener) {
break;
}
if (WA_INACTIVE == fActive) {
// when minimizing a window, the deactivation and focus events will
// be fired in the reverse order. Instead, just deactivate right away.
// This can also happen when a modal system dialog is opened, so check
// if the last window to receive the WM_KILLFOCUS message was this one
// or a child of this one.
if (HIWORD(wParam) ||
(mLastKillFocusWindow &&
(GetTopLevelForFocus(mLastKillFocusWindow) == mWnd))) {
DispatchFocusToTopLevelWindow(false);
} else {
sJustGotDeactivate = true;
}
if (IsTopLevelWidget()) {
mLastKeyboardLayout = KeyboardLayout::GetLayout();
}
} else {
StopFlashing();
sJustGotActivate = true;
WidgetMouseEvent event(true, eMouseActivate, this,
WidgetMouseEvent::eReal);
InitEvent(event);
ModifierKeyState modifierKeyState;
modifierKeyState.InitInputEvent(event);
DispatchInputEvent(&event);
if (sSwitchKeyboardLayout && mLastKeyboardLayout) {
ActivateKeyboardLayout(mLastKeyboardLayout, 0);
}
#ifdef ACCESSIBILITY
a11y::LazyInstantiator::ResetUiaDetectionCache();
#endif
}
} break;
case WM_ACTIVATEAPP: {
// Bug 1851991: Sometimes this can be called before gfxPlatform::Init
// when a window is created very early. In that case we just forego
// setting this and accept the GPU process might briefly run at a lower
// priority.
if (GPUProcessManager::Get()) {
GPUProcessManager::Get()->SetAppInForeground(wParam);
}
} break;
case WM_MOUSEACTIVATE:
// A popup with a parent owner should not be activated when clicked but
// should still allow the mouse event to be fired, so the return value
// is set to MA_NOACTIVATE. But if the owner isn't the frontmost window,
// just use default processing so that the window is activated.
if (IsPopup() && IsOwnerForegroundWindow()) {
*aRetValue = MA_NOACTIVATE;
result = true;
}
break;
case WM_WINDOWPOSCHANGING: {
LPWINDOWPOS info = (LPWINDOWPOS)lParam;
OnWindowPosChanging(info);
result = true;
} break;
// Workaround for race condition in explorer.exe.
case MOZ_WM_FULLSCREEN_STATE_UPDATE: {
TaskbarConcealer::OnAsyncStateUpdateRequest(mWnd);
result = true;
} break;
case WM_GETMINMAXINFO: {
MINMAXINFO* mmi = (MINMAXINFO*)lParam;
// Set the constraints. The minimum size should also be constrained to the
// default window maximum size so that it fits on screen.
mmi->ptMinTrackSize.x =
std::min((int32_t)mmi->ptMaxTrackSize.x,
std::max((int32_t)mmi->ptMinTrackSize.x,
mSizeConstraints.mMinSize.width));
mmi->ptMinTrackSize.y =
std::min((int32_t)mmi->ptMaxTrackSize.y,
std::max((int32_t)mmi->ptMinTrackSize.y,
mSizeConstraints.mMinSize.height));
mmi->ptMaxTrackSize.x = std::min((int32_t)mmi->ptMaxTrackSize.x,
mSizeConstraints.mMaxSize.width);
mmi->ptMaxTrackSize.y = std::min((int32_t)mmi->ptMaxTrackSize.y,
mSizeConstraints.mMaxSize.height);
} break;
case WM_SETFOCUS: {
WndProcUrgentInvocation::Marker _marker;
// If previous focused window isn't ours, it must have received the
// redirected message. So, we should forget it.
if (!WinUtils::IsOurProcessWindow(HWND(wParam))) {
RedirectedKeyDownMessageManager::Forget();
}
if (sJustGotActivate) {
DispatchFocusToTopLevelWindow(true);
}
TaskbarConcealer::OnFocusAcquired(this);
} break;
case WM_KILLFOCUS:
if (sJustGotDeactivate) {
DispatchFocusToTopLevelWindow(false);
} else {
mLastKillFocusWindow = mWnd;
}
break;
case WM_WINDOWPOSCHANGED: {
WINDOWPOS* wp = (LPWINDOWPOS)lParam;
OnWindowPosChanged(wp);
TaskbarConcealer::OnWindowPosChanged(this);
result = true;
} break;
case WM_INPUTLANGCHANGEREQUEST:
*aRetValue = TRUE;
result = false;
break;
case WM_INPUTLANGCHANGE:
KeyboardLayout::GetInstance()->OnLayoutChange(
reinterpret_cast<HKL>(lParam));
nsBidiKeyboard::OnLayoutChange();
result = false; // always pass to child window
break;
case WM_DESTROYCLIPBOARD: {
nsIClipboard* clipboard;
nsresult rv = CallGetService(kCClipboardCID, &clipboard);
if (NS_SUCCEEDED(rv)) {
clipboard->EmptyClipboard(nsIClipboard::kGlobalClipboard);
NS_RELEASE(clipboard);
}
} break;
#ifdef ACCESSIBILITY
case WM_GETOBJECT: {
*aRetValue = 0;
// Do explicit casting to make it working on 64bit systems (see bug 649236
// for details).
int32_t objId = static_cast<DWORD>(lParam);
if (objId == OBJID_CLIENT) { // oleacc.dll will be loaded dynamically
RefPtr<IAccessible> root(
a11y::LazyInstantiator::GetRootAccessible(mWnd));
if (root) {
*aRetValue = LresultFromObject(IID_IAccessible, wParam, root);
a11y::LazyInstantiator::EnableBlindAggregation(mWnd);
result = true;
}
} else if (objId == UiaRootObjectId) {
if (RefPtr<IRawElementProviderSimple> root =
a11y::LazyInstantiator::GetRootUia(mWnd)) {
*aRetValue = UiaReturnRawElementProvider(mWnd, wParam, lParam, root);
a11y::LazyInstantiator::EnableBlindAggregation(mWnd);
result = true;
}
}
} break;
#endif
case WM_SYSCOMMAND: {
WPARAM const filteredWParam = (wParam & 0xFFF0);
// SC_CLOSE may trigger a synchronous confirmation prompt. If we're in the
// middle of something important, put off responding to it.
if (filteredWParam == SC_CLOSE && WndProcUrgentInvocation::IsActive()) {
::PostMessageW(mWnd, msg, wParam, lParam);
result = true;
break;
}
if (mFrameState->GetSizeMode() == nsSizeMode_Fullscreen &&
filteredWParam == SC_RESTORE &&
GetCurrentShowCmd(mWnd) != SW_SHOWMINIMIZED) {
mFrameState->EnsureFullscreenMode(false);
result = true;
}
if (filteredWParam == SC_KEYMENU && lParam == VK_SPACE) {
const auto sizeMode = mFrameState->GetSizeMode();
// Handle the system menu manually when we're in full screen mode or
// with custom titlebar so we can set the appropriate options.
if (sizeMode == nsSizeMode_Fullscreen || mCustomNonClient) {
// Historically on fullscreen windows we've used this offset from the
// top left as our context menu position. Note that if the point we
// supply is offscreen, Windows will still try to put our menu in the
// right place.
constexpr LayoutDeviceIntPoint offset(20, 20);
auto pos = GetScreenBounds().TopLeft() + offset;
DisplaySystemMenu(mWnd, sizeMode, mIsRTL, pos.x, pos.y);
result = true;
}
}
} break;
case WM_DPICHANGED: {
LPRECT rect = (LPRECT)lParam;
OnDPIChanged(rect->left, rect->top, rect->right - rect->left,
rect->bottom - rect->top);
break;
}
/* Gesture support events */
case WM_TABLET_QUERYSYSTEMGESTURESTATUS:
// According to MS samples, this must be handled to enable
// rotational support in multi-touch drivers.
result = true;
*aRetValue = TABLET_ROTATE_GESTURE_ENABLE;
break;
case WM_TOUCH:
result = OnTouch(wParam, lParam);
if (result) {
*aRetValue = 0;
}
break;
case WM_GESTURE:
result = OnGesture(wParam, lParam);
break;
case WM_GESTURENOTIFY: {
// A GestureNotify event is dispatched to decide which single-finger
// panning direction should be active (including none) and if pan
// feedback should be displayed. Java and plugin windows can make their
// own calls.
GESTURENOTIFYSTRUCT* gestureinfo = (GESTURENOTIFYSTRUCT*)lParam;
nsPointWin touchPoint;
touchPoint = gestureinfo->ptsLocation;
touchPoint.ScreenToClient(mWnd);
WidgetGestureNotifyEvent gestureNotifyEvent(true, eGestureNotify, this);
gestureNotifyEvent.mRefPoint =
LayoutDeviceIntPoint::FromUnknownPoint(touchPoint);
nsEventStatus status;
DispatchEvent(&gestureNotifyEvent, status);
mDisplayPanFeedback = gestureNotifyEvent.mDisplayPanFeedback;
if (!mTouchWindow) {
mGesture.SetWinGestureSupport(mWnd, gestureNotifyEvent.mPanDirection);
}
result = false; // should always bubble to DefWindowProc
} break;
case WM_CLEAR: {
WidgetContentCommandEvent command(true, eContentCommandDelete, this);
DispatchWindowEvent(command);
result = true;
} break;
case WM_CUT: {
WidgetContentCommandEvent command(true, eContentCommandCut, this);
DispatchWindowEvent(command);
result = true;
} break;
case WM_COPY: {
WidgetContentCommandEvent command(true, eContentCommandCopy, this);
DispatchWindowEvent(command);
result = true;
} break;
case WM_PASTE: {
WidgetContentCommandEvent command(true, eContentCommandPaste, this);
DispatchWindowEvent(command);
result = true;
} break;
case EM_UNDO: {
WidgetContentCommandEvent command(true, eContentCommandUndo, this);
DispatchWindowEvent(command);
*aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
result = true;
} break;
case EM_REDO: {
WidgetContentCommandEvent command(true, eContentCommandRedo, this);
DispatchWindowEvent(command);
*aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
result = true;
} break;
case EM_CANPASTE: {
// Support EM_CANPASTE message only when wParam isn't specified or
// is plain text format.
if (wParam == 0 || wParam == CF_TEXT || wParam == CF_UNICODETEXT) {
WidgetContentCommandEvent command(true, eContentCommandPaste, this,
true);
DispatchWindowEvent(command);
*aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
result = true;
}
} break;
case EM_CANUNDO: {
WidgetContentCommandEvent command(true, eContentCommandUndo, this, true);
DispatchWindowEvent(command);
*aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
result = true;
} break;
case EM_CANREDO: {
WidgetContentCommandEvent command(true, eContentCommandRedo, this, true);
DispatchWindowEvent(command);
*aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
result = true;
} break;
case MOZ_WM_SKEWFIX: {
TimeStamp skewStamp;
if (CurrentWindowsTimeGetter::GetAndClearBackwardsSkewStamp(wParam,
&skewStamp)) {
TimeConverter().CompensateForBackwardsSkew(::GetMessageTime(),
skewStamp);
}
} break;
default: {
if (msg == nsAppShell::GetTaskbarButtonCreatedMessage()) {
SetHasTaskbarIconBeenCreated();
}
} break;
}
//*aRetValue = result;
if (mWnd) {
return result;
} else {
// Events which caused mWnd destruction and aren't consumed
// will crash during the Windows default processing.
return true;
}
}