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