1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/test/chromedriver/element_commands.h"
6
7 #include <stddef.h>
8
9 #include <cmath>
10 #include <vector>
11
12 #include "base/callback.h"
13 #include "base/files/file_path.h"
14 #include "base/files/file_util.h"
15 #include "base/logging.h"
16 #include "base/strings/string_split.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/stringprintf.h"
19 #include "base/threading/platform_thread.h"
20 #include "base/time/time.h"
21 #include "base/values.h"
22 #include "chrome/test/chromedriver/basic_types.h"
23 #include "chrome/test/chromedriver/chrome/chrome.h"
24 #include "chrome/test/chromedriver/chrome/js.h"
25 #include "chrome/test/chromedriver/chrome/status.h"
26 #include "chrome/test/chromedriver/chrome/ui_events.h"
27 #include "chrome/test/chromedriver/chrome/web_view.h"
28 #include "chrome/test/chromedriver/constants/version.h"
29 #include "chrome/test/chromedriver/element_util.h"
30 #include "chrome/test/chromedriver/session.h"
31 #include "chrome/test/chromedriver/util.h"
32 #include "third_party/webdriver/atoms.h"
33
34 const int kFlickTouchEventsPerSecond = 30;
35 const std::set<std::string> textControlTypes = {"text", "search", "tel", "url",
36 "password"};
37 const std::set<std::string> inputControlTypes = {
38 "text", "search", "url", "tel", "email",
39 "password", "date", "month", "week", "time",
40 "datetime-local", "number", "range", "color", "file"};
41
42 const std::set<std::string> nontypeableControlTypes = {"color"};
43
44 namespace {
45
FocusToElement(Session * session,WebView * web_view,const std::string & element_id)46 Status FocusToElement(
47 Session* session,
48 WebView* web_view,
49 const std::string& element_id) {
50 Status status = CheckElement(element_id);
51 if (status.IsError())
52 return status;
53 bool is_displayed = false;
54 bool is_focused = false;
55 base::TimeTicks start_time = base::TimeTicks::Now();
56 while (true) {
57 status = IsElementDisplayed(
58 session, web_view, element_id, true, &is_displayed);
59 if (status.IsError())
60 return status;
61 if (is_displayed)
62 break;
63 status = IsElementFocused(session, web_view, element_id, &is_focused);
64 if (status.IsError())
65 return status;
66 if (is_focused)
67 break;
68 if (base::TimeTicks::Now() - start_time >= session->implicit_wait) {
69 return Status(kElementNotVisible);
70 }
71 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
72 }
73
74 bool is_enabled = false;
75 status = IsElementEnabled(session, web_view, element_id, &is_enabled);
76 if (status.IsError())
77 return status;
78 if (!is_enabled)
79 return Status(kInvalidElementState);
80
81 if (!is_focused) {
82 base::ListValue args;
83 args.Append(CreateElement(element_id));
84 std::unique_ptr<base::Value> result;
85 status = web_view->CallFunction(
86 session->GetCurrentFrameId(), kFocusScript, args, &result);
87 if (status.IsError())
88 return status;
89 }
90 return Status(kOk);
91 }
92
SendKeysToElement(Session * session,WebView * web_view,const std::string & element_id,const bool is_text,const base::ListValue * key_list)93 Status SendKeysToElement(Session* session,
94 WebView* web_view,
95 const std::string& element_id,
96 const bool is_text,
97 const base::ListValue* key_list) {
98 // If we were previously focused, we don't need to focus again.
99 // But also, later we don't move the carat if we were already in focus.
100 // However, non-text elements such as contenteditable elements needs to be
101 // focused to ensure the keys will end up being sent to the correct place.
102 // So in the case of non-text elements, we still focusToElement.
103 bool wasPreviouslyFocused = false;
104 IsElementFocused(session, web_view, element_id, &wasPreviouslyFocused);
105 if (!wasPreviouslyFocused || !is_text) {
106 Status status = FocusToElement(session, web_view, element_id);
107 if (status.IsError())
108 return Status(kElementNotInteractable);
109 }
110
111 // Move cursor/caret to append the input if we only just focused this
112 // element. keys if element's type is text-related
113 if (is_text && !wasPreviouslyFocused) {
114 base::ListValue args;
115 args.Append(CreateElement(element_id));
116 std::unique_ptr<base::Value> result;
117 Status status = web_view->CallFunction(
118 session->GetCurrentFrameId(),
119 "elem => elem.setSelectionRange(elem.value.length, elem.value.length)",
120 args, &result);
121 if (status.IsError())
122 return status;
123 }
124 return SendKeysOnWindow(web_view, key_list, true, &session->sticky_modifiers);
125 }
126
127 } // namespace
128
ExecuteElementCommand(const ElementCommand & command,Session * session,WebView * web_view,const base::DictionaryValue & params,std::unique_ptr<base::Value> * value,Timeout * timeout)129 Status ExecuteElementCommand(
130 const ElementCommand& command,
131 Session* session,
132 WebView* web_view,
133 const base::DictionaryValue& params,
134 std::unique_ptr<base::Value>* value,
135 Timeout* timeout) {
136 std::string id;
137 if (params.GetString("id", &id) || params.GetString("element", &id))
138 return command.Run(session, web_view, id, params, value);
139 return Status(kInvalidArgument, "element identifier must be a string");
140 }
141
ExecuteFindChildElement(int interval_ms,Session * session,WebView * web_view,const std::string & element_id,const base::DictionaryValue & params,std::unique_ptr<base::Value> * value)142 Status ExecuteFindChildElement(int interval_ms,
143 Session* session,
144 WebView* web_view,
145 const std::string& element_id,
146 const base::DictionaryValue& params,
147 std::unique_ptr<base::Value>* value) {
148 return FindElement(
149 interval_ms, true, &element_id, session, web_view, params, value);
150 }
151
ExecuteFindChildElements(int interval_ms,Session * session,WebView * web_view,const std::string & element_id,const base::DictionaryValue & params,std::unique_ptr<base::Value> * value)152 Status ExecuteFindChildElements(int interval_ms,
153 Session* session,
154 WebView* web_view,
155 const std::string& element_id,
156 const base::DictionaryValue& params,
157 std::unique_ptr<base::Value>* value) {
158 return FindElement(
159 interval_ms, false, &element_id, session, web_view, params, value);
160 }
161
ExecuteClickElement(Session * session,WebView * web_view,const std::string & element_id,const base::DictionaryValue & params,std::unique_ptr<base::Value> * value)162 Status ExecuteClickElement(Session* session,
163 WebView* web_view,
164 const std::string& element_id,
165 const base::DictionaryValue& params,
166 std::unique_ptr<base::Value>* value) {
167 std::string tag_name;
168 Status status = GetElementTagName(session, web_view, element_id, &tag_name);
169 if (status.IsError())
170 return status;
171 if (tag_name == "option") {
172 bool is_toggleable;
173 status = IsOptionElementTogglable(
174 session, web_view, element_id, &is_toggleable);
175 if (status.IsError())
176 return status;
177 if (is_toggleable)
178 return ToggleOptionElement(session, web_view, element_id);
179 else
180 return SetOptionElementSelected(session, web_view, element_id, true);
181 } else {
182 if (tag_name == "input") {
183 std::unique_ptr<base::Value> get_element_type;
184 status = GetElementAttribute(session, web_view, element_id, "type",
185 &get_element_type);
186 if (status.IsError())
187 return status;
188 std::string element_type;
189 if (get_element_type->GetAsString(&element_type))
190 element_type = base::ToLowerASCII(element_type);
191 if (element_type == "file")
192 return Status(kInvalidArgument);
193 }
194 WebPoint location;
195 status = GetElementClickableLocation(
196 session, web_view, element_id, &location);
197 if (status.IsError())
198 return status;
199
200 std::vector<MouseEvent> events;
201 events.push_back(MouseEvent(kMovedMouseEventType, kNoneMouseButton,
202 location.x, location.y,
203 session->sticky_modifiers, 0, 0));
204 events.push_back(MouseEvent(kPressedMouseEventType, kLeftMouseButton,
205 location.x, location.y,
206 session->sticky_modifiers, 0, 1));
207 events.push_back(MouseEvent(kReleasedMouseEventType, kLeftMouseButton,
208 location.x, location.y,
209 session->sticky_modifiers, 1, 1));
210 status = web_view->DispatchMouseEvents(events, session->GetCurrentFrameId(),
211 false);
212 if (status.IsOk())
213 session->mouse_position = location;
214 return status;
215 }
216 }
217
ExecuteTouchSingleTap(Session * session,WebView * web_view,const std::string & element_id,const base::DictionaryValue & params,std::unique_ptr<base::Value> * value)218 Status ExecuteTouchSingleTap(Session* session,
219 WebView* web_view,
220 const std::string& element_id,
221 const base::DictionaryValue& params,
222 std::unique_ptr<base::Value>* value) {
223 WebPoint location;
224 Status status = GetElementClickableLocation(
225 session, web_view, element_id, &location);
226 if (status.IsError())
227 return status;
228 if (!session->chrome->HasTouchScreen()) {
229 // TODO(samuong): remove this once we stop supporting M44.
230 std::vector<TouchEvent> events;
231 events.push_back(
232 TouchEvent(kTouchStart, location.x, location.y));
233 events.push_back(
234 TouchEvent(kTouchEnd, location.x, location.y));
235 return web_view->DispatchTouchEvents(events, false);
236 }
237 return web_view->SynthesizeTapGesture(location.x, location.y, 1, false);
238 }
239
ExecuteTouchDoubleTap(Session * session,WebView * web_view,const std::string & element_id,const base::DictionaryValue & params,std::unique_ptr<base::Value> * value)240 Status ExecuteTouchDoubleTap(Session* session,
241 WebView* web_view,
242 const std::string& element_id,
243 const base::DictionaryValue& params,
244 std::unique_ptr<base::Value>* value) {
245 if (!session->chrome->HasTouchScreen()) {
246 // TODO(samuong): remove this once we stop supporting M44.
247 return Status(kUnknownCommand, "Double tap command requires Chrome 44+");
248 }
249 WebPoint location;
250 Status status = GetElementClickableLocation(
251 session, web_view, element_id, &location);
252 if (status.IsError())
253 return status;
254 return web_view->SynthesizeTapGesture(location.x, location.y, 2, false);
255 }
256
ExecuteTouchLongPress(Session * session,WebView * web_view,const std::string & element_id,const base::DictionaryValue & params,std::unique_ptr<base::Value> * value)257 Status ExecuteTouchLongPress(Session* session,
258 WebView* web_view,
259 const std::string& element_id,
260 const base::DictionaryValue& params,
261 std::unique_ptr<base::Value>* value) {
262 if (!session->chrome->HasTouchScreen()) {
263 // TODO(samuong): remove this once we stop supporting M44.
264 return Status(kUnknownCommand, "Long press command requires Chrome 44+");
265 }
266 WebPoint location;
267 Status status = GetElementClickableLocation(
268 session, web_view, element_id, &location);
269 if (status.IsError())
270 return status;
271 return web_view->SynthesizeTapGesture(location.x, location.y, 1, true);
272 }
273
ExecuteFlick(Session * session,WebView * web_view,const std::string & element_id,const base::DictionaryValue & params,std::unique_ptr<base::Value> * value)274 Status ExecuteFlick(Session* session,
275 WebView* web_view,
276 const std::string& element_id,
277 const base::DictionaryValue& params,
278 std::unique_ptr<base::Value>* value) {
279 WebPoint location;
280 Status status = GetElementClickableLocation(
281 session, web_view, element_id, &location);
282 if (status.IsError())
283 return status;
284
285 int xoffset, yoffset, speed;
286 if (!params.GetInteger("xoffset", &xoffset))
287 return Status(kInvalidArgument, "'xoffset' must be an integer");
288 if (!params.GetInteger("yoffset", &yoffset))
289 return Status(kInvalidArgument, "'yoffset' must be an integer");
290 if (!params.GetInteger("speed", &speed))
291 return Status(kInvalidArgument, "'speed' must be an integer");
292 if (speed < 1)
293 return Status(kInvalidArgument, "'speed' must be a positive integer");
294
295 status = web_view->DispatchTouchEvent(
296 TouchEvent(kTouchStart, location.x, location.y), false);
297 if (status.IsError())
298 return status;
299
300 const double offset =
301 std::sqrt(static_cast<double>(xoffset * xoffset + yoffset * yoffset));
302 const double xoffset_per_event =
303 (speed * xoffset) / (kFlickTouchEventsPerSecond * offset);
304 const double yoffset_per_event =
305 (speed * yoffset) / (kFlickTouchEventsPerSecond * offset);
306 const int total_events =
307 (offset * kFlickTouchEventsPerSecond) / speed;
308 for (int i = 0; i < total_events; i++) {
309 status = web_view->DispatchTouchEvent(
310 TouchEvent(kTouchMove, location.x + xoffset_per_event * i,
311 location.y + yoffset_per_event * i),
312 false);
313 if (status.IsError())
314 return status;
315 base::PlatformThread::Sleep(
316 base::TimeDelta::FromMilliseconds(1000 / kFlickTouchEventsPerSecond));
317 }
318 return web_view->DispatchTouchEvent(
319 TouchEvent(kTouchEnd, location.x + xoffset, location.y + yoffset), false);
320 }
321
ExecuteClearElement(Session * session,WebView * web_view,const std::string & element_id,const base::DictionaryValue & params,std::unique_ptr<base::Value> * value)322 Status ExecuteClearElement(Session* session,
323 WebView* web_view,
324 const std::string& element_id,
325 const base::DictionaryValue& params,
326 std::unique_ptr<base::Value>* value) {
327 Status status = CheckElement(element_id);
328 if (status.IsError())
329 return status;
330
331 std::string tag_name;
332 status = GetElementTagName(session, web_view, element_id, &tag_name);
333 if (status.IsError())
334 return status;
335 std::string element_type;
336 bool is_input_control = false;
337
338 if (tag_name == "input") {
339 std::unique_ptr<base::Value> get_element_type;
340 status = GetElementAttribute(session, web_view, element_id, "type",
341 &get_element_type);
342 if (status.IsError())
343 return status;
344 if (get_element_type->GetAsString(&element_type))
345 element_type = base::ToLowerASCII(element_type);
346
347 is_input_control =
348 inputControlTypes.find(element_type) != inputControlTypes.end();
349 }
350
351 bool is_text = tag_name == "textarea";
352 bool is_content_editable = false;
353 if (!is_text && !is_input_control) {
354 std::unique_ptr<base::Value> get_content_editable;
355 base::ListValue args;
356 args.Append(CreateElement(element_id));
357 status = web_view->CallFunction(session->GetCurrentFrameId(),
358 "element => element.isContentEditable",
359 args, &get_content_editable);
360 if (status.IsError())
361 return status;
362 get_content_editable->GetAsBoolean(&is_content_editable);
363 }
364
365 std::unique_ptr<base::Value> get_readonly;
366 bool is_readonly = false;
367 base::DictionaryValue params_readOnly;
368 if (!is_content_editable) {
369 params_readOnly.SetString("name", "readOnly");
370 status = ExecuteGetElementProperty(session, web_view, element_id,
371 params_readOnly, &get_readonly);
372 get_readonly->GetAsBoolean(&is_readonly);
373 if (status.IsError())
374 return status;
375 }
376 bool is_editable =
377 (is_input_control || is_text || is_content_editable) && !is_readonly;
378 if (!is_editable)
379 return Status(kInvalidElementState);
380 // Scrolling to element is done by webdriver::atoms::CLEAR
381 bool is_displayed = false;
382 base::TimeTicks start_time = base::TimeTicks::Now();
383 while (true) {
384 status = IsElementDisplayed(
385 session, web_view, element_id, true, &is_displayed);
386 if (status.IsError())
387 return status;
388 if (is_displayed)
389 break;
390 if (base::TimeTicks::Now() - start_time >= session->implicit_wait) {
391 return Status(kElementNotVisible);
392 }
393 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50));
394 }
395 static bool isClearWarningNotified = false;
396 if (!isClearWarningNotified) {
397 VLOG(0) << "\n\t=== NOTE: ===\n"
398 << "\tThe Clear command in " << kChromeDriverProductShortName
399 << " 2.43 and above\n"
400 << "\thas been updated to conform to the current standard,\n"
401 << "\tincluding raising blur event after clearing.\n";
402 isClearWarningNotified = true;
403 }
404 base::ListValue args;
405 args.Append(CreateElement(element_id));
406 std::unique_ptr<base::Value> result;
407 return web_view->CallFunction(
408 session->GetCurrentFrameId(),
409 webdriver::atoms::asString(webdriver::atoms::CLEAR),
410 args, &result);
411 }
412
ExecuteSendKeysToElement(Session * session,WebView * web_view,const std::string & element_id,const base::DictionaryValue & params,std::unique_ptr<base::Value> * value)413 Status ExecuteSendKeysToElement(Session* session,
414 WebView* web_view,
415 const std::string& element_id,
416 const base::DictionaryValue& params,
417 std::unique_ptr<base::Value>* value) {
418 Status status = CheckElement(element_id);
419 if (status.IsError())
420 return status;
421 const base::ListValue* key_list;
422 base::ListValue key_list_local;
423 const base::Value* text = nullptr;
424 if (session->w3c_compliant) {
425 if (!params.Get("text", &text) || !text->is_string())
426 return Status(kInvalidArgument, "'text' must be a string");
427 key_list_local.Set(0, std::make_unique<base::Value>(text->Clone()));
428 key_list = &key_list_local;
429 } else {
430 if (!params.GetList("value", &key_list))
431 return Status(kInvalidArgument, "'value' must be a list");
432 }
433
434 bool is_input = false;
435 status = IsElementAttributeEqualToIgnoreCase(session, web_view, element_id,
436 "tagName", "input", &is_input);
437 if (status.IsError())
438 return status;
439 std::unique_ptr<base::Value> get_element_type;
440 status = GetElementAttribute(session, web_view, element_id, "type",
441 &get_element_type);
442 if (status.IsError())
443 return status;
444 std::string element_type;
445 if (get_element_type->GetAsString(&element_type))
446 element_type = base::ToLowerASCII(element_type);
447 bool is_file = element_type == "file";
448 bool is_nontypeable = nontypeableControlTypes.find(element_type) !=
449 nontypeableControlTypes.end();
450
451 if (is_input && is_file) {
452 if (session->strict_file_interactability) {
453 status = FocusToElement(session, web_view,element_id);
454 if (status.IsError())
455 return status;
456 }
457 // Compress array into a single string.
458 base::FilePath::StringType paths_string;
459 for (size_t i = 0; i < key_list->GetSize(); ++i) {
460 base::FilePath::StringType path_part;
461 if (!key_list->GetString(i, &path_part))
462 return Status(kInvalidArgument, "'value' is invalid");
463 paths_string.append(path_part);
464 }
465
466 // w3c spec specifies empty path_part should throw invalidArgument error
467 if (paths_string.empty())
468 return Status(kInvalidArgument, "'text' is empty");
469
470 ChromeDesktopImpl* chrome_desktop = nullptr;
471 bool is_desktop = session->chrome->GetAsDesktop(&chrome_desktop).IsOk();
472
473 // Separate the string into separate paths, delimited by '\n'.
474 std::vector<base::FilePath> paths;
475 for (const auto& path_piece : base::SplitStringPiece(
476 paths_string, base::FilePath::StringType(1, '\n'),
477 base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
478 // For local desktop browser, verify that the file exists.
479 // No easy way to do that for remote or mobile browser.
480 if (is_desktop && !base::PathExists(base::FilePath(path_piece))) {
481 return Status(
482 kInvalidArgument,
483 base::StringPrintf("File not found : %" PRFilePath,
484 base::FilePath(path_piece).value().c_str()));
485 }
486 paths.push_back(base::FilePath(path_piece));
487 }
488
489 bool multiple = false;
490 status = IsElementAttributeEqualToIgnoreCase(
491 session, web_view, element_id, "multiple", "true", &multiple);
492 if (status.IsError())
493 return status;
494 if (!multiple && paths.size() > 1)
495 return Status(kInvalidArgument,
496 "the element can not hold multiple files");
497
498 std::unique_ptr<base::DictionaryValue> element(CreateElement(element_id));
499 return web_view->SetFileInputFiles(session->GetCurrentFrameId(), *element,
500 paths, multiple);
501 } else if (session->w3c_compliant && is_input && is_nontypeable) {
502 // Special handling for non-typeable inputs is only included in W3C Spec
503 // The Spec calls for returning element not interactable if the element
504 // has no value property, but this is included for all input elements, so
505 // no check is needed here.
506
507 // text is set only when session.w3c_compliant, so confirm here
508 DCHECK(text != nullptr);
509 base::ListValue args;
510 args.Append(CreateElement(element_id));
511 args.AppendString(text->GetString());
512 std::unique_ptr<base::Value> result;
513 // Set value to text as given by user; if this does not match the defined
514 // format for the input type, results are not defined
515 return web_view->CallFunction(session->GetCurrentFrameId(),
516 "(element, text) => element.value = text",
517 args, &result);
518 } else {
519 std::unique_ptr<base::Value> get_content_editable;
520 base::ListValue args;
521 args.Append(CreateElement(element_id));
522 status = web_view->CallFunction(session->GetCurrentFrameId(),
523 "element => element.isContentEditable",
524 args, &get_content_editable);
525 if (status.IsError())
526 return status;
527
528 // If element_type is in textControlTypes, sendKeys should append
529 bool is_textControlType = is_input && textControlTypes.find(element_type) !=
530 textControlTypes.end();
531 // If the element is a textarea, sendKeys should also append
532 bool is_textarea = false;
533 status = IsElementAttributeEqualToIgnoreCase(
534 session, web_view, element_id, "tagName", "textarea", &is_textarea);
535 if (status.IsError())
536 return status;
537 bool is_text = is_textControlType || is_textarea;
538
539 bool is_content_editable;
540 if (get_content_editable->GetAsBoolean(&is_content_editable) &&
541 is_content_editable) {
542 // If element is contentEditable
543 // check if element is focused
544 bool is_focused = false;
545 status = IsElementFocused(session, web_view, element_id, &is_focused);
546 if (status.IsError())
547 return status;
548
549 // Get top level contentEditable element
550 std::unique_ptr<base::Value> result;
551 status = web_view->CallFunction(
552 session->GetCurrentFrameId(),
553 "function(element) {"
554 "while (element.parentElement && "
555 "element.parentElement.isContentEditable) {"
556 " element = element.parentElement;"
557 " }"
558 "return element;"
559 "}",
560 args, &result);
561 if (status.IsError())
562 return status;
563 const base::DictionaryValue* element_dict;
564 std::string top_element_id;
565 if (!result->GetAsDictionary(&element_dict) ||
566 !element_dict->GetString(GetElementKey(), &top_element_id))
567 return Status(kUnknownError, "no element reference returned by script");
568
569 // check if top level contentEditable element is focused
570 bool is_top_focused = false;
571 status =
572 IsElementFocused(session, web_view, top_element_id, &is_top_focused);
573 if (status.IsError())
574 return status;
575 // If is_text we want to send keys to the element
576 // Otherwise, send keys to the top element
577 if ((is_text && !is_focused) || (!is_text && !is_top_focused)) {
578 // If element does not currentley have focus
579 // will move caret
580 // at end of element text. W3C mandates that the
581 // caret be moved "after any child content"
582 // Set selection using the element itself
583 std::unique_ptr<base::Value> unused;
584 status = web_view->CallFunction(session->GetCurrentFrameId(),
585 "function(element) {"
586 "var range = document.createRange();"
587 "range.selectNodeContents(element);"
588 "range.collapse();"
589 "var sel = window.getSelection();"
590 "sel.removeAllRanges();"
591 "sel.addRange(range);"
592 "}",
593 args, &unused);
594 if (status.IsError())
595 return status;
596 }
597 // Use top level element id for the purpose of focusing
598 if (!is_text)
599 return SendKeysToElement(session, web_view, top_element_id, is_text,
600 key_list);
601 }
602 return SendKeysToElement(session, web_view, element_id, is_text, key_list);
603 }
604 }
605
ExecuteSubmitElement(Session * session,WebView * web_view,const std::string & element_id,const base::DictionaryValue & params,std::unique_ptr<base::Value> * value)606 Status ExecuteSubmitElement(Session* session,
607 WebView* web_view,
608 const std::string& element_id,
609 const base::DictionaryValue& params,
610 std::unique_ptr<base::Value>* value) {
611 Status status = CheckElement(element_id);
612 if (status.IsError())
613 return status;
614 base::ListValue args;
615 args.Append(CreateElement(element_id));
616 return web_view->CallFunction(
617 session->GetCurrentFrameId(),
618 webdriver::atoms::asString(webdriver::atoms::SUBMIT),
619 args,
620 value);
621 }
622
ExecuteGetElementText(Session * session,WebView * web_view,const std::string & element_id,const base::DictionaryValue & params,std::unique_ptr<base::Value> * value)623 Status ExecuteGetElementText(Session* session,
624 WebView* web_view,
625 const std::string& element_id,
626 const base::DictionaryValue& params,
627 std::unique_ptr<base::Value>* value) {
628 Status status = CheckElement(element_id);
629 if (status.IsError())
630 return status;
631 base::ListValue args;
632 args.Append(CreateElement(element_id));
633 return web_view->CallFunction(
634 session->GetCurrentFrameId(),
635 webdriver::atoms::asString(webdriver::atoms::GET_TEXT),
636 args,
637 value);
638 }
639
ExecuteGetElementValue(Session * session,WebView * web_view,const std::string & element_id,const base::DictionaryValue & params,std::unique_ptr<base::Value> * value)640 Status ExecuteGetElementValue(Session* session,
641 WebView* web_view,
642 const std::string& element_id,
643 const base::DictionaryValue& params,
644 std::unique_ptr<base::Value>* value) {
645 Status status = CheckElement(element_id);
646 if (status.IsError())
647 return status;
648 base::ListValue args;
649 args.Append(CreateElement(element_id));
650 return web_view->CallFunction(
651 session->GetCurrentFrameId(),
652 "function(elem) { return elem['value'] }",
653 args,
654 value);
655 }
656
ExecuteGetElementProperty(Session * session,WebView * web_view,const std::string & element_id,const base::DictionaryValue & params,std::unique_ptr<base::Value> * value)657 Status ExecuteGetElementProperty(Session* session,
658 WebView* web_view,
659 const std::string& element_id,
660 const base::DictionaryValue& params,
661 std::unique_ptr<base::Value>* value) {
662 Status status = CheckElement(element_id);
663 if (status.IsError())
664 return status;
665 base::ListValue args;
666 args.Append(CreateElement(element_id));
667
668 std::string name;
669 if (!params.GetString("name", &name))
670 return Status(kInvalidArgument, "missing 'name'");
671 args.AppendString(name);
672
673 return web_view->CallFunction(
674 session->GetCurrentFrameId(),
675 "function(elem, name) { return elem[name] }",
676 args,
677 value);
678 }
679
ExecuteGetElementTagName(Session * session,WebView * web_view,const std::string & element_id,const base::DictionaryValue & params,std::unique_ptr<base::Value> * value)680 Status ExecuteGetElementTagName(Session* session,
681 WebView* web_view,
682 const std::string& element_id,
683 const base::DictionaryValue& params,
684 std::unique_ptr<base::Value>* value) {
685 Status status = CheckElement(element_id);
686 if (status.IsError())
687 return status;
688 base::ListValue args;
689 args.Append(CreateElement(element_id));
690 return web_view->CallFunction(
691 session->GetCurrentFrameId(),
692 "function(elem) { return elem.tagName.toLowerCase() }",
693 args,
694 value);
695 }
696
ExecuteIsElementSelected(Session * session,WebView * web_view,const std::string & element_id,const base::DictionaryValue & params,std::unique_ptr<base::Value> * value)697 Status ExecuteIsElementSelected(Session* session,
698 WebView* web_view,
699 const std::string& element_id,
700 const base::DictionaryValue& params,
701 std::unique_ptr<base::Value>* value) {
702 Status status = CheckElement(element_id);
703 if (status.IsError())
704 return status;
705 base::ListValue args;
706 args.Append(CreateElement(element_id));
707 return web_view->CallFunction(
708 session->GetCurrentFrameId(),
709 webdriver::atoms::asString(webdriver::atoms::IS_SELECTED),
710 args,
711 value);
712 }
713
ExecuteIsElementEnabled(Session * session,WebView * web_view,const std::string & element_id,const base::DictionaryValue & params,std::unique_ptr<base::Value> * value)714 Status ExecuteIsElementEnabled(Session* session,
715 WebView* web_view,
716 const std::string& element_id,
717 const base::DictionaryValue& params,
718 std::unique_ptr<base::Value>* value) {
719 Status status = CheckElement(element_id);
720 if (status.IsError())
721 return status;
722 base::ListValue args;
723 args.Append(CreateElement(element_id));
724
725 bool is_xml = false;
726 status = IsDocumentTypeXml(session, web_view, &is_xml);
727 if (status.IsError())
728 return status;
729
730 if (is_xml) {
731 value->reset(new base::Value(false));
732 return Status(kOk);
733 } else {
734 return web_view->CallFunction(
735 session->GetCurrentFrameId(),
736 webdriver::atoms::asString(webdriver::atoms::IS_ENABLED),
737 args,
738 value);
739 }
740 }
741
ExecuteGetComputedLabel(Session * session,WebView * web_view,const std::string & element_id,const base::DictionaryValue & params,std::unique_ptr<base::Value> * value)742 Status ExecuteGetComputedLabel(Session* session,
743 WebView* web_view,
744 const std::string& element_id,
745 const base::DictionaryValue& params,
746 std::unique_ptr<base::Value>* value) {
747 std::unique_ptr<base::Value> axNode;
748 Status status = GetAXNodeByElementId(session, web_view, element_id, &axNode);
749 if (status.IsError())
750 return status;
751
752 // Computed label stores as `name` in the AXTree.
753 base::Optional<base::Value> nameNode = axNode->ExtractKey("name");
754 if (!nameNode) {
755 // No computed label found. Return empty string.
756 *value = std::make_unique<base::Value>("");
757 return Status(kOk);
758 }
759
760 base::Optional<base::Value> nameVal = nameNode->ExtractKey("value");
761 if (!nameVal)
762 return Status(kUnknownError,
763 "No name value found in the node in CDP response");
764
765 *value = std::make_unique<base::Value>(std::move(*nameVal));
766
767 return Status(kOk);
768 }
769
ExecuteGetComputedRole(Session * session,WebView * web_view,const std::string & element_id,const base::DictionaryValue & params,std::unique_ptr<base::Value> * value)770 Status ExecuteGetComputedRole(Session* session,
771 WebView* web_view,
772 const std::string& element_id,
773 const base::DictionaryValue& params,
774 std::unique_ptr<base::Value>* value) {
775 std::unique_ptr<base::Value> axNode;
776 Status status = GetAXNodeByElementId(session, web_view, element_id, &axNode);
777 if (status.IsError())
778 return status;
779
780 base::Optional<base::Value> roleNode = axNode->ExtractKey("role");
781 if (!roleNode) {
782 // No computed role found. Return empty string.
783 *value = std::make_unique<base::Value>("");
784 return Status(kOk);
785 }
786
787 base::Optional<base::Value> roleVal = roleNode->ExtractKey("value");
788 if (!roleVal)
789 return Status(kUnknownError,
790 "No role value found in the node in CDP response");
791
792 *value = std::make_unique<base::Value>(std::move(*roleVal));
793
794 return Status(kOk);
795 }
796
ExecuteIsElementDisplayed(Session * session,WebView * web_view,const std::string & element_id,const base::DictionaryValue & params,std::unique_ptr<base::Value> * value)797 Status ExecuteIsElementDisplayed(Session* session,
798 WebView* web_view,
799 const std::string& element_id,
800 const base::DictionaryValue& params,
801 std::unique_ptr<base::Value>* value) {
802 Status status = CheckElement(element_id);
803 if (status.IsError())
804 return status;
805 base::ListValue args;
806 args.Append(CreateElement(element_id));
807 return web_view->CallFunction(
808 session->GetCurrentFrameId(),
809 webdriver::atoms::asString(webdriver::atoms::IS_DISPLAYED),
810 args,
811 value);
812 }
813
ExecuteGetElementLocation(Session * session,WebView * web_view,const std::string & element_id,const base::DictionaryValue & params,std::unique_ptr<base::Value> * value)814 Status ExecuteGetElementLocation(Session* session,
815 WebView* web_view,
816 const std::string& element_id,
817 const base::DictionaryValue& params,
818 std::unique_ptr<base::Value>* value) {
819 Status status = CheckElement(element_id);
820 if (status.IsError())
821 return status;
822 base::ListValue args;
823 args.Append(CreateElement(element_id));
824 return web_view->CallFunction(
825 session->GetCurrentFrameId(),
826 webdriver::atoms::asString(webdriver::atoms::GET_LOCATION),
827 args,
828 value);
829 }
830
ExecuteGetElementRect(Session * session,WebView * web_view,const std::string & element_id,const base::DictionaryValue & params,std::unique_ptr<base::Value> * value)831 Status ExecuteGetElementRect(Session* session,
832 WebView* web_view,
833 const std::string& element_id,
834 const base::DictionaryValue& params,
835 std::unique_ptr<base::Value>* value) {
836 Status status = CheckElement(element_id);
837 if (status.IsError())
838 return status;
839 base::ListValue args;
840 args.Append(CreateElement(element_id));
841
842 std::unique_ptr<base::Value> location;
843 status = web_view->CallFunction(
844 session->GetCurrentFrameId(),
845 webdriver::atoms::asString(webdriver::atoms::GET_LOCATION), args,
846 &location);
847 if (status.IsError())
848 return status;
849
850 std::unique_ptr<base::Value> size;
851 status = web_view->CallFunction(
852 session->GetCurrentFrameId(),
853 webdriver::atoms::asString(webdriver::atoms::GET_SIZE), args, &size);
854 if (status.IsError())
855 return status;
856
857 // do type conversions
858 base::DictionaryValue* size_dict;
859 if (!size->GetAsDictionary(&size_dict))
860 return Status(kUnknownError, "could not convert to DictionaryValue");
861 base::DictionaryValue* location_dict;
862 if (!location->GetAsDictionary(&location_dict))
863 return Status(kUnknownError, "could not convert to DictionaryValue");
864
865 // grab values
866 double x, y, width, height;
867 if (!location_dict->GetDouble("x", &x))
868 return Status(kUnknownError, "x coordinate is missing in element location");
869
870 if (!location_dict->GetDouble("y", &y))
871 return Status(kUnknownError, "y coordinate is missing in element location");
872
873 if (!size_dict->GetDouble("height", &height))
874 return Status(kUnknownError, "height is missing in element size");
875
876 if (!size_dict->GetDouble("width", &width))
877 return Status(kUnknownError, "width is missing in element size");
878
879 base::DictionaryValue ret;
880 ret.SetDouble("x", x);
881 ret.SetDouble("y", y);
882 ret.SetDouble("width", width);
883 ret.SetDouble("height", height);
884 value->reset(ret.DeepCopy());
885 return Status(kOk);
886 }
887
ExecuteGetElementLocationOnceScrolledIntoView(Session * session,WebView * web_view,const std::string & element_id,const base::DictionaryValue & params,std::unique_ptr<base::Value> * value)888 Status ExecuteGetElementLocationOnceScrolledIntoView(
889 Session* session,
890 WebView* web_view,
891 const std::string& element_id,
892 const base::DictionaryValue& params,
893 std::unique_ptr<base::Value>* value) {
894 WebPoint offset(0, 0);
895 WebPoint location;
896 Status status = ScrollElementIntoView(
897 session, web_view, element_id, &offset, &location);
898 if (status.IsError())
899 return status;
900 *value = CreateValueFrom(location);
901 return Status(kOk);
902 }
903
ExecuteGetElementSize(Session * session,WebView * web_view,const std::string & element_id,const base::DictionaryValue & params,std::unique_ptr<base::Value> * value)904 Status ExecuteGetElementSize(Session* session,
905 WebView* web_view,
906 const std::string& element_id,
907 const base::DictionaryValue& params,
908 std::unique_ptr<base::Value>* value) {
909 Status status = CheckElement(element_id);
910 if (status.IsError())
911 return status;
912 base::ListValue args;
913 args.Append(CreateElement(element_id));
914 return web_view->CallFunction(
915 session->GetCurrentFrameId(),
916 webdriver::atoms::asString(webdriver::atoms::GET_SIZE),
917 args,
918 value);
919 }
920
ExecuteGetElementAttribute(Session * session,WebView * web_view,const std::string & element_id,const base::DictionaryValue & params,std::unique_ptr<base::Value> * value)921 Status ExecuteGetElementAttribute(Session* session,
922 WebView* web_view,
923 const std::string& element_id,
924 const base::DictionaryValue& params,
925 std::unique_ptr<base::Value>* value) {
926 std::string name;
927 if (!params.GetString("name", &name))
928 return Status(kInvalidArgument, "missing 'name'");
929 return GetElementAttribute(session, web_view, element_id, name, value);
930 }
931
ExecuteGetElementValueOfCSSProperty(Session * session,WebView * web_view,const std::string & element_id,const base::DictionaryValue & params,std::unique_ptr<base::Value> * value)932 Status ExecuteGetElementValueOfCSSProperty(
933 Session* session,
934 WebView* web_view,
935 const std::string& element_id,
936 const base::DictionaryValue& params,
937 std::unique_ptr<base::Value>* value) {
938 bool is_xml = false;
939 Status status = IsDocumentTypeXml(session, web_view, &is_xml);
940 if (status.IsError())
941 return status;
942
943 if (is_xml) {
944 value->reset(new base::Value(""));
945 } else {
946 std::string property_name;
947 if (!params.GetString("propertyName", &property_name))
948 return Status(kInvalidArgument, "missing 'propertyName'");
949 std::string property_value;
950 status = GetElementEffectiveStyle(
951 session, web_view, element_id, property_name, &property_value);
952 if (status.IsError())
953 return status;
954 value->reset(new base::Value(property_value));
955 }
956 return Status(kOk);
957 }
958
ExecuteElementEquals(Session * session,WebView * web_view,const std::string & element_id,const base::DictionaryValue & params,std::unique_ptr<base::Value> * value)959 Status ExecuteElementEquals(Session* session,
960 WebView* web_view,
961 const std::string& element_id,
962 const base::DictionaryValue& params,
963 std::unique_ptr<base::Value>* value) {
964 std::string other_element_id;
965 if (!params.GetString("other", &other_element_id))
966 return Status(kInvalidArgument, "'other' must be a string");
967 value->reset(new base::Value(element_id == other_element_id));
968 return Status(kOk);
969 }
970
ExecuteElementScreenshot(Session * session,WebView * web_view,const std::string & element_id,const base::DictionaryValue & params,std::unique_ptr<base::Value> * value)971 Status ExecuteElementScreenshot(Session* session,
972 WebView* web_view,
973 const std::string& element_id,
974 const base::DictionaryValue& params,
975 std::unique_ptr<base::Value>* value) {
976 Status status = session->chrome->ActivateWebView(web_view->GetId());
977 if (status.IsError())
978 return status;
979
980 WebPoint offset(0, 0);
981 WebPoint location;
982 status =
983 ScrollElementIntoView(session, web_view, element_id, &offset, &location);
984 if (status.IsError())
985 return status;
986
987 std::unique_ptr<base::Value> clip;
988 status = ExecuteGetElementRect(session, web_view, element_id, params, &clip);
989 if (status.IsError())
990 return status;
991
992 // |location| returned by ScrollElementIntoView is relative to the current
993 // view port. However, CaptureScreenshot expects a location relative to the
994 // document origin. We make the adjustment using the scroll amount of the top
995 // level window. Scrolling of frames has already been included in |location|.
996 // Use window.pageXOffset and widnow.pageYOffset for scroll information,
997 // should always return scroll amount regardless of doctype. The parentheses
998 // around the JavaScript code below is needed because JavaScript syntax
999 // doesn't allow a statement to start with an object literal.
1000 // document.documentElement.clientHeight and Width provide viewport height
1001 // and width to crop screenshot if necessary.
1002 std::unique_ptr<base::Value> browser_info;
1003 status = web_view->EvaluateScript(
1004 std::string(),
1005 "({x: window.pageXOffset,"
1006 " y: window.pageYOffset,"
1007 " height: document.documentElement.clientHeight,"
1008 " width: document.documentElement.clientWidth,"
1009 " device_pixel_ratio: window.devicePixelRatio})",
1010 false, &browser_info);
1011 if (status.IsError())
1012 return status;
1013
1014 double scroll_left = browser_info->FindKey("x")->GetDouble();
1015 double scroll_top = browser_info->FindKey("y")->GetDouble();
1016 double viewport_height = browser_info->FindKey("height")->GetDouble();
1017 double viewport_width = browser_info->FindKey("width")->GetDouble();
1018 double device_pixel_ratio =
1019 browser_info->FindKey("device_pixel_ratio")->GetDouble();
1020
1021 std::unique_ptr<base::DictionaryValue> clip_dict =
1022 base::DictionaryValue::From(std::move(clip));
1023 if (!clip_dict)
1024 return Status(kUnknownError, "Element Rect is not a dictionary");
1025 // |clip_dict| already contains the right width and height of the target
1026 // element, but its x and y are relative to containing frame. We replace them
1027 // with the x and y relative to top-level document origin, as expected by
1028 // CaptureScreenshot.
1029 clip_dict->SetDouble("x", location.x + scroll_left);
1030 clip_dict->SetDouble("y", location.y + scroll_top);
1031 clip_dict->SetDouble("scale", 1 / device_pixel_ratio);
1032 // Crop screenshot by viewport if element is larger than viewport
1033 clip_dict->SetDouble(
1034 "height",
1035 std::min(viewport_height, clip_dict->FindKey("height")->GetDouble()));
1036 clip_dict->SetDouble(
1037 "width",
1038 std::min(viewport_width, clip_dict->FindKey("width")->GetDouble()));
1039 base::DictionaryValue screenshot_params;
1040 screenshot_params.SetDictionary("clip", std::move(clip_dict));
1041
1042 std::string screenshot;
1043 status = web_view->CaptureScreenshot(&screenshot, screenshot_params);
1044 if (status.IsError())
1045 return status;
1046
1047 value->reset(new base::Value(screenshot));
1048 return Status(kOk);
1049 }
1050