Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/cpp/iedriver/ActionSimulators/SendMessageActionSimulator.cpp
2868 views
1
// Licensed to the Software Freedom Conservancy (SFC) under one
2
// or more contributor license agreements. See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership. The SFC licenses this file
5
// to you under the Apache License, Version 2.0 (the "License");
6
// you may not use this file except in compliance with the License.
7
// You may obtain a copy of the License at
8
//
9
// http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing, software
12
// distributed under the License is distributed on an "AS IS" BASIS,
13
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
// See the License for the specific language governing permissions and
15
// limitations under the License.
16
17
#include "SendMessageActionSimulator.h"
18
19
#include <assert.h>
20
#include <math.h>
21
22
#include "errorcodes.h"
23
#include "logging.h"
24
25
#include "../DocumentHost.h"
26
#include "../HookProcessor.h"
27
#include "../WindowUtilities.h"
28
29
namespace webdriver {
30
31
SendMessageActionSimulator::SendMessageActionSimulator() {
32
this->keyboard_state_buffer_.resize(256);
33
::ZeroMemory(&this->keyboard_state_buffer_[0],
34
this->keyboard_state_buffer_.size());
35
}
36
37
SendMessageActionSimulator::~SendMessageActionSimulator() {
38
}
39
40
int SendMessageActionSimulator::SimulateActions(BrowserHandle browser_wrapper,
41
std::vector<INPUT> inputs,
42
InputState* input_state) {
43
LOG(TRACE) << "Entering InputManager::PerformInputWithSendMessage";
44
HWND window_handle = browser_wrapper->GetContentWindowHandle();
45
46
HookProcessor message_processor;
47
message_processor.Initialize("GetMessageProc", WH_GETMESSAGE);
48
if (!message_processor.CanSetWindowsHook(window_handle)) {
49
LOG(WARN) << "Keystrokes may be slow! There is a mismatch in the "
50
<< "bitness between the driver and browser. In particular, "
51
<< "be sure you are not attempting to use a 64-bit "
52
<< "IEDriverServer.exe against IE 10 or 11, even on 64-bit "
53
<< "Windows.";
54
}
55
56
DWORD browser_thread_id = ::GetWindowThreadProcessId(window_handle, NULL);
57
DWORD current_thread_id = ::GetCurrentThreadId();
58
BOOL attached = ::AttachThreadInput(current_thread_id, browser_thread_id, TRUE);
59
60
HKL layout = GetKeyboardLayout(browser_thread_id);
61
62
int double_click_time = ::GetDoubleClickTime();
63
64
std::vector<INPUT>::const_iterator input_iterator = inputs.begin();
65
for (; input_iterator != inputs.end(); ++input_iterator) {
66
INPUT current_input = *input_iterator;
67
if (current_input.type == INPUT_MOUSE) {
68
if (current_input.mi.dwFlags & MOUSEEVENTF_MOVE) {
69
this->SendMouseMoveMessage(window_handle,
70
*input_state,
71
current_input.mi.dx,
72
current_input.mi.dy);
73
} else if (current_input.mi.dwFlags & MOUSEEVENTF_LEFTDOWN) {
74
bool is_double_click = this->IsInputDoubleClick(current_input,
75
*input_state);
76
this->SendMouseDownMessage(window_handle,
77
*input_state,
78
WD_CLIENT_LEFT_MOUSE_BUTTON,
79
current_input.mi.dx,
80
current_input.mi.dy,
81
is_double_click);
82
} else if (current_input.mi.dwFlags & MOUSEEVENTF_LEFTUP) {
83
this->SendMouseUpMessage(window_handle,
84
*input_state,
85
WD_CLIENT_LEFT_MOUSE_BUTTON,
86
current_input.mi.dx,
87
current_input.mi.dy);
88
} else if (current_input.mi.dwFlags & MOUSEEVENTF_RIGHTDOWN) {
89
bool is_double_click = this->IsInputDoubleClick(current_input,
90
*input_state);
91
this->SendMouseDownMessage(window_handle,
92
*input_state,
93
WD_CLIENT_RIGHT_MOUSE_BUTTON,
94
current_input.mi.dx,
95
current_input.mi.dy,
96
is_double_click);
97
} else if (current_input.mi.dwFlags & MOUSEEVENTF_RIGHTUP) {
98
this->SendMouseUpMessage(window_handle,
99
*input_state,
100
WD_CLIENT_RIGHT_MOUSE_BUTTON,
101
current_input.mi.dx,
102
current_input.mi.dy);
103
}
104
} else if (current_input.type == INPUT_KEYBOARD) {
105
bool unicode = (current_input.ki.dwFlags & KEYEVENTF_UNICODE) != 0;
106
bool extended = (current_input.ki.dwFlags & KEYEVENTF_EXTENDEDKEY) != 0;
107
if (current_input.ki.dwFlags & KEYEVENTF_KEYUP) {
108
this->SendKeyUpMessage(window_handle,
109
*input_state,
110
current_input.ki.wVk,
111
current_input.ki.wScan,
112
extended,
113
unicode,
114
layout,
115
&this->keyboard_state_buffer_);
116
} else {
117
this->SendKeyDownMessage(window_handle,
118
*input_state,
119
current_input.ki.wVk,
120
current_input.ki.wScan,
121
extended,
122
unicode,
123
layout,
124
&this->keyboard_state_buffer_);
125
}
126
} else if (current_input.type == INPUT_HARDWARE) {
127
::Sleep(current_input.hi.uMsg);
128
}
129
this->UpdateInputState(current_input, input_state);
130
}
131
attached = ::AttachThreadInput(current_thread_id, browser_thread_id, FALSE);
132
message_processor.Dispose();
133
return WD_SUCCESS;
134
}
135
136
bool SendMessageActionSimulator::IsInputDoubleClick(INPUT current_input,
137
InputState input_state) {
138
DWORD double_click_time = ::GetDoubleClickTime();
139
unsigned int time_since_last_click = static_cast<unsigned int>(static_cast<float>(clock() - input_state.last_click_time) / CLOCKS_PER_SEC * 1000);
140
bool button_pressed = true;
141
if ((current_input.mi.dwFlags & MOUSEEVENTF_LEFTDOWN) != 0) {
142
button_pressed = input_state.is_left_button_pressed;
143
}
144
145
if ((current_input.mi.dwFlags & MOUSEEVENTF_RIGHTDOWN) != 0) {
146
button_pressed = input_state.is_right_button_pressed;
147
}
148
149
if (!button_pressed &&
150
input_state.mouse_x == current_input.mi.dx &&
151
input_state.mouse_y == current_input.mi.dy &&
152
time_since_last_click < double_click_time) {
153
return true;
154
}
155
return false;
156
}
157
158
void SendMessageActionSimulator::SendKeyDownMessage(HWND window_handle,
159
InputState input_state,
160
int key_code,
161
int scan_code,
162
bool extended,
163
bool unicode,
164
HKL layout,
165
std::vector<BYTE>* keyboard_state) {
166
LPARAM lparam = 0;
167
clock_t max_wait = clock() + 250;
168
169
if (key_code == VK_SHIFT || key_code == VK_CONTROL || key_code == VK_MENU) {
170
(*keyboard_state)[key_code] |= 0x80;
171
172
lparam = 1 | ::MapVirtualKeyEx(key_code, 0, layout) << 16;
173
if (!::PostMessage(window_handle, WM_KEYDOWN, key_code, lparam)) {
174
LOG(WARN) << "Modifier keydown failed: " << ::GetLastError();
175
}
176
177
WindowUtilities::Wait(0);
178
return;
179
}
180
181
if (unicode) {
182
wchar_t c = static_cast<wchar_t>(scan_code);
183
SHORT keyscan = VkKeyScanW(c);
184
HookProcessor::ResetEventCount();
185
::PostMessage(window_handle, WM_KEYDOWN, keyscan, lparam);
186
::PostMessage(window_handle, WM_USER, 1234, 5678);
187
WindowUtilities::Wait(0);
188
bool is_processed = HookProcessor::GetEventCount() > 0;
189
while (!is_processed && clock() < max_wait) {
190
WindowUtilities::Wait(5);
191
is_processed = HookProcessor::GetEventCount() > 0;
192
}
193
::PostMessage(window_handle, WM_CHAR, c, lparam);
194
WindowUtilities::Wait(0);
195
} else {
196
key_code = LOBYTE(key_code);
197
(*keyboard_state)[key_code] |= 0x80;
198
::SetKeyboardState(&((*keyboard_state)[0]));
199
200
lparam = 1 | scan_code << 16;
201
if (extended) {
202
lparam |= 1 << 24;
203
}
204
205
HookProcessor::ResetEventCount();
206
if (!::PostMessage(window_handle, WM_KEYDOWN, key_code, lparam)) {
207
LOG(WARN) << "Key down failed: " << ::GetLastError();
208
}
209
210
::PostMessage(window_handle, WM_USER, 1234, 5678);
211
212
// Listen out for the keypress event which IE synthesizes when IE
213
// processes the keydown message. Use a time out, just in case we
214
// have not got the logic right :)
215
216
bool is_processed = HookProcessor::GetEventCount() > 0;
217
max_wait = clock() + 5000;
218
while (!is_processed && clock() < max_wait) {
219
WindowUtilities::Wait(5);
220
is_processed = HookProcessor::GetEventCount() > 0;
221
if (clock() >= max_wait) {
222
LOG(WARN) << "Timeout awaiting keypress: " << key_code;
223
break;
224
}
225
}
226
}
227
}
228
229
void SendMessageActionSimulator::SendKeyUpMessage(HWND window_handle,
230
InputState input_state,
231
int key_code,
232
int scan_code,
233
bool extended,
234
bool unicode,
235
HKL layout,
236
std::vector<BYTE>* keyboard_state) {
237
LPARAM lparam = 0;
238
239
if (key_code == VK_SHIFT || key_code == VK_CONTROL || key_code == VK_MENU) {
240
(*keyboard_state)[key_code] &= ~0x80;
241
242
lparam = 1 | ::MapVirtualKeyEx(key_code, 0, layout) << 16;
243
lparam |= 0x3 << 30;
244
if (!::PostMessage(window_handle, WM_KEYUP, key_code, lparam)) {
245
LOG(WARN) << "Modifier keyup failed: " << ::GetLastError();
246
}
247
248
WindowUtilities::Wait(0);
249
return;
250
}
251
252
if (unicode) {
253
wchar_t c = static_cast<wchar_t>(scan_code);
254
SHORT keyscan = VkKeyScanW(c);
255
::PostMessage(window_handle, WM_KEYUP, keyscan, lparam);
256
} else {
257
key_code = LOBYTE(key_code);
258
(*keyboard_state)[key_code] &= ~0x80;
259
::SetKeyboardState(&((*keyboard_state)[0]));
260
261
lparam = 1 | scan_code << 16;
262
if (extended) {
263
lparam |= 1 << 24;
264
}
265
266
lparam |= 0x3 << 30;
267
if (!::PostMessage(window_handle, WM_KEYUP, key_code, lparam)) {
268
LOG(WARN) << "Key up failed: " << ::GetLastError();
269
}
270
271
WindowUtilities::Wait(0);
272
}
273
}
274
275
void SendMessageActionSimulator::SendMouseMoveMessage(HWND window_handle,
276
InputState input_state,
277
int x,
278
int y) {
279
LRESULT message_timeout = 0;
280
DWORD_PTR send_message_result = 0;
281
WPARAM button_value = 0;
282
if (input_state.is_left_button_pressed) {
283
button_value |= MK_LBUTTON;
284
}
285
if (input_state.is_right_button_pressed) {
286
button_value |= MK_RBUTTON;
287
}
288
if (input_state.is_shift_pressed) {
289
button_value |= MK_SHIFT;
290
}
291
if (input_state.is_control_pressed) {
292
button_value |= MK_CONTROL;
293
}
294
LPARAM coordinates = MAKELPARAM(x, y);
295
message_timeout = ::SendMessageTimeout(window_handle,
296
WM_MOUSEMOVE,
297
button_value,
298
coordinates,
299
SMTO_NORMAL,
300
100,
301
&send_message_result);
302
if (message_timeout == 0) {
303
LOGERR(WARN) << "MouseMove: SendMessageTimeout failed";
304
}
305
}
306
307
void SendMessageActionSimulator::SendMouseDownMessage(HWND window_handle,
308
InputState input_state,
309
int button,
310
int x,
311
int y,
312
bool is_double_click) {
313
UINT msg = WM_LBUTTONDOWN;
314
WPARAM button_value = MK_LBUTTON;
315
if (is_double_click) {
316
msg = WM_LBUTTONDBLCLK;
317
}
318
if (button == WD_CLIENT_RIGHT_MOUSE_BUTTON) {
319
msg = WM_RBUTTONDOWN;
320
button_value = MK_RBUTTON;
321
if (is_double_click) {
322
msg = WM_RBUTTONDBLCLK;
323
}
324
}
325
int modifier = 0;
326
if (input_state.is_shift_pressed) {
327
modifier |= MK_SHIFT;
328
}
329
if (input_state.is_control_pressed) {
330
modifier |= MK_CONTROL;
331
}
332
button_value |= modifier;
333
LPARAM coordinates = MAKELPARAM(x, y);
334
// Must use PostMessage for mouse down because message gets lost with
335
// SendMessage and variants. Use a SendMessage with WM_USER to ensure
336
// the posted message has been processed.
337
::PostMessage(window_handle, msg, button_value, coordinates);
338
::SendMessage(window_handle, WM_USER, 0, 0);
339
340
// This 5 millisecond sleep is important for the click element scenario,
341
// as it allows the element to register and respond to the focus event.
342
::Sleep(5);
343
}
344
345
346
void SendMessageActionSimulator::SendMouseUpMessage(HWND window_handle,
347
InputState input_state,
348
int button,
349
int x,
350
int y) {
351
UINT msg = WM_LBUTTONUP;
352
WPARAM button_value = MK_LBUTTON;
353
if (button == WD_CLIENT_RIGHT_MOUSE_BUTTON) {
354
msg = WM_RBUTTONUP;
355
button_value = MK_RBUTTON;
356
}
357
int modifier = 0;
358
if (input_state.is_shift_pressed) {
359
modifier |= MK_SHIFT;
360
}
361
if (input_state.is_control_pressed) {
362
modifier |= MK_CONTROL;
363
}
364
button_value |= modifier;
365
LPARAM coordinates = MAKELPARAM(x, y);
366
// To properly mimic manual mouse movement, we need a move before the up.
367
::SendMessage(window_handle, WM_MOUSEMOVE, modifier, coordinates);
368
// Must use PostMessage for mouse up because message gets lost with
369
// SendMessage and variants. Use a SendMessage with WM_USER to ensure
370
// the posted message has been processed.
371
::PostMessage(window_handle, msg, button_value, coordinates);
372
::SendMessage(window_handle, WM_USER, 0, 0);
373
}
374
375
} // namespace webdriver
376
377
#ifdef __cplusplus
378
extern "C" {
379
#endif
380
381
LRESULT CALLBACK GetMessageProc(int nCode, WPARAM wParam, LPARAM lParam) {
382
if ((nCode == HC_ACTION) && (wParam == PM_REMOVE)) {
383
MSG* msg = reinterpret_cast<MSG*>(lParam);
384
if (msg->message == WM_USER && msg->wParam == 1234 && msg->lParam == 5678) {
385
int message_count = 50;
386
webdriver::HookProcessor::IncrementEventCount(message_count);
387
}
388
}
389
390
return CallNextHookEx(NULL, nCode, wParam, lParam);
391
}
392
393
#ifdef __cplusplus
394
}
395
#endif
396
397