Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/cpp/iedriver/CommandHandlers/ClickElementCommandHandler.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
#define CLICK_OPTION_EVENT_NAME L"ClickOptionEvent"
18
19
#include "ClickElementCommandHandler.h"
20
#include "errorcodes.h"
21
#include "logging.h"
22
#include "../Browser.h"
23
#include "../Element.h"
24
#include "../Generated/atoms.h"
25
#include "../IECommandExecutor.h"
26
#include "../InputManager.h"
27
#include "../Script.h"
28
#include "../StringUtilities.h"
29
#include "../WebDriverConstants.h"
30
31
namespace webdriver {
32
33
ClickElementCommandHandler::ClickElementCommandHandler(void) {
34
}
35
36
ClickElementCommandHandler::~ClickElementCommandHandler(void) {
37
}
38
39
void ClickElementCommandHandler::ExecuteInternal(const IECommandExecutor& executor,
40
const ParametersMap& command_parameters,
41
Response* response) {
42
ParametersMap::const_iterator id_parameter_iterator = command_parameters.find("id");
43
if (id_parameter_iterator == command_parameters.end()) {
44
response->SetErrorResponse(ERROR_INVALID_ARGUMENT, "Missing parameter in URL: id");
45
return;
46
} else {
47
int status_code = WD_SUCCESS;
48
std::string element_id = id_parameter_iterator->second.asString();
49
50
BrowserHandle browser_wrapper;
51
status_code = executor.GetCurrentBrowser(&browser_wrapper);
52
if (status_code != WD_SUCCESS) {
53
response->SetErrorResponse(status_code, "Unable to get browser");
54
return;
55
}
56
57
ElementHandle element_wrapper;
58
status_code = this->GetElement(executor, element_id, &element_wrapper);
59
if (status_code == WD_SUCCESS) {
60
if (this->IsFileUploadElement(element_wrapper)) {
61
response->SetErrorResponse(ERROR_INVALID_ARGUMENT, "Cannot call click on an <input type='file'> element. Use sendKeys to upload files.");
62
return;
63
}
64
std::string navigation_url = "";
65
bool reattach_after_click = false;
66
if (this->IsPossibleNavigation(element_wrapper, &navigation_url)) {
67
reattach_after_click = browser_wrapper->IsCrossZoneUrl(navigation_url);
68
}
69
if (executor.input_manager()->enable_native_events()) {
70
if (this->IsOptionElement(element_wrapper)) {
71
std::string option_click_error = "";
72
status_code = this->ExecuteAtom(executor,
73
this->GetClickAtom(),
74
browser_wrapper,
75
element_wrapper,
76
&option_click_error);
77
if (status_code != WD_SUCCESS) {
78
response->SetErrorResponse(status_code, "Cannot click on option element. " + option_click_error);
79
return;
80
}
81
} else {
82
Json::Value move_action;
83
move_action["type"] = "pointerMove";
84
move_action["origin"] = element_wrapper->ConvertToJson();
85
move_action["duration"] = 0;
86
87
Json::Value down_action;
88
down_action["type"] = "pointerDown";
89
down_action["button"] = 0;
90
91
Json::Value up_action;
92
up_action["type"] = "pointerUp";
93
up_action["button"] = 0;
94
95
Json::Value action_array(Json::arrayValue);
96
action_array.append(move_action);
97
action_array.append(down_action);
98
action_array.append(up_action);
99
100
Json::Value parameters_value;
101
parameters_value["pointerType"] = "mouse";
102
103
Json::Value value;
104
value["type"] = "pointer";
105
value["id"] = "click action mouse";
106
value["parameters"] = parameters_value;
107
value["actions"] = action_array;
108
109
Json::Value actions(Json::arrayValue);
110
actions.append(value);
111
112
int double_click_time = ::GetDoubleClickTime();
113
int milliseconds_since_last_click = (clock() - executor.input_manager()->last_click_time()) * CLOCKS_PER_SEC / 1000;
114
if (double_click_time - milliseconds_since_last_click > 0) {
115
::Sleep(double_click_time - milliseconds_since_last_click);
116
}
117
118
// Scroll the target element into view before executing the action
119
// sequence.
120
LocationInfo location = {};
121
std::vector<LocationInfo> frame_locations;
122
status_code = element_wrapper->GetLocationOnceScrolledIntoView(executor.input_manager()->scroll_behavior(),
123
&location,
124
&frame_locations);
125
126
bool displayed;
127
status_code = element_wrapper->IsDisplayed(true, &displayed);
128
if (status_code != WD_SUCCESS || !displayed) {
129
response->SetErrorResponse(EELEMENTNOTDISPLAYED,
130
"Element is not displayed");
131
return;
132
}
133
134
LocationInfo click_location = {};
135
long obscuring_element_index = -1;
136
std::string obscuring_element_description = "";
137
bool obscured = element_wrapper->IsObscured(&click_location,
138
&obscuring_element_index,
139
&obscuring_element_description);
140
if (obscured) {
141
std::string error_msg = StringUtilities::Format("Element not clickable at point (%d,%d). Other element would receive the click: %s (elementsFromPoint index %d)",
142
click_location.x,
143
click_location.y,
144
obscuring_element_description.c_str(),
145
obscuring_element_index);
146
response->SetErrorResponse(ERROR_ELEMENT_CLICK_INTERCEPTED, error_msg);
147
return;
148
}
149
150
if (reattach_after_click) {
151
browser_wrapper->InitiateBrowserReattach();
152
}
153
std::string error_info = "";
154
IECommandExecutor& mutable_executor = const_cast<IECommandExecutor&>(executor);
155
status_code = mutable_executor.input_manager()->PerformInputSequence(browser_wrapper,
156
actions,
157
&error_info);
158
browser_wrapper->set_wait_required(true);
159
if (status_code != WD_SUCCESS) {
160
if (status_code == EELEMENTCLICKPOINTNOTSCROLLED) {
161
// We hard-code the error code here to be "Element not visible"
162
// to maintain compatibility with previous behavior.
163
response->SetErrorResponse(ERROR_ELEMENT_NOT_INTERACTABLE, "The point at which the driver is attempting to click on the element was not scrolled into the viewport.");
164
} else {
165
response->SetErrorResponse(status_code, "Cannot click on element");
166
}
167
return;
168
}
169
}
170
} else {
171
bool displayed;
172
status_code = element_wrapper->IsDisplayed(true, &displayed);
173
if (status_code != WD_SUCCESS) {
174
response->SetErrorResponse(status_code, "Unable to determine element is displayed");
175
return;
176
}
177
178
if (!displayed) {
179
response->SetErrorResponse(ERROR_ELEMENT_NOT_INTERACTABLE, "Element is not displayed");
180
return;
181
}
182
std::string synthetic_click_error = "";
183
status_code = this->ExecuteAtom(executor,
184
this->GetSyntheticClickAtom(),
185
browser_wrapper,
186
element_wrapper,
187
&synthetic_click_error);
188
if (status_code != WD_SUCCESS) {
189
// This is a hack. We should change this when we can get proper error
190
// codes back from the atoms. We'll assume the script failed because
191
// the element isn't visible.
192
response->SetErrorResponse(ERROR_ELEMENT_NOT_INTERACTABLE,
193
"Received a JavaScript error attempting to click on the element using synthetic events. We are assuming this is because the element isn't displayed, but it may be due to other problems with executing JavaScript.");
194
return;
195
}
196
browser_wrapper->set_wait_required(true);
197
}
198
} else if (status_code == ENOSUCHELEMENT) {
199
response->SetErrorResponse(ERROR_NO_SUCH_ELEMENT, "Invalid internal element ID requested: " + element_id);
200
return;
201
} else {
202
response->SetErrorResponse(status_code, "Element is no longer valid");
203
return;
204
}
205
206
response->SetSuccessResponse(Json::Value::null);
207
}
208
}
209
210
bool ClickElementCommandHandler::IsOptionElement(ElementHandle element_wrapper) {
211
CComPtr<IHTMLOptionElement> option;
212
HRESULT(hr) = element_wrapper->element()->QueryInterface<IHTMLOptionElement>(&option);
213
return SUCCEEDED(hr) && !!option;
214
}
215
216
std::wstring ClickElementCommandHandler::GetSyntheticClickAtom() {
217
std::wstring script_source = L"(function() { return function(){" +
218
atoms::asString(atoms::INPUTS_BIN) +
219
L"; return webdriver.atoms.inputs.click(arguments[0]);" +
220
L"};})();";
221
return script_source;
222
}
223
224
std::wstring ClickElementCommandHandler::GetClickAtom() {
225
std::wstring script_source = L"(function() { return (";
226
script_source += atoms::asString(atoms::CLICK);
227
script_source += L")})();";
228
return script_source;
229
}
230
231
int ClickElementCommandHandler::ExecuteAtom(
232
const IECommandExecutor& executor,
233
const std::wstring& atom_script_source,
234
BrowserHandle browser_wrapper,
235
ElementHandle element_wrapper,
236
std::string* error_msg) {
237
HWND async_executor_handle;
238
CComPtr<IHTMLDocument2> doc;
239
browser_wrapper->GetDocument(&doc);
240
Script script_wrapper(doc, atom_script_source);
241
Json::Value args(Json::arrayValue);
242
args.append(element_wrapper->ConvertToJson());
243
int status_code = script_wrapper.ExecuteAsync(executor,
244
args,
245
ASYNC_SCRIPT_EXECUTION_TIMEOUT_IN_MILLISECONDS,
246
&async_executor_handle);
247
if (status_code != WD_SUCCESS) {
248
if (script_wrapper.ResultIsString()) {
249
std::wstring error = script_wrapper.result().bstrVal;
250
*error_msg = StringUtilities::ToString(error);
251
} else {
252
std::string error = "Executing JavaScript click function returned an";
253
error.append(" unexpected error, but no error could be returned from");
254
error.append(" Internet Explorer's JavaScript engine.");
255
*error_msg = error;
256
}
257
}
258
return status_code;
259
}
260
261
bool ClickElementCommandHandler::IsFileUploadElement(ElementHandle element) {
262
CComPtr<IHTMLInputFileElement> file;
263
element->element()->QueryInterface<IHTMLInputFileElement>(&file);
264
CComPtr<IHTMLInputElement> input;
265
element->element()->QueryInterface<IHTMLInputElement>(&input);
266
CComBSTR element_type;
267
if (input) {
268
input->get_type(&element_type);
269
HRESULT hr = element_type.ToLower();
270
if (FAILED(hr)) {
271
LOGHR(WARN, hr) << "Failed converting type attribute of <input> element to lowercase using ToLower() method of BSTR";
272
}
273
}
274
bool is_file_element = (file != NULL) ||
275
(input != NULL && element_type == L"file");
276
return is_file_element;
277
}
278
279
bool ClickElementCommandHandler::IsPossibleNavigation(ElementHandle element_wrapper,
280
std::string* url) {
281
CComPtr<IHTMLAnchorElement> anchor;
282
element_wrapper->element()->QueryInterface<IHTMLAnchorElement>(&anchor);
283
if (anchor) {
284
CComVariant href_value;
285
element_wrapper->GetAttributeValue("href", &href_value);
286
if (href_value.vt == VT_BSTR) {
287
std::wstring wide_url = href_value.bstrVal;
288
CComPtr<IUri> parsed_url;
289
::CreateUri(wide_url.c_str(), Uri_CREATE_ALLOW_RELATIVE, 0, &parsed_url);
290
DWORD url_scheme = 0;
291
parsed_url->GetScheme(&url_scheme);
292
if (url_scheme == URL_SCHEME_HTTPS || url_scheme == URL_SCHEME_HTTP) {
293
*url = StringUtilities::ToString(wide_url);
294
return true;
295
}
296
}
297
}
298
return false;
299
}
300
301
} // namespace webdriver
302
303