Skip to content

Commit e73ca05

Browse files
committed
Modifying mouse event handling to match W3C Specification
THIS IS A POTENTIAL BREAKING CHANGE. The simulation of mouse movement for the actions command now conforms to the W3C WebDriver Specification for mouse movement. This means that offsets in elements are now measured from the center of the element instead of the top-left, which was the previous behavior. Additionally, attempting to move the mouse pointer outside the browser view port will result in a "mouse movement out of bounds" error. While users should be aware that this might cause code using user interactions to fail, not that the behavior is now consistent with the behavior outlined in the specification, and the behavior of geckodriver in particular.
1 parent 62d3a6d commit e73ca05

9 files changed

+165
-54
lines changed

cpp/iedriver/CommandHandlers/ActionsCommandHandler.cpp

+6-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,12 @@ void ActionsCommandHandler::ExecuteInternal(
5151
}
5252
status_code = executor.input_manager()->PerformInputSequence(browser_wrapper, actions_parameter_iterator->second);
5353
if (status_code != WD_SUCCESS) {
54-
response->SetErrorResponse(status_code, "Unexpected error performing action sequence.");
54+
if (status_code == EMOVETARGETOUTOFBOUNDS) {
55+
response->SetErrorResponse(status_code, "The requested mouse movement would be outside the bounds of the current view port.");
56+
} else {
57+
response->SetErrorResponse(status_code, "Unexpected error performing action sequence.");
58+
}
59+
return;
5560
}
5661
response->SetSuccessResponse(Json::Value::null);
5762
}

cpp/iedriver/CommandHandlers/ClickElementCommandHandler.cpp

+16
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,22 @@ void ClickElementCommandHandler::ExecuteInternal(const IECommandExecutor& execut
104104
::Sleep(double_click_time - milliseconds_since_last_click);
105105
}
106106

107+
// Scroll the target element into view before executing the action
108+
// sequence.
109+
LocationInfo location = {};
110+
std::vector<LocationInfo> frame_locations;
111+
status_code = element_wrapper->GetLocationOnceScrolledIntoView(executor.input_manager()->scroll_behavior(),
112+
&location,
113+
&frame_locations);
114+
115+
bool displayed;
116+
status_code = element_wrapper->IsDisplayed(true, &displayed);
117+
if (status_code != WD_SUCCESS || !displayed) {
118+
response->SetErrorResponse(EELEMENTNOTDISPLAYED,
119+
"Element is not displayed");
120+
return;
121+
}
122+
107123
IECommandExecutor& mutable_executor = const_cast<IECommandExecutor&>(executor);
108124
status_code = mutable_executor.input_manager()->PerformInputSequence(browser_wrapper, actions);
109125
browser_wrapper->set_wait_required(true);

cpp/iedriver/CommandHandlers/SendKeysCommandHandler.cpp

+4-2
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,13 @@ void SendKeysCommandHandler::ExecuteInternal(
9292
if (status_code == WD_SUCCESS) {
9393
CComPtr<IHTMLElement> element(element_wrapper->element());
9494

95+
// Scroll the target element into view before executing the action
96+
// sequence.
9597
LocationInfo location = {};
9698
std::vector<LocationInfo> frame_locations;
9799
element_wrapper->GetLocationOnceScrolledIntoView(executor.input_manager()->scroll_behavior(),
98-
&location,
99-
&frame_locations);
100+
&location,
101+
&frame_locations);
100102

101103
CComPtr<IHTMLInputFileElement> file;
102104
element->QueryInterface<IHTMLInputFileElement>(&file);

cpp/iedriver/Element.cpp

+9
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,15 @@ int Element::GetClickLocation(const ElementScrollBehavior scroll_behavior,
245245
return status_code;
246246
}
247247

248+
int Element::GetStaticClickLocation(LocationInfo* click_location) {
249+
std::vector<LocationInfo> frame_locations;
250+
LocationInfo element_location = {};
251+
int result = this->GetLocation(&element_location, &frame_locations);
252+
bool document_contains_frames = frame_locations.size() != 0;
253+
*click_location = this->CalculateClickPoint(element_location, document_contains_frames);
254+
return result;
255+
}
256+
248257
int Element::GetAttributeValue(const std::string& attribute_name,
249258
VARIANT* attribute_value) {
250259
LOG(TRACE) << "Entering Element::GetAttributeValue";

cpp/iedriver/Element.h

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class Element {
4848
int GetLocationOnceScrolledIntoView(const ElementScrollBehavior scroll,
4949
LocationInfo* location,
5050
std::vector<LocationInfo>* frame_locations);
51+
int GetStaticClickLocation(LocationInfo* click_location);
5152
int GetClickLocation(const ElementScrollBehavior scroll_behavior,
5253
LocationInfo* element_location,
5354
LocationInfo* click_location);

cpp/iedriver/InputManager.cpp

+91-17
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ InputManager::InputManager() {
5454
this->current_input_state_.is_alt_pressed = false;
5555
this->current_input_state_.is_control_pressed = false;
5656
this->current_input_state_.is_shift_pressed = false;
57+
this->current_input_state_.is_meta_pressed = false;
5758
this->current_input_state_.is_left_button_pressed = false;
5859
this->current_input_state_.is_right_button_pressed = false;
5960
this->current_input_state_.mouse_x = 0;
@@ -239,6 +240,7 @@ InputState InputManager::CloneCurrentInputState(void) {
239240
current_input_state.is_alt_pressed = this->current_input_state_.is_alt_pressed;
240241
current_input_state.is_control_pressed = this->current_input_state_.is_control_pressed;
241242
current_input_state.is_shift_pressed = this->current_input_state_.is_shift_pressed;
243+
current_input_state.is_meta_pressed = this->current_input_state_.is_meta_pressed;
242244
current_input_state.is_left_button_pressed = this->current_input_state_.is_left_button_pressed;
243245
current_input_state.is_right_button_pressed = this->current_input_state_.is_right_button_pressed;
244246
current_input_state.mouse_x = this->current_input_state_.mouse_x;
@@ -395,10 +397,13 @@ int InputManager::PointerMoveTo(BrowserHandle browser_wrapper,
395397
long end_y = start_y;
396398
if (element_specified) {
397399
LocationInfo element_location;
398-
LocationInfo move_location;
399-
status_code = target_element->GetClickLocation(this->scroll_behavior_,
400-
&element_location,
401-
&move_location);
400+
// Note: The caller of the action sequence is responsible for making
401+
// sure the target element is in the view port. In particular, the
402+
// high-level click and sendKeys implementations do this in their
403+
// command handlers. Further note that offsets specified in this
404+
// move action will be relative to the center of the element as
405+
// calculated here.
406+
status_code = target_element->GetStaticClickLocation(&element_location);
402407
// We can't use the status code alone here. Even though the center of the
403408
// element may not reachable via the mouse, we might still be able to move
404409
// to whatever portion of the element *is* visible in the viewport, especially
@@ -420,11 +425,6 @@ int InputManager::PointerMoveTo(BrowserHandle browser_wrapper,
420425
// move will be at some offset from the element origin.
421426
end_x = element_location.x;
422427
end_y = element_location.y;
423-
if (!offset_specified) {
424-
// No offset was specified, which means move to the center of the element.
425-
end_x = move_location.x;
426-
end_y = move_location.y;
427-
}
428428
}
429429

430430
if (origin == "viewport") {
@@ -441,9 +441,20 @@ int InputManager::PointerMoveTo(BrowserHandle browser_wrapper,
441441
}
442442
}
443443

444+
444445
LOG(DEBUG) << "Queueing SendInput structure for mouse move (origin: " << origin
445446
<< ", x: " << end_x << ", y: " << end_y << ")";
446447
HWND browser_window_handle = browser_wrapper->GetContentWindowHandle();
448+
RECT window_rect;
449+
::GetWindowRect(browser_window_handle, &window_rect);
450+
POINT click_point = { end_x, end_y };
451+
::ClientToScreen(browser_window_handle, &click_point);
452+
if (click_point.x < window_rect.left ||
453+
click_point.x > window_rect.right ||
454+
click_point.y < window_rect.top ||
455+
click_point.y > window_rect.bottom) {
456+
return EMOVETARGETOUTOFBOUNDS;
457+
}
447458
if (end_x == input_state->mouse_x && end_y == input_state->mouse_y) {
448459
LOG(DEBUG) << "Omitting SendInput structure for mouse move; no movement required (x: "
449460
<< end_x << ", y: " << end_y << ")";
@@ -685,6 +696,25 @@ void InputManager::AddKeyboardInput(HWND window_handle,
685696
}
686697
this->UpdatePressedKeys(WD_KEY_ALT, input_state->is_alt_pressed);
687698
}
699+
700+
// If the character represents the Meta (Windows) key, or represents
701+
// the "release all modifiers" key and the Meta key is down, send
702+
// the appropriate down or up keystroke for the Meta key.
703+
if (character == WD_KEY_META ||
704+
character == WD_KEY_R_META ||
705+
(character == WD_KEY_NULL && input_state->is_meta_pressed)) {
706+
//modifier_key_info.key_code = VK_LWIN;
707+
//this->CreateKeyboardInputItem(modifier_key_info,
708+
// 0,
709+
// input_state->is_meta_pressed);
710+
//if (input_state->is_meta_pressed) {
711+
// input_state->is_meta_pressed = false;
712+
//} else {
713+
// input_state->is_meta_pressed = true;
714+
//}
715+
//this->UpdatePressedKeys(WD_KEY_META, input_state->is_meta_pressed);
716+
}
717+
688718
return;
689719
}
690720

@@ -803,9 +833,11 @@ bool InputManager::IsModifierKey(wchar_t character) {
803833
return character == WD_KEY_SHIFT ||
804834
character == WD_KEY_CONTROL ||
805835
character == WD_KEY_ALT ||
836+
character == WD_KEY_META ||
806837
character == WD_KEY_R_SHIFT ||
807838
character == WD_KEY_R_CONTROL ||
808839
character == WD_KEY_R_ALT ||
840+
character == WD_KEY_R_META ||
809841
character == WD_KEY_NULL;
810842
}
811843

@@ -999,6 +1031,56 @@ KeyInfo InputManager::GetKeyInfo(HWND window_handle, wchar_t character) {
9991031
key_info.scan_code = VK_DIVIDE;
10001032
key_info.is_extended_key = true;
10011033
}
1034+
else if (character == WD_KEY_R_PAGEUP) {
1035+
key_info.key_code = VK_PRIOR;
1036+
key_info.scan_code = VK_PRIOR;
1037+
key_info.is_extended_key = true;
1038+
}
1039+
else if (character == WD_KEY_R_PAGEDN) {
1040+
key_info.key_code = VK_NEXT;
1041+
key_info.scan_code = VK_NEXT;
1042+
key_info.is_extended_key = true;
1043+
}
1044+
else if (character == WD_KEY_R_END) { // end
1045+
key_info.key_code = VK_END;
1046+
key_info.scan_code = VK_END;
1047+
key_info.is_extended_key = true;
1048+
}
1049+
else if (character == WD_KEY_R_HOME) { // home
1050+
key_info.key_code = VK_HOME;
1051+
key_info.scan_code = VK_HOME;
1052+
key_info.is_extended_key = true;
1053+
}
1054+
else if (character == WD_KEY_R_LEFT) { // left arrow
1055+
key_info.key_code = VK_LEFT;
1056+
key_info.scan_code = VK_LEFT;
1057+
key_info.is_extended_key = true;
1058+
}
1059+
else if (character == WD_KEY_R_UP) { // up arrow
1060+
key_info.key_code = VK_UP;
1061+
key_info.scan_code = VK_UP;
1062+
key_info.is_extended_key = true;
1063+
}
1064+
else if (character == WD_KEY_R_RIGHT) { // right arrow
1065+
key_info.key_code = VK_RIGHT;
1066+
key_info.scan_code = VK_RIGHT;
1067+
key_info.is_extended_key = true;
1068+
}
1069+
else if (character == WD_KEY_R_DOWN) { // down arrow
1070+
key_info.key_code = VK_DOWN;
1071+
key_info.scan_code = VK_DOWN;
1072+
key_info.is_extended_key = true;
1073+
}
1074+
else if (character == WD_KEY_R_INSERT) { // insert
1075+
key_info.key_code = VK_INSERT;
1076+
key_info.scan_code = VK_INSERT;
1077+
key_info.is_extended_key = true;
1078+
}
1079+
else if (character == WD_KEY_R_DELETE) { // delete
1080+
key_info.key_code = VK_DELETE;
1081+
key_info.scan_code = VK_DELETE;
1082+
key_info.is_extended_key = true;
1083+
}
10021084
else if (character == WD_KEY_F1) { // F1
10031085
key_info.key_code = VK_F1;
10041086
key_info.scan_code = VK_F1;
@@ -1047,14 +1129,6 @@ KeyInfo InputManager::GetKeyInfo(HWND window_handle, wchar_t character) {
10471129
key_info.key_code = VK_F12;
10481130
key_info.scan_code = VK_F12;
10491131
}
1050-
else if (character == WD_KEY_META) { // Meta
1051-
key_info.key_code = VK_LWIN;
1052-
key_info.scan_code = VK_LWIN;
1053-
}
1054-
else if (character == WD_KEY_R_META) { // Meta
1055-
key_info.key_code = VK_RWIN;
1056-
key_info.scan_code = VK_RWIN;
1057-
}
10581132
else if (character == L'\n') { // line feed
10591133
key_info.key_code = VK_RETURN;
10601134
key_info.scan_code = VK_RETURN;

cpp/iedriver/InputState.h

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ struct InputState {
2525
bool is_shift_pressed;
2626
bool is_control_pressed;
2727
bool is_alt_pressed;
28+
bool is_meta_pressed;
2829
bool is_left_button_pressed;
2930
bool is_right_button_pressed;
3031
long mouse_x;

cpp/webdriver-server/errorcodes.h

+35-34
Original file line numberDiff line numberDiff line change
@@ -19,39 +19,40 @@
1919

2020
#define WD_SUCCESS 0
2121

22-
#define EINDEXOUTOFBOUNDS 1
23-
#define ENOCOLLECTION 2
24-
#define ENOSTRING 3
25-
#define ENOSTRINGLENGTH 4
26-
#define ENOSTRINGWRAPPER 5
27-
#define ENOSUCHDRIVER 6
28-
#define ENOSUCHELEMENT 7
29-
#define ENOSUCHFRAME 8
30-
#define ENOTIMPLEMENTED 9
31-
#define EOBSOLETEELEMENT 10
32-
#define EELEMENTNOTDISPLAYED 11
33-
#define EELEMENTNOTENABLED 12
34-
#define EUNHANDLEDERROR 13
35-
#define EEXPECTEDERROR 14
36-
#define EELEMENTNOTSELECTED 15
37-
#define ENOSUCHDOCUMENT 16
38-
#define EUNEXPECTEDJSERROR 17
39-
#define ENOSCRIPTRESULT 18
40-
#define EUNKNOWNSCRIPTRESULT 19
41-
#define ENOSUCHCOLLECTION 20
42-
#define ETIMEOUT 21
43-
#define ENULLPOINTER 22
44-
#define ENOSUCHWINDOW 23
45-
#define EINVALIDCOOKIEDOMAIN 24
46-
#define EUNABLETOSETCOOKIE 25
47-
#define EUNEXPECTEDALERTOPEN 26
48-
#define ENOSUCHALERT 27
49-
#define ESCRIPTTIMEOUT 28
50-
#define EINVALIDCOORDINATES 29
51-
#define EINVALIDSELECTOR 32
52-
#define ECLICKINTERCEPTED 33
53-
#define EINVALIDARGUMENT 34
54-
#define ENOSUCHCOOKIE 35
22+
#define EINDEXOUTOFBOUNDS 1
23+
#define ENOCOLLECTION 2
24+
#define ENOSTRING 3
25+
#define ENOSTRINGLENGTH 4
26+
#define ENOSTRINGWRAPPER 5
27+
#define ENOSUCHDRIVER 6
28+
#define ENOSUCHELEMENT 7
29+
#define ENOSUCHFRAME 8
30+
#define ENOTIMPLEMENTED 9
31+
#define EOBSOLETEELEMENT 10
32+
#define EELEMENTNOTDISPLAYED 11
33+
#define EELEMENTNOTENABLED 12
34+
#define EUNHANDLEDERROR 13
35+
#define EEXPECTEDERROR 14
36+
#define EELEMENTNOTSELECTED 15
37+
#define ENOSUCHDOCUMENT 16
38+
#define EUNEXPECTEDJSERROR 17
39+
#define ENOSCRIPTRESULT 18
40+
#define EUNKNOWNSCRIPTRESULT 19
41+
#define ENOSUCHCOLLECTION 20
42+
#define ETIMEOUT 21
43+
#define ENULLPOINTER 22
44+
#define ENOSUCHWINDOW 23
45+
#define EINVALIDCOOKIEDOMAIN 24
46+
#define EUNABLETOSETCOOKIE 25
47+
#define EUNEXPECTEDALERTOPEN 26
48+
#define ENOSUCHALERT 27
49+
#define ESCRIPTTIMEOUT 28
50+
#define EINVALIDCOORDINATES 29
51+
#define EINVALIDSELECTOR 32
52+
#define ECLICKINTERCEPTED 33
53+
#define EMOVETARGETOUTOFBOUNDS 34
54+
#define ENOSUCHCOOKIE 35
55+
#define EINVALIDARGUMENT 62
5556

5657
#define ERROR_ELEMENT_CLICK_INTERCEPTED "element click intercepted"
5758
#define ERROR_ELEMENT_NOT_SELECTABLE "element not selectable"
@@ -82,4 +83,4 @@
8283
#define ERROR_UNKNOWN_METHOD "unknown method"
8384
#define ERROR_UNSUPPORTED_OPERATION "unsupported operation"
8485

85-
#endif // WEBDRIVER_SERVER_ERRORCODES_H_
86+
#endif // WEBDRIVER_SERVER_ERRORCODES_H_

cpp/webdriver-server/response.cc

+2
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@ std::string Response::ConvertErrorCode(const int error_code) {
178178
return ERROR_INVALID_COOKIE_DOMAIN;
179179
} else if (error_code == ESCRIPTTIMEOUT) {
180180
return ERROR_SCRIPT_TIMEOUT;
181+
} else if (error_code == EMOVETARGETOUTOFBOUNDS) {
182+
return ERROR_MOVE_TARGET_OUT_OF_BOUNDS;
181183
}
182184

183185
return "";

0 commit comments

Comments
 (0)