Path: blob/trunk/cpp/iedriver/ActionSimulators/SendMessageActionSimulator.cpp
2868 views
// Licensed to the Software Freedom Conservancy (SFC) under one1// or more contributor license agreements. See the NOTICE file2// distributed with this work for additional information3// regarding copyright ownership. The SFC licenses this file4// to you under the Apache License, Version 2.0 (the "License");5// you may not use this file except in compliance with the License.6// You may obtain a copy of the License at7//8// http://www.apache.org/licenses/LICENSE-2.09//10// Unless required by applicable law or agreed to in writing, software11// distributed under the License is distributed on an "AS IS" BASIS,12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.13// See the License for the specific language governing permissions and14// limitations under the License.1516#include "SendMessageActionSimulator.h"1718#include <assert.h>19#include <math.h>2021#include "errorcodes.h"22#include "logging.h"2324#include "../DocumentHost.h"25#include "../HookProcessor.h"26#include "../WindowUtilities.h"2728namespace webdriver {2930SendMessageActionSimulator::SendMessageActionSimulator() {31this->keyboard_state_buffer_.resize(256);32::ZeroMemory(&this->keyboard_state_buffer_[0],33this->keyboard_state_buffer_.size());34}3536SendMessageActionSimulator::~SendMessageActionSimulator() {37}3839int SendMessageActionSimulator::SimulateActions(BrowserHandle browser_wrapper,40std::vector<INPUT> inputs,41InputState* input_state) {42LOG(TRACE) << "Entering InputManager::PerformInputWithSendMessage";43HWND window_handle = browser_wrapper->GetContentWindowHandle();4445HookProcessor message_processor;46message_processor.Initialize("GetMessageProc", WH_GETMESSAGE);47if (!message_processor.CanSetWindowsHook(window_handle)) {48LOG(WARN) << "Keystrokes may be slow! There is a mismatch in the "49<< "bitness between the driver and browser. In particular, "50<< "be sure you are not attempting to use a 64-bit "51<< "IEDriverServer.exe against IE 10 or 11, even on 64-bit "52<< "Windows.";53}5455DWORD browser_thread_id = ::GetWindowThreadProcessId(window_handle, NULL);56DWORD current_thread_id = ::GetCurrentThreadId();57BOOL attached = ::AttachThreadInput(current_thread_id, browser_thread_id, TRUE);5859HKL layout = GetKeyboardLayout(browser_thread_id);6061int double_click_time = ::GetDoubleClickTime();6263std::vector<INPUT>::const_iterator input_iterator = inputs.begin();64for (; input_iterator != inputs.end(); ++input_iterator) {65INPUT current_input = *input_iterator;66if (current_input.type == INPUT_MOUSE) {67if (current_input.mi.dwFlags & MOUSEEVENTF_MOVE) {68this->SendMouseMoveMessage(window_handle,69*input_state,70current_input.mi.dx,71current_input.mi.dy);72} else if (current_input.mi.dwFlags & MOUSEEVENTF_LEFTDOWN) {73bool is_double_click = this->IsInputDoubleClick(current_input,74*input_state);75this->SendMouseDownMessage(window_handle,76*input_state,77WD_CLIENT_LEFT_MOUSE_BUTTON,78current_input.mi.dx,79current_input.mi.dy,80is_double_click);81} else if (current_input.mi.dwFlags & MOUSEEVENTF_LEFTUP) {82this->SendMouseUpMessage(window_handle,83*input_state,84WD_CLIENT_LEFT_MOUSE_BUTTON,85current_input.mi.dx,86current_input.mi.dy);87} else if (current_input.mi.dwFlags & MOUSEEVENTF_RIGHTDOWN) {88bool is_double_click = this->IsInputDoubleClick(current_input,89*input_state);90this->SendMouseDownMessage(window_handle,91*input_state,92WD_CLIENT_RIGHT_MOUSE_BUTTON,93current_input.mi.dx,94current_input.mi.dy,95is_double_click);96} else if (current_input.mi.dwFlags & MOUSEEVENTF_RIGHTUP) {97this->SendMouseUpMessage(window_handle,98*input_state,99WD_CLIENT_RIGHT_MOUSE_BUTTON,100current_input.mi.dx,101current_input.mi.dy);102}103} else if (current_input.type == INPUT_KEYBOARD) {104bool unicode = (current_input.ki.dwFlags & KEYEVENTF_UNICODE) != 0;105bool extended = (current_input.ki.dwFlags & KEYEVENTF_EXTENDEDKEY) != 0;106if (current_input.ki.dwFlags & KEYEVENTF_KEYUP) {107this->SendKeyUpMessage(window_handle,108*input_state,109current_input.ki.wVk,110current_input.ki.wScan,111extended,112unicode,113layout,114&this->keyboard_state_buffer_);115} else {116this->SendKeyDownMessage(window_handle,117*input_state,118current_input.ki.wVk,119current_input.ki.wScan,120extended,121unicode,122layout,123&this->keyboard_state_buffer_);124}125} else if (current_input.type == INPUT_HARDWARE) {126::Sleep(current_input.hi.uMsg);127}128this->UpdateInputState(current_input, input_state);129}130attached = ::AttachThreadInput(current_thread_id, browser_thread_id, FALSE);131message_processor.Dispose();132return WD_SUCCESS;133}134135bool SendMessageActionSimulator::IsInputDoubleClick(INPUT current_input,136InputState input_state) {137DWORD double_click_time = ::GetDoubleClickTime();138unsigned int time_since_last_click = static_cast<unsigned int>(static_cast<float>(clock() - input_state.last_click_time) / CLOCKS_PER_SEC * 1000);139bool button_pressed = true;140if ((current_input.mi.dwFlags & MOUSEEVENTF_LEFTDOWN) != 0) {141button_pressed = input_state.is_left_button_pressed;142}143144if ((current_input.mi.dwFlags & MOUSEEVENTF_RIGHTDOWN) != 0) {145button_pressed = input_state.is_right_button_pressed;146}147148if (!button_pressed &&149input_state.mouse_x == current_input.mi.dx &&150input_state.mouse_y == current_input.mi.dy &&151time_since_last_click < double_click_time) {152return true;153}154return false;155}156157void SendMessageActionSimulator::SendKeyDownMessage(HWND window_handle,158InputState input_state,159int key_code,160int scan_code,161bool extended,162bool unicode,163HKL layout,164std::vector<BYTE>* keyboard_state) {165LPARAM lparam = 0;166clock_t max_wait = clock() + 250;167168if (key_code == VK_SHIFT || key_code == VK_CONTROL || key_code == VK_MENU) {169(*keyboard_state)[key_code] |= 0x80;170171lparam = 1 | ::MapVirtualKeyEx(key_code, 0, layout) << 16;172if (!::PostMessage(window_handle, WM_KEYDOWN, key_code, lparam)) {173LOG(WARN) << "Modifier keydown failed: " << ::GetLastError();174}175176WindowUtilities::Wait(0);177return;178}179180if (unicode) {181wchar_t c = static_cast<wchar_t>(scan_code);182SHORT keyscan = VkKeyScanW(c);183HookProcessor::ResetEventCount();184::PostMessage(window_handle, WM_KEYDOWN, keyscan, lparam);185::PostMessage(window_handle, WM_USER, 1234, 5678);186WindowUtilities::Wait(0);187bool is_processed = HookProcessor::GetEventCount() > 0;188while (!is_processed && clock() < max_wait) {189WindowUtilities::Wait(5);190is_processed = HookProcessor::GetEventCount() > 0;191}192::PostMessage(window_handle, WM_CHAR, c, lparam);193WindowUtilities::Wait(0);194} else {195key_code = LOBYTE(key_code);196(*keyboard_state)[key_code] |= 0x80;197::SetKeyboardState(&((*keyboard_state)[0]));198199lparam = 1 | scan_code << 16;200if (extended) {201lparam |= 1 << 24;202}203204HookProcessor::ResetEventCount();205if (!::PostMessage(window_handle, WM_KEYDOWN, key_code, lparam)) {206LOG(WARN) << "Key down failed: " << ::GetLastError();207}208209::PostMessage(window_handle, WM_USER, 1234, 5678);210211// Listen out for the keypress event which IE synthesizes when IE212// processes the keydown message. Use a time out, just in case we213// have not got the logic right :)214215bool is_processed = HookProcessor::GetEventCount() > 0;216max_wait = clock() + 5000;217while (!is_processed && clock() < max_wait) {218WindowUtilities::Wait(5);219is_processed = HookProcessor::GetEventCount() > 0;220if (clock() >= max_wait) {221LOG(WARN) << "Timeout awaiting keypress: " << key_code;222break;223}224}225}226}227228void SendMessageActionSimulator::SendKeyUpMessage(HWND window_handle,229InputState input_state,230int key_code,231int scan_code,232bool extended,233bool unicode,234HKL layout,235std::vector<BYTE>* keyboard_state) {236LPARAM lparam = 0;237238if (key_code == VK_SHIFT || key_code == VK_CONTROL || key_code == VK_MENU) {239(*keyboard_state)[key_code] &= ~0x80;240241lparam = 1 | ::MapVirtualKeyEx(key_code, 0, layout) << 16;242lparam |= 0x3 << 30;243if (!::PostMessage(window_handle, WM_KEYUP, key_code, lparam)) {244LOG(WARN) << "Modifier keyup failed: " << ::GetLastError();245}246247WindowUtilities::Wait(0);248return;249}250251if (unicode) {252wchar_t c = static_cast<wchar_t>(scan_code);253SHORT keyscan = VkKeyScanW(c);254::PostMessage(window_handle, WM_KEYUP, keyscan, lparam);255} else {256key_code = LOBYTE(key_code);257(*keyboard_state)[key_code] &= ~0x80;258::SetKeyboardState(&((*keyboard_state)[0]));259260lparam = 1 | scan_code << 16;261if (extended) {262lparam |= 1 << 24;263}264265lparam |= 0x3 << 30;266if (!::PostMessage(window_handle, WM_KEYUP, key_code, lparam)) {267LOG(WARN) << "Key up failed: " << ::GetLastError();268}269270WindowUtilities::Wait(0);271}272}273274void SendMessageActionSimulator::SendMouseMoveMessage(HWND window_handle,275InputState input_state,276int x,277int y) {278LRESULT message_timeout = 0;279DWORD_PTR send_message_result = 0;280WPARAM button_value = 0;281if (input_state.is_left_button_pressed) {282button_value |= MK_LBUTTON;283}284if (input_state.is_right_button_pressed) {285button_value |= MK_RBUTTON;286}287if (input_state.is_shift_pressed) {288button_value |= MK_SHIFT;289}290if (input_state.is_control_pressed) {291button_value |= MK_CONTROL;292}293LPARAM coordinates = MAKELPARAM(x, y);294message_timeout = ::SendMessageTimeout(window_handle,295WM_MOUSEMOVE,296button_value,297coordinates,298SMTO_NORMAL,299100,300&send_message_result);301if (message_timeout == 0) {302LOGERR(WARN) << "MouseMove: SendMessageTimeout failed";303}304}305306void SendMessageActionSimulator::SendMouseDownMessage(HWND window_handle,307InputState input_state,308int button,309int x,310int y,311bool is_double_click) {312UINT msg = WM_LBUTTONDOWN;313WPARAM button_value = MK_LBUTTON;314if (is_double_click) {315msg = WM_LBUTTONDBLCLK;316}317if (button == WD_CLIENT_RIGHT_MOUSE_BUTTON) {318msg = WM_RBUTTONDOWN;319button_value = MK_RBUTTON;320if (is_double_click) {321msg = WM_RBUTTONDBLCLK;322}323}324int modifier = 0;325if (input_state.is_shift_pressed) {326modifier |= MK_SHIFT;327}328if (input_state.is_control_pressed) {329modifier |= MK_CONTROL;330}331button_value |= modifier;332LPARAM coordinates = MAKELPARAM(x, y);333// Must use PostMessage for mouse down because message gets lost with334// SendMessage and variants. Use a SendMessage with WM_USER to ensure335// the posted message has been processed.336::PostMessage(window_handle, msg, button_value, coordinates);337::SendMessage(window_handle, WM_USER, 0, 0);338339// This 5 millisecond sleep is important for the click element scenario,340// as it allows the element to register and respond to the focus event.341::Sleep(5);342}343344345void SendMessageActionSimulator::SendMouseUpMessage(HWND window_handle,346InputState input_state,347int button,348int x,349int y) {350UINT msg = WM_LBUTTONUP;351WPARAM button_value = MK_LBUTTON;352if (button == WD_CLIENT_RIGHT_MOUSE_BUTTON) {353msg = WM_RBUTTONUP;354button_value = MK_RBUTTON;355}356int modifier = 0;357if (input_state.is_shift_pressed) {358modifier |= MK_SHIFT;359}360if (input_state.is_control_pressed) {361modifier |= MK_CONTROL;362}363button_value |= modifier;364LPARAM coordinates = MAKELPARAM(x, y);365// To properly mimic manual mouse movement, we need a move before the up.366::SendMessage(window_handle, WM_MOUSEMOVE, modifier, coordinates);367// Must use PostMessage for mouse up because message gets lost with368// SendMessage and variants. Use a SendMessage with WM_USER to ensure369// the posted message has been processed.370::PostMessage(window_handle, msg, button_value, coordinates);371::SendMessage(window_handle, WM_USER, 0, 0);372}373374} // namespace webdriver375376#ifdef __cplusplus377extern "C" {378#endif379380LRESULT CALLBACK GetMessageProc(int nCode, WPARAM wParam, LPARAM lParam) {381if ((nCode == HC_ACTION) && (wParam == PM_REMOVE)) {382MSG* msg = reinterpret_cast<MSG*>(lParam);383if (msg->message == WM_USER && msg->wParam == 1234 && msg->lParam == 5678) {384int message_count = 50;385webdriver::HookProcessor::IncrementEventCount(message_count);386}387}388389return CallNextHookEx(NULL, nCode, wParam, lParam);390}391392#ifdef __cplusplus393}394#endif395396397