Skip to content

Commit ba3592b

Browse files
committed
Updating IE driver to use SendInput for a single input at a time
The IE driver uses the Windows `SendInput` API when a session is initiated with the `requireWindowFocus` capability. This is a low-level user input simulation API, and very closely mimics the use of a physical mouse and keyboard. One of the guarantees of the API is that when one sends an array of `INPUT` structures, then no input messages can be inserted into the middle of the input stream. However, it appears that there are cases where sending multiple input structures that IE does not process all of them. This leads to truncated or missing keys when using the Selenium sendKeys method. This change makes the driver use SendInput, but only passing a single INPUT structure at a time. This is dangerous, in that it allows other inputs to potentially be slipped into the input stream. This change makes the driver prefer one form of instability (potentially unwanted input events) over another (input events not processed by the browser). Additionally, this change introduces an additional method for forcing the IE window into focus before sending input. This additional method uses the Windows UI Automation accessibility API to do this. The potential down side to using this (apparently) more reliable mechanism is that it may introduce a slight performance penalty, as the UI Automation API is notoriously slow. In practice, this performance penalty is observed to be on the order of milliseconds, so the trade-off here is deemed worth the risk, and there is no way to disable this new check.
1 parent f648f12 commit ba3592b

File tree

2 files changed

+83
-25
lines changed

2 files changed

+83
-25
lines changed

cpp/iedriver/ActionSimulators/SendInputActionSimulator.cpp

Lines changed: 82 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
#include "SendInputActionSimulator.h"
1818

19+
#include <UIAutomation.h>
20+
1921
#include "errorcodes.h"
2022
#include "logging.h"
2123

@@ -85,12 +87,10 @@ int SendInputActionSimulator::SimulateActions(BrowserHandle browser_wrapper,
8587
INPUT sleep_input = inputs[sleep_input_index];
8688
size_t number_of_inputs = sleep_input_index - next_input_index;
8789
if (number_of_inputs > 0) {
88-
this->SetFocusToBrowser(browser_wrapper);
89-
HookProcessor::ResetEventCount();
90-
int sent_inputs = ::SendInput(static_cast<int>(number_of_inputs),
91-
&inputs[next_input_index],
92-
sizeof(INPUT));
93-
this->WaitForInputEventProcessing(sent_inputs);
90+
this->SendInputToBrowser(browser_wrapper,
91+
inputs,
92+
static_cast<int>(next_input_index),
93+
static_cast<int>(number_of_inputs));
9494
}
9595
LOG(DEBUG) << "Processing pause event";
9696
::Sleep(inputs[sleep_input_index].hi.uMsg);
@@ -99,12 +99,10 @@ int SendInputActionSimulator::SimulateActions(BrowserHandle browser_wrapper,
9999
// Now send any inputs after the last sleep, if any.
100100
size_t last_inputs = inputs.size() - next_input_index;
101101
if (last_inputs > 0) {
102-
this->SetFocusToBrowser(browser_wrapper);
103-
HookProcessor::ResetEventCount();
104-
int sent_inputs = ::SendInput(static_cast<int>(last_inputs),
105-
&inputs[next_input_index],
106-
sizeof(INPUT));
107-
this->WaitForInputEventProcessing(sent_inputs);
102+
this->SendInputToBrowser(browser_wrapper,
103+
inputs,
104+
static_cast<int>(next_input_index),
105+
static_cast<int>(last_inputs));
108106
}
109107

110108
// We're done here, so uninstall the hooks, and reset the buffer size.
@@ -114,6 +112,22 @@ int SendInputActionSimulator::SimulateActions(BrowserHandle browser_wrapper,
114112
return WD_SUCCESS;
115113
}
116114

115+
void SendInputActionSimulator::SendInputToBrowser(BrowserHandle browser_wrapper,
116+
std::vector<INPUT> inputs,
117+
int start_index,
118+
int input_count) {
119+
if (input_count > 0) {
120+
this->SetFocusToBrowser(browser_wrapper);
121+
HookProcessor::ResetEventCount();
122+
int sent_inputs = 0;
123+
for (int i = start_index; i < start_index + input_count; ++i) {
124+
::SendInput(1, &inputs[i], sizeof(INPUT));
125+
sent_inputs += 1;
126+
}
127+
this->WaitForInputEventProcessing(sent_inputs);
128+
}
129+
}
130+
117131
void SendInputActionSimulator::GetNormalizedCoordinates(HWND window_handle,
118132
int x,
119133
int y,
@@ -151,20 +165,58 @@ bool SendInputActionSimulator::WaitForInputEventProcessing(int input_count) {
151165
inputs_processed = processed_event_count >= input_count;
152166
}
153167
LOG(DEBUG) << "Requested waiting for " << input_count
154-
<< " events, processed " << processed_event_count << " events";
168+
<< " events, processed " << processed_event_count << " events,"
169+
<< " timed out after " << total_timeout_in_milliseconds
170+
<< " milliseconds";
155171
return inputs_processed;
156172
}
157173

158174
bool SendInputActionSimulator::SetFocusToBrowser(BrowserHandle browser_wrapper) {
159175
LOG(TRACE) << "Entering InputManager::SetFocusToBrowser";
160-
UINT_PTR lock_timeout = 0;
161-
DWORD process_id = 0;
162-
DWORD thread_id = ::GetWindowThreadProcessId(browser_wrapper->GetContentWindowHandle(),
163-
&process_id);
164-
DWORD current_thread_id = ::GetCurrentThreadId();
165-
DWORD current_process_id = ::GetCurrentProcessId();
166-
HWND current_foreground_window = ::GetForegroundWindow();
167-
if (current_foreground_window != browser_wrapper->GetTopLevelWindowHandle()) {
176+
177+
HWND top_level_window_handle = browser_wrapper->GetTopLevelWindowHandle();
178+
HWND foreground_window = ::GetAncestor(::GetForegroundWindow(), GA_ROOT);
179+
if (foreground_window != top_level_window_handle) {
180+
CComPtr<IUIAutomation> ui_automation;
181+
HRESULT hr = ::CoCreateInstance(CLSID_CUIAutomation,
182+
NULL,
183+
CLSCTX_INPROC_SERVER,
184+
IID_IUIAutomation,
185+
reinterpret_cast<void**>(&ui_automation));
186+
if (SUCCEEDED(hr)) {
187+
LOG(TRACE) << "Using UI Automation to set window focus";
188+
CComPtr<IUIAutomationElement> parent_window;
189+
hr = ui_automation->ElementFromHandle(top_level_window_handle,
190+
&parent_window);
191+
if (SUCCEEDED(hr)) {
192+
CComPtr<IUIAutomationWindowPattern> window_pattern;
193+
hr = parent_window->GetCurrentPatternAs(UIA_WindowPatternId,
194+
IID_PPV_ARGS(&window_pattern));
195+
if (SUCCEEDED(hr)) {
196+
BOOL is_topmost;
197+
hr = window_pattern->get_CurrentIsTopmost(&is_topmost);
198+
WindowVisualState visual_state;
199+
hr = window_pattern->get_CurrentWindowVisualState(&visual_state);
200+
if (visual_state == WindowVisualState::WindowVisualState_Maximized ||
201+
visual_state == WindowVisualState::WindowVisualState_Normal) {
202+
parent_window->SetFocus();
203+
window_pattern->SetWindowVisualState(visual_state);
204+
}
205+
}
206+
}
207+
}
208+
}
209+
210+
foreground_window = ::GetAncestor(::GetForegroundWindow(), GA_ROOT);
211+
if (foreground_window != top_level_window_handle) {
212+
LOG(TRACE) << "Window still not in foreground; "
213+
<< "attempting to use SetForegroundWindow API";
214+
UINT_PTR lock_timeout = 0;
215+
DWORD process_id = 0;
216+
DWORD thread_id = ::GetWindowThreadProcessId(browser_wrapper->GetContentWindowHandle(),
217+
&process_id);
218+
DWORD current_thread_id = ::GetCurrentThreadId();
219+
DWORD current_process_id = ::GetCurrentProcessId();
168220
if (current_thread_id != thread_id) {
169221
::AttachThreadInput(current_thread_id, thread_id, TRUE);
170222
::SystemParametersInfo(SPI_GETFOREGROUNDLOCKTIMEOUT,
@@ -177,7 +229,7 @@ bool SendInputActionSimulator::SetFocusToBrowser(BrowserHandle browser_wrapper)
177229
SPIF_SENDWININICHANGE | SPIF_UPDATEINIFILE);
178230
::AllowSetForegroundWindow(current_process_id);
179231
}
180-
::SetForegroundWindow(browser_wrapper->GetTopLevelWindowHandle());
232+
::SetForegroundWindow(top_level_window_handle);
181233
if (current_thread_id != thread_id) {
182234
::SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT,
183235
0,
@@ -186,7 +238,8 @@ bool SendInputActionSimulator::SetFocusToBrowser(BrowserHandle browser_wrapper)
186238
::AttachThreadInput(current_thread_id, thread_id, FALSE);
187239
}
188240
}
189-
return ::GetForegroundWindow() == browser_wrapper->GetTopLevelWindowHandle();
241+
foreground_window = ::GetAncestor(::GetForegroundWindow(), GA_ROOT);
242+
return foreground_window == top_level_window_handle;
190243
}
191244

192245
} // namespace webdriver
@@ -196,12 +249,16 @@ extern "C" {
196249
#endif
197250

198251
LRESULT CALLBACK KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam) {
199-
webdriver::HookProcessor::IncrementEventCount(1);
252+
if (nCode == HC_ACTION) {
253+
webdriver::HookProcessor::IncrementEventCount(1);
254+
}
200255
return ::CallNextHookEx(NULL, nCode, wParam, lParam);
201256
}
202257

203258
LRESULT CALLBACK MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam) {
204-
webdriver::HookProcessor::IncrementEventCount(1);
259+
if (nCode == HC_ACTION) {
260+
webdriver::HookProcessor::IncrementEventCount(1);
261+
}
205262
return ::CallNextHookEx(NULL, nCode, wParam, lParam);
206263
}
207264

cpp/iedriver/ActionSimulators/SendInputActionSimulator.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class SendInputActionSimulator : public ActionSimulator {
3939

4040
bool WaitForInputEventProcessing(int input_count);
4141
bool SetFocusToBrowser(BrowserHandle browser_wrapper);
42+
void SendInputToBrowser(BrowserHandle browser_wrapper, std::vector<INPUT> inputs, int start_index, int input_count);
4243
};
4344

4445
} // namespace webdriver

0 commit comments

Comments
 (0)