| // 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 <algorithm> | 
 | #include <cmath> | 
 | #include <utility> | 
 |  | 
 | #include "base/functional/bind.h" | 
 | #include "base/functional/callback.h" | 
 | #include "base/location.h" | 
 | #include "base/logging.h" | 
 | #include "base/run_loop.h" | 
 | #include "base/task/single_thread_task_runner.h" | 
 | #include "base/test/test_timeouts.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 { | 
 |  | 
 | // 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); | 
 |  | 
 |   // Special case of CreateForMessage() for WM_KEYUP (can await multiple events | 
 |   // when modifiers are involved). | 
 |   static void CreateForKeyUp(base::OnceClosure callback, | 
 |                              int num_keyups_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_KEYUP | 
 |   InputDispatcher(base::OnceClosure callback, | 
 |                   WPARAM message_waiting_for, | 
 |                   UINT system_queue_flag, | 
 |                   int num_keyups_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 next_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 -- unused for WM_KEYUP | 
 |   // messages. | 
 |   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 WM_KEYUP messages to receive before dispatching |callback_|. | 
 |   // Only relevant when |message_waiting_for_| is WM_KEYUP. | 
 |   int num_keyups_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::next_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::CreateForKeyUp(base::OnceClosure callback, | 
 |                                      int num_keyups_awaited) { | 
 |   // Owns self. | 
 |   new InputDispatcher(std::move(callback), WM_KEYUP, QS_KEY, | 
 |                       num_keyups_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_keyups_awaited) | 
 |     : callback_(std::move(callback)), | 
 |       message_waiting_for_(message_waiting_for), | 
 |       system_queue_flag_(system_queue_flag), | 
 |       num_keyups_awaited_(num_keyups_awaited) { | 
 |   DCHECK_EQ(message_waiting_for_, static_cast<WPARAM>(WM_KEYUP)); | 
 |   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) { | 
 |   DCHECK_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(next_hook_); | 
 |   next_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 (message_waiting_for_ == WM_KEYUP) { | 
 |     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()); | 
 |     } | 
 |   } | 
 |   next_hook_ = | 
 |       SetWindowsHookEx(hook_type, hook_function, nullptr, GetCurrentThreadId()); | 
 |   DPCHECK(next_hook_); | 
 | } | 
 |  | 
 | // static | 
 | LRESULT CALLBACK InputDispatcher::MouseHook(int n_code, | 
 |                                             WPARAM w_param, | 
 |                                             LPARAM l_param) { | 
 |   HHOOK next_hook = next_hook_; | 
 |   if (n_code == HC_ACTION) { | 
 |     DCHECK(current_dispatcher_); | 
 |     current_dispatcher_->DispatchedMessage( | 
 |         static_cast<UINT>(w_param), | 
 |         reinterpret_cast<MOUSEHOOKSTRUCT*>(l_param)); | 
 |   } | 
 |   return CallNextHookEx(next_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) && (HIWORD(l_param) & KF_UP)) { | 
 |     DCHECK(current_dispatcher_); | 
 |     base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( | 
 |         FROM_HERE, | 
 |         base::BindOnce(&InputDispatcher::MatchingMessageProcessed, | 
 |                        current_dispatcher_->weak_factory_.GetWeakPtr(), false)); | 
 |   } | 
 |   return CallNextHookEx(next_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 (message_waiting_for_ == WM_KEYUP && --num_keyups_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(); | 
 | } | 
 |  | 
 | // 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); | 
 |   } | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | namespace ui_controls { | 
 | namespace internal { | 
 |  | 
 | bool SendKeyPressImpl(HWND window, | 
 |                       ui::KeyboardCode key, | 
 |                       int accelerator_state, | 
 |                       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::CreateForKeyUp(std::move(task), 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::CreateForKeyUp(std::move(task), input.size() / 2); | 
 |   return true; | 
 | } | 
 |  | 
 | bool SendMouseMoveImpl(int screen_x, int screen_y, base::OnceClosure task) { | 
 |   gfx::Point screen_point = | 
 |       display::win::ScreenWin::DIPToScreenPoint({screen_x, screen_y}); | 
 |  | 
 |   // Check if the mouse is already there. | 
 |   POINT current_pos; | 
 |   ::GetCursorPos(¤t_pos); | 
 |   if (screen_point.x() == current_pos.x && screen_point.y() == current_pos.y) { | 
 |     if (task) | 
 |       base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( | 
 |           FROM_HERE, std::move(task)); | 
 |     return true; | 
 |   } | 
 |  | 
 |   if (!ui::SendMouseEvent(screen_point, | 
 |                           MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE)) { | 
 |     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 = MOUSEEVENTF_ABSOLUTE; | 
 |   DWORD up_flags = MOUSEEVENTF_ABSOLUTE; | 
 |   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(); | 
 |       return false; | 
 |   } | 
 |  | 
 |   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 = pointer_touch_info[i]; | 
 |     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 = 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 = 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 |