bool nsWindow::ProcessMessageInternal()

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;
  }
}