// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/base/test/ui_controls_internal_win.h"

#include <windows.h>

#include <commctrl.h>

#include <algorithm>
#include <cmath>
#include <utility>
#include <vector>

#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/no_destructor.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/run_until.h"
#include "base/test/test_timeouts.h"
#include "base/threading/platform_thread.h"
#include "base/threading/thread_checker.h"
#include "base/win/win_util.h"
#include "ui/base/win/event_creation_utils.h"
#include "ui/display/win/screen_win.h"
#include "ui/events/keycodes/keyboard_code_conversion_win.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/gfx/geometry/point.h"

namespace {

bool IsKeyEvent(WPARAM message_type) {
  return message_type == WM_KEYDOWN || message_type == WM_KEYUP;
}

// InputDispatcher ------------------------------------------------------------

// InputDispatcher is used to listen for a mouse/keyboard event. Only one
// instance may be alive at a time. The callback is run when the appropriate
// event is received.
class InputDispatcher {
 public:
  // Constructs an InputDispatcher that will invoke |callback| when
  // |message_type| is received. This must be invoked on thread, after the input
  // is sent but before it is processed.
  static void CreateForMouseEvent(base::OnceClosure callback,
                                  WPARAM message_type);

  // Constructs an InputDispatcher that will invoke `callback` after
  // `num_key_events_awaited` events of type `wait_for` have been received.
  static void CreateForKeyEvent(base::OnceClosure callback,
                                ui_controls::KeyEventType wait_for,
                                int num_key_events_awaited);

  // Special case of CreateForMessage() for WM_MOUSEMOVE. Upon receipt, an error
  // message is logged if the destination of the move is not |screen_point|.
  // |callback| is run regardless after a sufficiently long delay. This
  // generally happens when another process has a window over the test's window,
  // or if |screen_point| is not over a window owned by the test.
  static void CreateForMouseMove(base::OnceClosure callback,
                                 const gfx::Point& screen_point);

  InputDispatcher(const InputDispatcher&) = delete;
  InputDispatcher& operator=(const InputDispatcher&) = delete;

 private:
  // Generic message
  InputDispatcher(base::OnceClosure callback,
                  WPARAM message_waiting_for,
                  UINT system_queue_flag);

  // WM_KEYDOWN or WM_KEYUP
  InputDispatcher(base::OnceClosure callback,
                  WPARAM message_waiting_for,
                  UINT system_queue_flag,
                  int num_key_events_awaited);

  // WM_MOUSEMOVE
  InputDispatcher(base::OnceClosure callback,
                  WPARAM message_waiting_for,
                  UINT system_queue_flag,
                  const gfx::Point& screen_point);

  ~InputDispatcher();

  // Installs the dispatcher as the current hook.
  void InstallHook();

  // Callback from hook when a mouse message is received.
  static LRESULT CALLBACK MouseHook(int n_code, WPARAM w_param, LPARAM l_param);

  // Callback from hook when a key message is received.
  static LRESULT CALLBACK KeyHook(int n_code, WPARAM w_param, LPARAM l_param);

  // Invoked from the hook. If |message_id| matches message_waiting_for_
  // MatchingMessageProcessed() is invoked. |mouse_hook_struct| contains extra
  // information about the mouse event.
  void DispatchedMessage(UINT message_id,
                         const MOUSEHOOKSTRUCT* mouse_hook_struct);

  // Invoked when a matching event is found. Must be invoked through a task
  // posted from the hook so that the event, which is processed after the hook,
  // has already been handled.
  // |definitively_done| is set to true if this event is definitely the one we
  // were waiting for (i.e., we will resume regardless of the presence of
  // |system_queue_flag_| messages in the queue).
  void MatchingMessageProcessed(bool definitively_done);

  // Invoked when the hook for a mouse move is not called within a reasonable
  // time. This likely means that a window from another process is over a test
  // window, so the event does not reach this process.
  void OnTimeout();

  // The current dispatcher if a hook is installed; otherwise, nullptr;
  static InputDispatcher* current_dispatcher_;

  // Return value from SetWindowsHookEx.
  static HHOOK hook_;

  THREAD_CHECKER(thread_checker_);

  // The callback to run when the desired message is received.
  base::OnceClosure callback_;

  // The message on which the instance is waiting.
  const WPARAM message_waiting_for_;

  // The system queue flag (ref. ::GetQueueStatus) which the awaited event is
  // reflected in.
  const UINT system_queue_flag_;

  // The number of messages to receive before dispatching `callback_`. Only
  // relevant when `message_waiting_for_` is WM_KEYDOWN or WM_KEYUP.
  int num_key_events_awaited_ = 0;

  // The desired mouse position for a mouse move event.
  const gfx::Point expected_mouse_location_;

  // Whether all desired messages were observed, but MatchingMessageProcessed()
  // is flushing remaining messages of type `system_queue_flag_`.
  bool flushing_messages_ = false;

  base::WeakPtrFactory<InputDispatcher> weak_factory_{this};
};

// static
InputDispatcher* InputDispatcher::current_dispatcher_ = nullptr;

// static
HHOOK InputDispatcher::hook_ = nullptr;

// static
void InputDispatcher::CreateForMouseEvent(base::OnceClosure callback,
                                          WPARAM message_type) {
  DCHECK(message_type == WM_LBUTTONDOWN || message_type == WM_LBUTTONUP ||
         message_type == WM_MBUTTONDOWN || message_type == WM_MBUTTONUP ||
         message_type == WM_RBUTTONDOWN || message_type == WM_RBUTTONUP)
      << message_type;

  // Owns self.
  new InputDispatcher(std::move(callback), message_type, QS_MOUSEBUTTON);
}

// static
void InputDispatcher::CreateForKeyEvent(base::OnceClosure callback,
                                        ui_controls::KeyEventType wait_for,
                                        int num_key_events_awaited) {
  CHECK(wait_for == ui_controls::KeyEventType::kKeyPress ||
        wait_for == ui_controls::KeyEventType::kKeyRelease);
  // Owns self.
  new InputDispatcher(
      std::move(callback),
      wait_for == ui_controls::KeyEventType::kKeyPress ? WM_KEYDOWN : WM_KEYUP,
      QS_KEY, num_key_events_awaited);
}

// static
void InputDispatcher::CreateForMouseMove(base::OnceClosure callback,
                                         const gfx::Point& screen_point) {
  // Owns self.
  new InputDispatcher(std::move(callback), WM_MOUSEMOVE, QS_MOUSEMOVE,
                      screen_point);
}

InputDispatcher::InputDispatcher(base::OnceClosure callback,
                                 WPARAM message_waiting_for,
                                 UINT system_queue_flag)
    : callback_(std::move(callback)),
      message_waiting_for_(message_waiting_for),
      system_queue_flag_(system_queue_flag) {
  InstallHook();
}

InputDispatcher::InputDispatcher(base::OnceClosure callback,
                                 WPARAM message_waiting_for,
                                 UINT system_queue_flag,
                                 int num_key_events_awaited)
    : callback_(std::move(callback)),
      message_waiting_for_(message_waiting_for),
      system_queue_flag_(system_queue_flag),
      num_key_events_awaited_(num_key_events_awaited) {
  CHECK(IsKeyEvent(message_waiting_for_));
  InstallHook();
}

InputDispatcher::InputDispatcher(base::OnceClosure callback,
                                 WPARAM message_waiting_for,
                                 UINT system_queue_flag,
                                 const gfx::Point& screen_point)
    : callback_(std::move(callback)),
      message_waiting_for_(message_waiting_for),
      system_queue_flag_(system_queue_flag),
      expected_mouse_location_(screen_point) {
  CHECK_EQ(message_waiting_for_, static_cast<WPARAM>(WM_MOUSEMOVE));
  InstallHook();
}

InputDispatcher::~InputDispatcher() {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK_EQ(current_dispatcher_, this);
  current_dispatcher_ = nullptr;
  UnhookWindowsHookEx(hook_);
  hook_ = nullptr;
}

void InputDispatcher::InstallHook() {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(!current_dispatcher_);

  current_dispatcher_ = this;

  int hook_type;
  HOOKPROC hook_function;
  if (IsKeyEvent(message_waiting_for_)) {
    hook_type = WH_KEYBOARD;
    hook_function = &KeyHook;
  } else {
    // WH_CALLWNDPROCRET does not generate mouse messages for some reason.
    hook_type = WH_MOUSE;
    hook_function = &MouseHook;
    if (message_waiting_for_ == WM_MOUSEMOVE) {
      // Things don't go well with move events sometimes. Bail out if it takes
      // too long.
      base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
          FROM_HERE,
          base::BindOnce(&InputDispatcher::OnTimeout,
                         weak_factory_.GetWeakPtr()),
          TestTimeouts::action_timeout());
    }
  }
  hook_ =
      SetWindowsHookEx(hook_type, hook_function, nullptr, GetCurrentThreadId());
  DPCHECK(hook_);
}

// static
LRESULT CALLBACK InputDispatcher::MouseHook(int n_code,
                                            WPARAM w_param,
                                            LPARAM l_param) {
  HHOOK hook = hook_;
  if (n_code == HC_ACTION) {
    DCHECK(current_dispatcher_);
    current_dispatcher_->DispatchedMessage(
        static_cast<UINT>(w_param),
        reinterpret_cast<MOUSEHOOKSTRUCT*>(l_param));
  }
  return CallNextHookEx(hook, n_code, w_param, l_param);
}

// static
LRESULT CALLBACK InputDispatcher::KeyHook(int n_code,
                                          WPARAM w_param,
                                          LPARAM l_param) {
  if (n_code == HC_ACTION) {
    const WPARAM type = (HIWORD(l_param) & KF_UP) ? WM_KEYUP : WM_KEYDOWN;
    CHECK(current_dispatcher_);
    if (type == current_dispatcher_->message_waiting_for_) {
      base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE,
          base::BindOnce(&InputDispatcher::MatchingMessageProcessed,
                         current_dispatcher_->weak_factory_.GetWeakPtr(),
                         false));
    }
  }
  return CallNextHookEx(hook_, n_code, w_param, l_param);
}

void InputDispatcher::DispatchedMessage(
    UINT message_id,
    const MOUSEHOOKSTRUCT* mouse_hook_struct) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  if (message_id == message_waiting_for_) {
    bool definitively_done = false;
    if (message_id == WM_MOUSEMOVE) {
      // Allow a slight offset, targets are never one pixel wide and pixel math
      // is imprecise (see SendMouseMoveImpl()).
      gfx::Point actual_location(mouse_hook_struct->pt);
      auto offset = expected_mouse_location_ - actual_location;
      definitively_done = std::abs(offset.x()) + std::abs(offset.y()) < 2;

      // Verify that the mouse ended up at the desired location.
      LOG_IF(ERROR, !definitively_done)
          << "Mouse moved to (" << mouse_hook_struct->pt.x << ", "
          << mouse_hook_struct->pt.y << ") rather than ("
          << expected_mouse_location_.x() << ", "
          << expected_mouse_location_.y()
          << "); check the math in SendMouseMoveImpl.";
    }
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(&InputDispatcher::MatchingMessageProcessed,
                       weak_factory_.GetWeakPtr(), definitively_done));
  } else if ((message_waiting_for_ == WM_LBUTTONDOWN &&
              message_id == WM_LBUTTONDBLCLK) ||
             (message_waiting_for_ == WM_MBUTTONDOWN &&
              message_id == WM_MBUTTONDBLCLK) ||
             (message_waiting_for_ == WM_RBUTTONDOWN &&
              message_id == WM_RBUTTONDBLCLK)) {
    LOG(WARNING) << "Double click event being treated as single-click. "
                 << "This may result in different event processing behavior. "
                 << "If you need a single click try moving the mouse between "
                 << "down events.";
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(&InputDispatcher::MatchingMessageProcessed,
                                  weak_factory_.GetWeakPtr(), false));
  }
}

void InputDispatcher::MatchingMessageProcessed(bool definitively_done) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  // Guard against re-entrancy.
  if (flushing_messages_)
    return;

  if (IsKeyEvent(message_waiting_for_)) {
    --num_key_events_awaited_;
    if (num_key_events_awaited_ != 0) {
      return;
    }
  }

  // Unless specified otherwise by |definitively_done| : resume on the last
  // event of its type only (instead of the first one) to prevent flakes when
  // InputDispatcher is created while there are preexisting matching events
  // remaining in the queue. Emit a warning to help diagnose flakes should the
  // queue somehow never become empty of such events.
  if (!definitively_done) {
    while (HIWORD(::GetQueueStatus(system_queue_flag_))) {
      LOG(WARNING) << "Got all expected messages, but the queue still contains "
                      "messages of type "
                   << system_queue_flag_
                   << ". Pumping messages until it's no longer the case.";

      // RunLoop::Run() calls MessagePumpForUI::ProcessNextWindowsMessage(),
      // which should remove at least one message from the queue.
      flushing_messages_ = true;
      auto weak_ptr = weak_factory_.GetWeakPtr();
      base::RunLoop().RunUntilIdle();
      DCHECK(weak_ptr);
    }
  }

  // Delete |this| before running the callback to allow callers to chain input
  // events.
  auto callback = std::move(callback_);
  delete this;
  std::move(callback).Run();
}

void InputDispatcher::OnTimeout() {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  LOG(ERROR) << "Timed out waiting for mouse move event. The test will now "
                "continue, but may fail.";

  auto callback = std::move(callback_);
  delete this;
  std::move(callback).Run();
}

// WindowMessageObserver is used to listen for a message sent to a target
// window using a window subclass. The callback is run when the message matches.
class WindowMessageObserver {
 public:
  static void Create(base::OnceClosure callback,
                     HWND target_hwnd,
                     WPARAM message_type);

  ~WindowMessageObserver();

  WindowMessageObserver(const WindowMessageObserver&) = delete;
  WindowMessageObserver& operator=(const WindowMessageObserver&) = delete;

 private:
  WindowMessageObserver(base::OnceClosure callback,
                        HWND target_hwnd,
                        WPARAM message_type);

  void RegisterObserver();

  static LRESULT CALLBACK SubclassProc(HWND hwnd,
                                       UINT msg,
                                       WPARAM w_param,
                                       LPARAM l_param,
                                       UINT_PTR id,
                                       DWORD_PTR ref_data);

  void ProcessMessage(HWND hwnd, UINT msg, UINT_PTR id);

  void MatchingMessageProcessed();

  THREAD_CHECKER(thread_checker_);

  base::OnceClosure callback_;
  const HWND expected_hwnd_;
  const WPARAM message_waiting_for_;

  base::WeakPtrFactory<WindowMessageObserver> weak_factory_{this};
};

// WindowMessageObserver ------------------------------------------------------

// static
void WindowMessageObserver::Create(base::OnceClosure callback,
                                   HWND target_hwnd,
                                   WPARAM message_type) {
  // Owns self.
  new WindowMessageObserver(std::move(callback), target_hwnd, message_type);
}

WindowMessageObserver::WindowMessageObserver(base::OnceClosure callback,
                                             HWND target_hwnd,
                                             WPARAM message_type)
    : callback_(std::move(callback)),
      expected_hwnd_(target_hwnd),
      message_waiting_for_(message_type) {
  RegisterObserver();
}

WindowMessageObserver::~WindowMessageObserver() {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  ::RemoveWindowSubclass(expected_hwnd_, &SubclassProc,
                         reinterpret_cast<UINT_PTR>(this));
}

void WindowMessageObserver::RegisterObserver() {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  ::SetWindowSubclass(expected_hwnd_, &SubclassProc,
                      reinterpret_cast<UINT_PTR>(this),
                      reinterpret_cast<DWORD_PTR>(this));
}

void WindowMessageObserver::ProcessMessage(HWND hwnd,
                                           UINT msg,
                                           UINT_PTR id) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  if (msg == message_waiting_for_) {
    ::RemoveWindowSubclass(hwnd, &SubclassProc, id);
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(&WindowMessageObserver::MatchingMessageProcessed,
                       weak_factory_.GetWeakPtr()));
  } else if (msg == WM_NCDESTROY) {
    ::RemoveWindowSubclass(hwnd, &SubclassProc, id);
    // Since the window is being destroyed and we never matched the message,
    // we should delete ourselves to prevent a leak.
    //
    // We must delete asynchronously (via DeleteSoon) rather than immediately:
    // 1. To allow the current subclass message dispatch stack to completely
    //    unwind before the C++ controller object is destroyed. Deleting
    //    immediately inside the window procedure callback could result in
    //    use-after-free bugs if subsequent message handlers on the call stack
    //    attempt to inspect the window subclass state.
    // 2. To prevent redundant and nested calls to RemoveWindowSubclass
    //    while the subclass proc is currently executing on the stack frame.
    base::SingleThreadTaskRunner::GetCurrentDefault()->DeleteSoon(FROM_HERE,
                                                                  this);
  }
}

// static
LRESULT CALLBACK WindowMessageObserver::SubclassProc(HWND hwnd,
                                                     UINT msg,
                                                     WPARAM w_param,
                                                     LPARAM l_param,
                                                     UINT_PTR id,
                                                     DWORD_PTR ref_data) {
  auto* observer = reinterpret_cast<WindowMessageObserver*>(ref_data);
  CHECK(observer);
  observer->ProcessMessage(hwnd, msg, id);
  return ::DefSubclassProc(hwnd, msg, w_param, l_param);
}

void WindowMessageObserver::MatchingMessageProcessed() {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  auto callback = std::move(callback_);
  delete this;
  std::move(callback).Run();
}

// Private functions ----------------------------------------------------------

UINT MapVirtualKeyToScanCode(UINT code) {
  UINT ret_code = MapVirtualKey(code, MAPVK_VK_TO_VSC);
  // We have to manually mark the following virtual
  // keys as extended or else their scancodes depend
  // on NumLock state.
  // For ex. VK_DOWN will be mapped onto either DOWN or NumPad2
  // depending on NumLock state which can lead to tests failures.
  switch (code) {
    case VK_INSERT:
    case VK_DELETE:
    case VK_HOME:
    case VK_END:
    case VK_NEXT:
    case VK_PRIOR:
    case VK_LEFT:
    case VK_RIGHT:
    case VK_UP:
    case VK_DOWN:
    case VK_NUMLOCK:
      ret_code |= KF_EXTENDED;
      break;
    default:
      break;
  }
  return ret_code;
}

// Whether scan code should be used for |key|.
// When sending keyboard events by SendInput() function, Windows does not
// "smartly" add scan code if virtual key-code is used. So these key events
// won't have scan code or DOM UI Event code string.
// But we cannot blindly send all events with scan code. For some layout
// dependent keys, the Windows may not translate them to what they used to be,
// because the test cases are usually running in headless environment with
// default keyboard layout. So fall back to use virtual key code for these keys.
bool ShouldSendThroughScanCode(ui::KeyboardCode key) {
  const DWORD native_code = ui::WindowsKeyCodeForKeyboardCode(key);
  const DWORD scan_code = MapVirtualKeyToScanCode(native_code);
  return native_code == MapVirtualKey(scan_code, MAPVK_VSC_TO_VK);
}

// Append an INPUT structure with the appropriate keyboard event
// parameters required by SendInput
void AppendKeyboardInput(ui::KeyboardCode key,
                         bool key_up,
                         std::vector<INPUT>* input) {
  INPUT key_input = {};
  key_input.type = INPUT_KEYBOARD;
  key_input.ki.wVk = ui::WindowsKeyCodeForKeyboardCode(key);
  if (ShouldSendThroughScanCode(key)) {
    key_input.ki.wScan = MapVirtualKeyToScanCode(key_input.ki.wVk);
    // When KEYEVENTF_SCANCODE is used, ki.wVk is ignored, so we do not need to
    // clear it.
    key_input.ki.dwFlags = KEYEVENTF_SCANCODE;
    if ((key_input.ki.wScan & 0xFF00) != 0)
      key_input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
  }
  if (key_up)
    key_input.ki.dwFlags |= KEYEVENTF_KEYUP;
  input->push_back(key_input);
}

// Append an INPUT structure with a simple mouse up or down event to be used
// by SendInput.
void AppendMouseInput(DWORD flags, std::vector<INPUT>* input) {
  INPUT mouse_input = {};
  mouse_input.type = INPUT_MOUSE;
  mouse_input.mi.dwFlags = flags;
  input->push_back(mouse_input);
}

// Append an INPUT array with optional accelerator keys that may be pressed
// with a keyboard or mouse event. This array will be sent by SendInput.
void AppendAcceleratorInputs(int accelerator_state,
                             bool key_up,
                             std::vector<INPUT>* input) {
  if (accelerator_state & ui_controls::kControl) {
    AppendKeyboardInput(ui::VKEY_CONTROL, key_up, input);
  }
  if (accelerator_state & ui_controls::kAlt) {
    AppendKeyboardInput(ui::VKEY_LMENU, key_up, input);
  }
  if (accelerator_state & ui_controls::kShift) {
    AppendKeyboardInput(ui::VKEY_SHIFT, key_up, input);
  }
}

// Helper to find the deepest descendant window at a specified screen point.
// We loop using ::ChildWindowFromPointEx because the standard Windows API
// ::ChildWindowFromPointEx is non-recursive (it only searches immediate first-
// level children of the parent window).
// We cannot use ::WindowFromPoint here because when Chrome is occluded by an
// external process window, ::WindowFromPoint returns the occluding window.
HWND FindDeepestChildAtPoint(HWND parent, const POINT& screen_pt) {
  HWND current = parent;
  while (current) {
    POINT client_pt = screen_pt;
    ::ScreenToClient(current, &client_pt);
    HWND child = ::ChildWindowFromPointEx(current, client_pt,
                                          CWP_SKIPINVISIBLE | CWP_SKIPDISABLED);
    if (!child || child == current) {
      break;
    }
    current = child;
  }
  return current;
}

HWND FindChromeWindowAtPoint(const POINT& pt) {
  const DWORD chrome_pid = ::GetCurrentProcessId();
  HWND hwnd = ::GetTopWindow(nullptr);
  while (hwnd) {
    DWORD pid;
    ::GetWindowThreadProcessId(hwnd, &pid);
    if (pid == chrome_pid && ::IsWindowVisible(hwnd)) {
      RECT rect;
      ::GetWindowRect(hwnd, &rect);
      if (::PtInRect(&rect, pt)) {
        return FindDeepestChildAtPoint(hwnd, pt);
      }
    }
    hwnd = ::GetWindow(hwnd, GW_HWNDNEXT);
  }
  return NULL;
}

// Posts a mouse event message directly to a Chrome window and monitors
// its delivery.
void SendMouseEventByPostMessage(HWND chrome_hwnd,
                                 HWND hwnd_under_mouse,
                                 DWORD pid,
                                 const gfx::Point& screen_point,
                                 WPARAM message_type,
                                 base::OnceClosure task) {
  wchar_t class_name[256];
  ::GetClassName(hwnd_under_mouse, class_name, std::size(class_name));

  wchar_t title[256];
  ::GetWindowText(hwnd_under_mouse, title, std::size(title));

  RECT rect;
  ::GetWindowRect(hwnd_under_mouse, &rect);

  VLOG(1) << "Occluding window detected details:"
          << "\n  HWND: " << hwnd_under_mouse << "\n  Class: " << class_name
          << "\n  Title: " << title << "\n  Bounds: (" << rect.left << ", "
          << rect.top << ") - (" << rect.right << ", " << rect.bottom << ")";

  // Update the cursor for WM_MOUSEMOVE. This is needed so that GetCursorPos()
  // returns the correct cursor position.
  if (message_type == WM_MOUSEMOVE) {
    ui::SendMouseEvent(screen_point,
                       MOUSEEVENTF_MOVE | MOUSEEVENTF_VIRTUALDESK);
  }

  POINT client_pt = {screen_point.x(), screen_point.y()};
  ::ScreenToClient(chrome_hwnd, &client_pt);
  ::PostMessage(chrome_hwnd, message_type, 0,
                MAKELPARAM(client_pt.x, client_pt.y));

  // Subclass the target window. Call the completion callback when the subclass
  // proc receives the message.
  if (task) {
    WindowMessageObserver::Create(std::move(task), chrome_hwnd, message_type);
  }
}

}  // namespace

namespace ui_controls {
namespace internal {

bool SendKeyPressReleaseImpl(HWND window,
                             ui::KeyboardCode key,
                             int accelerator_state,
                             KeyEventType wait_for,
                             base::OnceClosure task) {
  // SendInput only works as we expect it if one of our windows is the
  // foreground window already.
  HWND target_window = (::GetActiveWindow() &&
                        ::GetWindow(::GetActiveWindow(), GW_OWNER) == window) ?
                       ::GetActiveWindow() :
                       window;
  if (window && ::GetForegroundWindow() != target_window)
    return false;

  // If a pop-up menu is open, it won't receive events sent using SendInput.
  // Check for a pop-up menu using its window class (#32768) and if one
  // exists, send the key event directly there.
  HWND popup_menu = ::FindWindow(L"#32768", 0);
  if (popup_menu != NULL && popup_menu == ::GetTopWindow(NULL)) {
    WPARAM w_param = ui::WindowsKeyCodeForKeyboardCode(key);
    LPARAM l_param = 0;
    ::SendMessage(popup_menu, WM_KEYDOWN, w_param, l_param);
    ::SendMessage(popup_menu, WM_KEYUP, w_param, l_param);

    if (task)
      InputDispatcher::CreateForKeyEvent(std::move(task), wait_for, 1);
    return true;
  }

  std::vector<INPUT> input;
  AppendAcceleratorInputs(accelerator_state, false, &input);
  AppendKeyboardInput(key, false, &input);

  AppendKeyboardInput(key, true, &input);
  AppendAcceleratorInputs(accelerator_state, true, &input);

  if (input.size() > std::numeric_limits<UINT>::max())
    return false;

  if (::SendInput(static_cast<UINT>(input.size()), input.data(),
                  sizeof(INPUT)) != input.size()) {
    return false;
  }

  if (task)
    InputDispatcher::CreateForKeyEvent(std::move(task), wait_for,
                                       input.size() / 2);
  return true;
}

bool SendMouseMoveImpl(int screen_x, int screen_y, base::OnceClosure task) {
  gfx::Point screen_point =
      display::win::GetScreenWin()->DIPToScreenPoint({screen_x, screen_y});

  // Check if the mouse is already there.
  POINT current_pos;
  ::GetCursorPos(&current_pos);
  if (screen_point.x() == current_pos.x && screen_point.y() == current_pos.y) {
    // Windows does not dispatch a WM_MOUSEMOVE message if the cursor is already
    // at the target coordinates. To force Windows to dispatch the event, offset
    // the target x-coordinate by 1 DIP.
    LOG(WARNING) << "ui_controls: Cursor is already at the target position. "
                    "Offsetting move target by 1 DIP for x to force dispatch.";
    screen_point = display::win::GetScreenWin()->DIPToScreenPoint(
        {screen_x + 1, screen_y});
  }

  POINT pt = {screen_point.x(), screen_point.y()};
  HWND hwnd_under_mouse = ::WindowFromPoint(pt);
  DWORD pid = 0;
  ::GetWindowThreadProcessId(hwnd_under_mouse, &pid);
  const bool other_process_window_under_mouse =
      pid != ::GetCurrentProcessId() && hwnd_under_mouse != NULL;

  // The mouse is over a window owned by another process. On bots we see
  // HWNDs owned by text_input_host.exe or explorer.exe occluding Chrome's
  // window. WM_MOUSEMOVE will be sent to them, not Chrome. They are not also
  // responsive to SetWindowPos or SendInput calls from Chrome.
  // Subtle: only post a WM_MOUSEMOVE if occluded by another process. If we
  // always post a WM_MOUSEMOVE, Chrome will receive 2 WM_MOUSEMOVEs, one
  // from SendInput (required for cursor update) and the other from PostMessage.
  if (other_process_window_under_mouse) {
    HWND chrome_hwnd = FindChromeWindowAtPoint(pt);
    if (chrome_hwnd) {
      SendMouseEventByPostMessage(chrome_hwnd, hwnd_under_mouse, pid,
                                  screen_point, WM_MOUSEMOVE, std::move(task));
      return true;
    }
  }

  if (!ui::SendMouseEvent(screen_point,
                          MOUSEEVENTF_MOVE | MOUSEEVENTF_VIRTUALDESK)) {
    return false;
  }

  if (task)
    InputDispatcher::CreateForMouseMove(std::move(task),
                                        {screen_point.x(), screen_point.y()});
  return true;
}

bool SendMouseEventsImpl(MouseButton type,
                         int button_state,
                         base::OnceClosure task,
                         int accelerator_state) {
  DWORD down_flags = 0;
  DWORD up_flags = 0;
  UINT last_event;

  switch (type) {
    case LEFT:
      down_flags |= MOUSEEVENTF_LEFTDOWN;
      up_flags |= MOUSEEVENTF_LEFTUP;
      last_event = (button_state & UP) ? WM_LBUTTONUP : WM_LBUTTONDOWN;
      break;

    case MIDDLE:
      down_flags |= MOUSEEVENTF_MIDDLEDOWN;
      up_flags |= MOUSEEVENTF_MIDDLEUP;
      last_event = (button_state & UP) ? WM_MBUTTONUP : WM_MBUTTONDOWN;
      break;

    case RIGHT:
      down_flags |= MOUSEEVENTF_RIGHTDOWN;
      up_flags |= MOUSEEVENTF_RIGHTUP;
      last_event = (button_state & UP) ? WM_RBUTTONUP : WM_RBUTTONDOWN;
      break;

    default:
      NOTREACHED();
  }

  std::vector<INPUT> input;
  if (button_state & DOWN) {
    AppendAcceleratorInputs(accelerator_state, false, &input);
    AppendMouseInput(down_flags, &input);
  }

  if (button_state & UP) {
    AppendMouseInput(up_flags, &input);
    AppendAcceleratorInputs(accelerator_state, true, &input);
  }

  if (input.size() > std::numeric_limits<UINT>::max())
    return false;

  if (::SendInput(static_cast<UINT>(input.size()), input.data(),
                  sizeof(INPUT)) != input.size()) {
    return false;
  }

  if (task)
    InputDispatcher::CreateForMouseEvent(std::move(task), last_event);
  return true;
}

bool SendTouchEventsImpl(int action, int num, int x, int y) {
  const int kTouchesLengthCap = 16;
  DCHECK_LE(num, kTouchesLengthCap);

  using InitializeTouchInjectionFn = BOOL(WINAPI*)(UINT32, DWORD);
  static const auto initialize_touch_injection =
      reinterpret_cast<InitializeTouchInjectionFn>(
          base::win::GetUser32FunctionPointer("InitializeTouchInjection"));
  if (!initialize_touch_injection ||
      !initialize_touch_injection(num, TOUCH_FEEDBACK_INDIRECT)) {
    return false;
  }

  using InjectTouchInputFn = BOOL(WINAPI*)(UINT32, POINTER_TOUCH_INFO*);
  static const auto inject_touch_input = reinterpret_cast<InjectTouchInputFn>(
      base::win::GetUser32FunctionPointer("InjectTouchInput"));
  if (!inject_touch_input)
    return false;

  POINTER_TOUCH_INFO pointer_touch_info[kTouchesLengthCap];
  for (int i = 0; i < num; i++) {
    POINTER_TOUCH_INFO& contact = UNSAFE_TODO(pointer_touch_info[i]);
    UNSAFE_TODO(memset(&contact, 0, sizeof(POINTER_TOUCH_INFO)));
    contact.pointerInfo.pointerType = PT_TOUCH;
    contact.pointerInfo.pointerId = i;
    contact.pointerInfo.ptPixelLocation.y = y;
    contact.pointerInfo.ptPixelLocation.x = x + 10 * i;

    contact.touchFlags = TOUCH_FLAG_NONE;
    contact.touchMask =
        TOUCH_MASK_CONTACTAREA | TOUCH_MASK_ORIENTATION | TOUCH_MASK_PRESSURE;
    contact.orientation = 90;
    contact.pressure = 32000;

    // defining contact area
    contact.rcContact.top = contact.pointerInfo.ptPixelLocation.y - 2;
    contact.rcContact.bottom = contact.pointerInfo.ptPixelLocation.y + 2;
    contact.rcContact.left = contact.pointerInfo.ptPixelLocation.x - 2;
    contact.rcContact.right = contact.pointerInfo.ptPixelLocation.x + 2;

    contact.pointerInfo.pointerFlags =
        POINTER_FLAG_DOWN | POINTER_FLAG_INRANGE | POINTER_FLAG_INCONTACT;
  }
  // Injecting the touch down on screen
  if (!inject_touch_input(num, pointer_touch_info))
    return false;

  // Injecting the touch move on screen
  if (action & kTouchMove) {
    for (int i = 0; i < num; i++) {
      POINTER_TOUCH_INFO& contact = UNSAFE_TODO(pointer_touch_info[i]);
      contact.pointerInfo.ptPixelLocation.y = y + 10;
      contact.pointerInfo.ptPixelLocation.x = x + 10 * i + 30;
      contact.pointerInfo.pointerFlags =
          POINTER_FLAG_UPDATE | POINTER_FLAG_INRANGE | POINTER_FLAG_INCONTACT;
    }
    if (!inject_touch_input(num, pointer_touch_info))
      return false;
  }

  // Injecting the touch up on screen
  if (action & kTouchRelease) {
    for (int i = 0; i < num; i++) {
      POINTER_TOUCH_INFO& contact = UNSAFE_TODO(pointer_touch_info[i]);
      contact.pointerInfo.ptPixelLocation.y = y + 10;
      contact.pointerInfo.ptPixelLocation.x = x + 10 * i + 30;
      contact.pointerInfo.pointerFlags = POINTER_FLAG_UP | POINTER_FLAG_INRANGE;
    }
    if (!inject_touch_input(num, pointer_touch_info))
      return false;
  }

  return true;
}

}  // namespace internal
}  // namespace ui_controls
