blob: f25f0007be072572473a85fabf0305b90bbe363b [file] [log] [blame]
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "SendMessageActionSimulator.h"
#include <assert.h>
#include <math.h>
#include "errorcodes.h"
#include "logging.h"
#include "../DocumentHost.h"
#include "../HookProcessor.h"
#include "../WindowUtilities.h"
namespace webdriver {
SendMessageActionSimulator::SendMessageActionSimulator() {
this->keyboard_state_buffer_.resize(256);
::ZeroMemory(&this->keyboard_state_buffer_[0],
this->keyboard_state_buffer_.size());
}
SendMessageActionSimulator::~SendMessageActionSimulator() {
}
int SendMessageActionSimulator::SimulateActions(BrowserHandle browser_wrapper,
std::vector<INPUT> inputs,
InputState* input_state) {
LOG(TRACE) << "Entering InputManager::PerformInputWithSendMessage";
HWND window_handle = browser_wrapper->GetContentWindowHandle();
HookProcessor message_processor;
message_processor.Initialize("GetMessageProc", WH_GETMESSAGE);
if (!message_processor.CanSetWindowsHook(window_handle)) {
LOG(WARN) << "Keystrokes may be slow! There is a mismatch in the "
<< "bitness between the driver and browser. In particular, "
<< "be sure you are not attempting to use a 64-bit "
<< "IEDriverServer.exe against IE 10 or 11, even on 64-bit "
<< "Windows.";
}
DWORD browser_thread_id = ::GetWindowThreadProcessId(window_handle, NULL);
DWORD current_thread_id = ::GetCurrentThreadId();
BOOL attached = ::AttachThreadInput(current_thread_id, browser_thread_id, TRUE);
HKL layout = GetKeyboardLayout(browser_thread_id);
int double_click_time = ::GetDoubleClickTime();
std::vector<INPUT>::const_iterator input_iterator = inputs.begin();
for (; input_iterator != inputs.end(); ++input_iterator) {
INPUT current_input = *input_iterator;
if (current_input.type == INPUT_MOUSE) {
if (current_input.mi.dwFlags & MOUSEEVENTF_MOVE) {
this->SendMouseMoveMessage(window_handle,
*input_state,
current_input.mi.dx,
current_input.mi.dy);
} else if (current_input.mi.dwFlags & MOUSEEVENTF_LEFTDOWN) {
bool is_double_click = this->IsInputDoubleClick(current_input,
*input_state);
this->SendMouseDownMessage(window_handle,
*input_state,
WD_CLIENT_LEFT_MOUSE_BUTTON,
current_input.mi.dx,
current_input.mi.dy,
is_double_click);
} else if (current_input.mi.dwFlags & MOUSEEVENTF_LEFTUP) {
this->SendMouseUpMessage(window_handle,
*input_state,
WD_CLIENT_LEFT_MOUSE_BUTTON,
current_input.mi.dx,
current_input.mi.dy);
} else if (current_input.mi.dwFlags & MOUSEEVENTF_RIGHTDOWN) {
bool is_double_click = this->IsInputDoubleClick(current_input,
*input_state);
this->SendMouseDownMessage(window_handle,
*input_state,
WD_CLIENT_RIGHT_MOUSE_BUTTON,
current_input.mi.dx,
current_input.mi.dy,
is_double_click);
} else if (current_input.mi.dwFlags & MOUSEEVENTF_RIGHTUP) {
this->SendMouseUpMessage(window_handle,
*input_state,
WD_CLIENT_RIGHT_MOUSE_BUTTON,
current_input.mi.dx,
current_input.mi.dy);
}
} else if (current_input.type == INPUT_KEYBOARD) {
bool unicode = (current_input.ki.dwFlags & KEYEVENTF_UNICODE) != 0;
bool extended = (current_input.ki.dwFlags & KEYEVENTF_EXTENDEDKEY) != 0;
if (current_input.ki.dwFlags & KEYEVENTF_KEYUP) {
this->SendKeyUpMessage(window_handle,
*input_state,
current_input.ki.wVk,
current_input.ki.wScan,
extended,
unicode,
layout,
&this->keyboard_state_buffer_);
} else {
this->SendKeyDownMessage(window_handle,
*input_state,
current_input.ki.wVk,
current_input.ki.wScan,
extended,
unicode,
layout,
&this->keyboard_state_buffer_);
}
} else if (current_input.type == INPUT_HARDWARE) {
::Sleep(current_input.hi.uMsg);
}
this->UpdateInputState(current_input, input_state);
}
attached = ::AttachThreadInput(current_thread_id, browser_thread_id, FALSE);
message_processor.Dispose();
return WD_SUCCESS;
}
bool SendMessageActionSimulator::IsInputDoubleClick(INPUT current_input,
InputState input_state) {
DWORD double_click_time = ::GetDoubleClickTime();
unsigned int time_since_last_click = static_cast<unsigned int>(static_cast<float>(clock() - input_state.last_click_time) / CLOCKS_PER_SEC * 1000);
bool button_pressed = true;
if ((current_input.mi.dwFlags & MOUSEEVENTF_LEFTDOWN) != 0) {
button_pressed = input_state.is_left_button_pressed;
}
if ((current_input.mi.dwFlags & MOUSEEVENTF_RIGHTDOWN) != 0) {
button_pressed = input_state.is_right_button_pressed;
}
if (!button_pressed &&
input_state.mouse_x == current_input.mi.dx &&
input_state.mouse_y == current_input.mi.dy &&
time_since_last_click < double_click_time) {
return true;
}
return false;
}
void SendMessageActionSimulator::SendKeyDownMessage(HWND window_handle,
InputState input_state,
int key_code,
int scan_code,
bool extended,
bool unicode,
HKL layout,
std::vector<BYTE>* keyboard_state) {
LPARAM lparam = 0;
clock_t max_wait = clock() + 250;
if (key_code == VK_SHIFT || key_code == VK_CONTROL || key_code == VK_MENU) {
(*keyboard_state)[key_code] |= 0x80;
lparam = 1 | ::MapVirtualKeyEx(key_code, 0, layout) << 16;
if (!::PostMessage(window_handle, WM_KEYDOWN, key_code, lparam)) {
LOG(WARN) << "Modifier keydown failed: " << ::GetLastError();
}
WindowUtilities::Wait(0);
return;
}
if (unicode) {
wchar_t c = static_cast<wchar_t>(scan_code);
SHORT keyscan = VkKeyScanW(c);
HookProcessor::ResetEventCount();
::PostMessage(window_handle, WM_KEYDOWN, keyscan, lparam);
::PostMessage(window_handle, WM_USER, 1234, 5678);
WindowUtilities::Wait(0);
bool is_processed = HookProcessor::GetEventCount() > 0;
while (!is_processed && clock() < max_wait) {
WindowUtilities::Wait(5);
is_processed = HookProcessor::GetEventCount() > 0;
}
::PostMessage(window_handle, WM_CHAR, c, lparam);
WindowUtilities::Wait(0);
} else {
key_code = LOBYTE(key_code);
(*keyboard_state)[key_code] |= 0x80;
::SetKeyboardState(&((*keyboard_state)[0]));
lparam = 1 | scan_code << 16;
if (extended) {
lparam |= 1 << 24;
}
HookProcessor::ResetEventCount();
if (!::PostMessage(window_handle, WM_KEYDOWN, key_code, lparam)) {
LOG(WARN) << "Key down failed: " << ::GetLastError();
}
::PostMessage(window_handle, WM_USER, 1234, 5678);
// Listen out for the keypress event which IE synthesizes when IE
// processes the keydown message. Use a time out, just in case we
// have not got the logic right :)
bool is_processed = HookProcessor::GetEventCount() > 0;
max_wait = clock() + 5000;
while (!is_processed && clock() < max_wait) {
WindowUtilities::Wait(5);
is_processed = HookProcessor::GetEventCount() > 0;
if (clock() >= max_wait) {
LOG(WARN) << "Timeout awaiting keypress: " << key_code;
break;
}
}
}
}
void SendMessageActionSimulator::SendKeyUpMessage(HWND window_handle,
InputState input_state,
int key_code,
int scan_code,
bool extended,
bool unicode,
HKL layout,
std::vector<BYTE>* keyboard_state) {
LPARAM lparam = 0;
if (key_code == VK_SHIFT || key_code == VK_CONTROL || key_code == VK_MENU) {
(*keyboard_state)[key_code] &= ~0x80;
lparam = 1 | ::MapVirtualKeyEx(key_code, 0, layout) << 16;
lparam |= 0x3 << 30;
if (!::PostMessage(window_handle, WM_KEYUP, key_code, lparam)) {
LOG(WARN) << "Modifier keyup failed: " << ::GetLastError();
}
WindowUtilities::Wait(0);
return;
}
if (unicode) {
wchar_t c = static_cast<wchar_t>(scan_code);
SHORT keyscan = VkKeyScanW(c);
::PostMessage(window_handle, WM_KEYUP, keyscan, lparam);
} else {
key_code = LOBYTE(key_code);
(*keyboard_state)[key_code] &= ~0x80;
::SetKeyboardState(&((*keyboard_state)[0]));
lparam = 1 | scan_code << 16;
if (extended) {
lparam |= 1 << 24;
}
lparam |= 0x3 << 30;
if (!::PostMessage(window_handle, WM_KEYUP, key_code, lparam)) {
LOG(WARN) << "Key up failed: " << ::GetLastError();
}
WindowUtilities::Wait(0);
}
}
void SendMessageActionSimulator::SendMouseMoveMessage(HWND window_handle,
InputState input_state,
int x,
int y) {
LRESULT message_timeout = 0;
DWORD_PTR send_message_result = 0;
WPARAM button_value = 0;
if (input_state.is_left_button_pressed) {
button_value |= MK_LBUTTON;
}
if (input_state.is_right_button_pressed) {
button_value |= MK_RBUTTON;
}
if (input_state.is_shift_pressed) {
button_value |= MK_SHIFT;
}
if (input_state.is_control_pressed) {
button_value |= MK_CONTROL;
}
LPARAM coordinates = MAKELPARAM(x, y);
message_timeout = ::SendMessageTimeout(window_handle,
WM_MOUSEMOVE,
button_value,
coordinates,
SMTO_NORMAL,
100,
&send_message_result);
if (message_timeout == 0) {
LOGERR(WARN) << "MouseMove: SendMessageTimeout failed";
}
}
void SendMessageActionSimulator::SendMouseDownMessage(HWND window_handle,
InputState input_state,
int button,
int x,
int y,
bool is_double_click) {
UINT msg = WM_LBUTTONDOWN;
WPARAM button_value = MK_LBUTTON;
if (is_double_click) {
msg = WM_LBUTTONDBLCLK;
}
if (button == WD_CLIENT_RIGHT_MOUSE_BUTTON) {
msg = WM_RBUTTONDOWN;
button_value = MK_RBUTTON;
if (is_double_click) {
msg = WM_RBUTTONDBLCLK;
}
}
int modifier = 0;
if (input_state.is_shift_pressed) {
modifier |= MK_SHIFT;
}
if (input_state.is_control_pressed) {
modifier |= MK_CONTROL;
}
button_value |= modifier;
LPARAM coordinates = MAKELPARAM(x, y);
// Must use PostMessage for mouse down because message gets lost with
// SendMessage and variants. Use a SendMessage with WM_USER to ensure
// the posted message has been processed.
::PostMessage(window_handle, msg, button_value, coordinates);
::SendMessage(window_handle, WM_USER, 0, 0);
// This 5 millisecond sleep is important for the click element scenario,
// as it allows the element to register and respond to the focus event.
::Sleep(5);
}
void SendMessageActionSimulator::SendMouseUpMessage(HWND window_handle,
InputState input_state,
int button,
int x,
int y) {
UINT msg = WM_LBUTTONUP;
WPARAM button_value = MK_LBUTTON;
if (button == WD_CLIENT_RIGHT_MOUSE_BUTTON) {
msg = WM_RBUTTONUP;
button_value = MK_RBUTTON;
}
int modifier = 0;
if (input_state.is_shift_pressed) {
modifier |= MK_SHIFT;
}
if (input_state.is_control_pressed) {
modifier |= MK_CONTROL;
}
button_value |= modifier;
LPARAM coordinates = MAKELPARAM(x, y);
// To properly mimic manual mouse movement, we need a move before the up.
::SendMessage(window_handle, WM_MOUSEMOVE, modifier, coordinates);
// Must use PostMessage for mouse up because message gets lost with
// SendMessage and variants. Use a SendMessage with WM_USER to ensure
// the posted message has been processed.
::PostMessage(window_handle, msg, button_value, coordinates);
::SendMessage(window_handle, WM_USER, 0, 0);
}
} // namespace webdriver
#ifdef __cplusplus
extern "C" {
#endif
LRESULT CALLBACK GetMessageProc(int nCode, WPARAM wParam, LPARAM lParam) {
if ((nCode == HC_ACTION) && (wParam == PM_REMOVE)) {
MSG* msg = reinterpret_cast<MSG*>(lParam);
if (msg->message == WM_USER && msg->wParam == 1234 && msg->lParam == 5678) {
int message_count = 50;
webdriver::HookProcessor::IncrementEventCount(message_count);
}
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
#ifdef __cplusplus
}
#endif