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 <cmath>
8#include <list>
9#include <vector>
10
11#include "base/callback.h"
12#include "base/files/file_path.h"
13#include "base/strings/string_split.h"
14#include "base/strings/stringprintf.h"
15#include "base/threading/platform_thread.h"
16#include "base/time/time.h"
17#include "base/values.h"
18#include "chrome/test/chromedriver/basic_types.h"
19#include "chrome/test/chromedriver/chrome/browser_info.h"
20#include "chrome/test/chromedriver/chrome/chrome.h"
21#include "chrome/test/chromedriver/chrome/js.h"
22#include "chrome/test/chromedriver/chrome/status.h"
23#include "chrome/test/chromedriver/chrome/ui_events.h"
24#include "chrome/test/chromedriver/chrome/web_view.h"
25#include "chrome/test/chromedriver/element_util.h"
26#include "chrome/test/chromedriver/session.h"
27#include "chrome/test/chromedriver/util.h"
28#include "third_party/webdriver/atoms.h"
29
30const int kFlickTouchEventsPerSecond = 30;
31
32namespace {
33
34Status SendKeysToElement(
35    Session* session,
36    WebView* web_view,
37    const std::string& element_id,
38    const base::ListValue* key_list) {
39  bool is_displayed = false;
40  bool is_focused = false;
41  base::TimeTicks start_time = base::TimeTicks::Now();
42  while (true) {
43    Status status = IsElementDisplayed(
44        session, web_view, element_id, true, &is_displayed);
45    if (status.IsError())
46      return status;
47    if (is_displayed)
48      break;
49    status = IsElementFocused(session, web_view, element_id, &is_focused);
50    if (status.IsError())
51      return status;
52    if (is_focused)
53      break;
54    if (base::TimeTicks::Now() - start_time >= session->implicit_wait) {
55      return Status(kElementNotVisible);
56    }
57    base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
58  }
59
60  bool is_enabled = false;
61  Status status = IsElementEnabled(session, web_view, element_id, &is_enabled);
62  if (status.IsError())
63    return status;
64  if (!is_enabled)
65    return Status(kInvalidElementState);
66
67  if (!is_focused) {
68    base::ListValue args;
69    args.Append(CreateElement(element_id));
70    scoped_ptr<base::Value> result;
71    status = web_view->CallFunction(
72        session->GetCurrentFrameId(), kFocusScript, args, &result);
73    if (status.IsError())
74      return status;
75  }
76
77  return SendKeysOnWindow(web_view, key_list, true, &session->sticky_modifiers);
78}
79
80Status ExecuteTouchSingleTapAtom(
81    Session* session,
82    WebView* web_view,
83    const std::string& element_id,
84    const base::DictionaryValue& params,
85    scoped_ptr<base::Value>* value) {
86  base::ListValue args;
87  args.Append(CreateElement(element_id));
88  return web_view->CallFunction(
89      session->GetCurrentFrameId(),
90      webdriver::atoms::asString(webdriver::atoms::TOUCH_SINGLE_TAP),
91      args,
92      value);
93}
94
95}  // namespace
96
97Status ExecuteElementCommand(
98    const ElementCommand& command,
99    Session* session,
100    WebView* web_view,
101    const base::DictionaryValue& params,
102    scoped_ptr<base::Value>* value) {
103  std::string id;
104  if (params.GetString("id", &id) || params.GetString("element", &id))
105    return command.Run(session, web_view, id, params, value);
106  return Status(kUnknownError, "element identifier must be a string");
107}
108
109Status ExecuteFindChildElement(
110    int interval_ms,
111    Session* session,
112    WebView* web_view,
113    const std::string& element_id,
114    const base::DictionaryValue& params,
115    scoped_ptr<base::Value>* value) {
116  return FindElement(
117      interval_ms, true, &element_id, session, web_view, params, value);
118}
119
120Status ExecuteFindChildElements(
121    int interval_ms,
122    Session* session,
123    WebView* web_view,
124    const std::string& element_id,
125    const base::DictionaryValue& params,
126    scoped_ptr<base::Value>* value) {
127  return FindElement(
128      interval_ms, false, &element_id, session, web_view, params, value);
129}
130
131Status ExecuteHoverOverElement(
132    Session* session,
133    WebView* web_view,
134    const std::string& element_id,
135    const base::DictionaryValue& params,
136    scoped_ptr<base::Value>* value) {
137  WebPoint location;
138  Status status = GetElementClickableLocation(
139      session, web_view, element_id, &location);
140  if (status.IsError())
141    return status;
142
143  MouseEvent move_event(
144      kMovedMouseEventType, kNoneMouseButton, location.x, location.y,
145      session->sticky_modifiers, 0);
146  std::list<MouseEvent> events;
147  events.push_back(move_event);
148  status = web_view->DispatchMouseEvents(events, session->GetCurrentFrameId());
149  if (status.IsOk())
150    session->mouse_position = location;
151  return status;
152}
153
154Status ExecuteClickElement(
155    Session* session,
156    WebView* web_view,
157    const std::string& element_id,
158    const base::DictionaryValue& params,
159    scoped_ptr<base::Value>* value) {
160  std::string tag_name;
161  Status status = GetElementTagName(session, web_view, element_id, &tag_name);
162  if (status.IsError())
163    return status;
164  if (tag_name == "option") {
165    bool is_toggleable;
166    status = IsOptionElementTogglable(
167        session, web_view, element_id, &is_toggleable);
168    if (status.IsError())
169      return status;
170    if (is_toggleable)
171      return ToggleOptionElement(session, web_view, element_id);
172    else
173      return SetOptionElementSelected(session, web_view, element_id, true);
174  } else {
175    WebPoint location;
176    status = GetElementClickableLocation(
177        session, web_view, element_id, &location);
178    if (status.IsError())
179      return status;
180
181    std::list<MouseEvent> events;
182    events.push_back(
183        MouseEvent(kMovedMouseEventType, kNoneMouseButton,
184                   location.x, location.y, session->sticky_modifiers, 0));
185    events.push_back(
186        MouseEvent(kPressedMouseEventType, kLeftMouseButton,
187                   location.x, location.y, session->sticky_modifiers, 1));
188    events.push_back(
189        MouseEvent(kReleasedMouseEventType, kLeftMouseButton,
190                   location.x, location.y, session->sticky_modifiers, 1));
191    status =
192        web_view->DispatchMouseEvents(events, session->GetCurrentFrameId());
193    if (status.IsOk())
194      session->mouse_position = location;
195    return status;
196  }
197}
198
199Status ExecuteTouchSingleTap(
200    Session* session,
201    WebView* web_view,
202    const std::string& element_id,
203    const base::DictionaryValue& params,
204    scoped_ptr<base::Value>* value) {
205  // Fall back to javascript atom for pre-m30 Chrome.
206  if (session->chrome->GetBrowserInfo()->build_no < 1576)
207    return ExecuteTouchSingleTapAtom(
208        session, web_view, element_id, params, value);
209
210  WebPoint location;
211  Status status = GetElementClickableLocation(
212      session, web_view, element_id, &location);
213  if (status.IsError())
214    return status;
215
216  std::list<TouchEvent> events;
217  events.push_back(
218      TouchEvent(kTouchStart, location.x, location.y));
219  events.push_back(
220      TouchEvent(kTouchEnd, location.x, location.y));
221  return web_view->DispatchTouchEvents(events);
222}
223
224Status ExecuteFlick(
225    Session* session,
226    WebView* web_view,
227    const std::string& element_id,
228    const base::DictionaryValue& params,
229    scoped_ptr<base::Value>* value) {
230  WebPoint location;
231  Status status = GetElementClickableLocation(
232      session, web_view, element_id, &location);
233  if (status.IsError())
234    return status;
235
236  int xoffset, yoffset, speed;
237  if (!params.GetInteger("xoffset", &xoffset))
238    return Status(kUnknownError, "'xoffset' must be an integer");
239  if (!params.GetInteger("yoffset", &yoffset))
240    return Status(kUnknownError, "'yoffset' must be an integer");
241  if (!params.GetInteger("speed", &speed))
242    return Status(kUnknownError, "'speed' must be an integer");
243  if (speed < 1)
244    return Status(kUnknownError, "'speed' must be a positive integer");
245
246  status = web_view->DispatchTouchEvent(
247      TouchEvent(kTouchStart, location.x, location.y));
248  if (status.IsError())
249    return status;
250
251  const double offset =
252      std::sqrt(static_cast<double>(xoffset * xoffset + yoffset * yoffset));
253  const double xoffset_per_event =
254      (speed * xoffset) / (kFlickTouchEventsPerSecond * offset);
255  const double yoffset_per_event =
256      (speed * yoffset) / (kFlickTouchEventsPerSecond * offset);
257  const int total_events =
258      (offset * kFlickTouchEventsPerSecond) / speed;
259  for (int i = 0; i < total_events; i++) {
260    status = web_view->DispatchTouchEvent(
261        TouchEvent(kTouchMove,
262                   location.x + xoffset_per_event * i,
263                   location.y + yoffset_per_event * i));
264    if (status.IsError())
265      return status;
266    base::PlatformThread::Sleep(
267        base::TimeDelta::FromMilliseconds(1000 / kFlickTouchEventsPerSecond));
268  }
269  return web_view->DispatchTouchEvent(
270      TouchEvent(kTouchEnd, location.x + xoffset, location.y + yoffset));
271}
272
273Status ExecuteClearElement(
274    Session* session,
275    WebView* web_view,
276    const std::string& element_id,
277    const base::DictionaryValue& params,
278    scoped_ptr<base::Value>* value) {
279  base::ListValue args;
280  args.Append(CreateElement(element_id));
281  scoped_ptr<base::Value> result;
282  return web_view->CallFunction(
283      session->GetCurrentFrameId(),
284      webdriver::atoms::asString(webdriver::atoms::CLEAR),
285      args, &result);
286}
287
288Status ExecuteSendKeysToElement(
289    Session* session,
290    WebView* web_view,
291    const std::string& element_id,
292    const base::DictionaryValue& params,
293    scoped_ptr<base::Value>* value) {
294  const base::ListValue* key_list;
295  if (!params.GetList("value", &key_list))
296    return Status(kUnknownError, "'value' must be a list");
297
298  bool is_input = false;
299  Status status = IsElementAttributeEqualToIgnoreCase(
300      session, web_view, element_id, "tagName", "input", &is_input);
301  if (status.IsError())
302    return status;
303  bool is_file = false;
304  status = IsElementAttributeEqualToIgnoreCase(
305      session, web_view, element_id, "type", "file", &is_file);
306  if (status.IsError())
307    return status;
308  if (is_input && is_file) {
309    // Compress array into a single string.
310    base::FilePath::StringType paths_string;
311    for (size_t i = 0; i < key_list->GetSize(); ++i) {
312      base::FilePath::StringType path_part;
313      if (!key_list->GetString(i, &path_part))
314        return Status(kUnknownError, "'value' is invalid");
315      paths_string.append(path_part);
316    }
317
318    // Separate the string into separate paths, delimited by '\n'.
319    std::vector<base::FilePath::StringType> path_strings;
320    base::SplitString(paths_string, '\n', &path_strings);
321    std::vector<base::FilePath> paths;
322    for (size_t i = 0; i < path_strings.size(); ++i)
323      paths.push_back(base::FilePath(path_strings[i]));
324
325    bool multiple = false;
326    status = IsElementAttributeEqualToIgnoreCase(
327        session, web_view, element_id, "multiple", "true", &multiple);
328    if (status.IsError())
329      return status;
330    if (!multiple && paths.size() > 1)
331      return Status(kUnknownError, "the element can not hold multiple files");
332
333    scoped_ptr<base::DictionaryValue> element(CreateElement(element_id));
334    return web_view->SetFileInputFiles(
335        session->GetCurrentFrameId(), *element, paths);
336  } else {
337    return SendKeysToElement(session, web_view, element_id, key_list);
338  }
339}
340
341Status ExecuteSubmitElement(
342    Session* session,
343    WebView* web_view,
344    const std::string& element_id,
345    const base::DictionaryValue& params,
346    scoped_ptr<base::Value>* value) {
347  base::ListValue args;
348  args.Append(CreateElement(element_id));
349  return web_view->CallFunction(
350      session->GetCurrentFrameId(),
351      webdriver::atoms::asString(webdriver::atoms::SUBMIT),
352      args,
353      value);
354}
355
356Status ExecuteGetElementText(
357    Session* session,
358    WebView* web_view,
359    const std::string& element_id,
360    const base::DictionaryValue& params,
361    scoped_ptr<base::Value>* value) {
362  base::ListValue args;
363  args.Append(CreateElement(element_id));
364  return web_view->CallFunction(
365      session->GetCurrentFrameId(),
366      webdriver::atoms::asString(webdriver::atoms::GET_TEXT),
367      args,
368      value);
369}
370
371Status ExecuteGetElementValue(
372    Session* session,
373    WebView* web_view,
374    const std::string& element_id,
375    const base::DictionaryValue& params,
376    scoped_ptr<base::Value>* value) {
377  base::ListValue args;
378  args.Append(CreateElement(element_id));
379  return web_view->CallFunction(
380      session->GetCurrentFrameId(),
381      "function(elem) { return elem['value'] }",
382      args,
383      value);
384}
385
386Status ExecuteGetElementTagName(
387    Session* session,
388    WebView* web_view,
389    const std::string& element_id,
390    const base::DictionaryValue& params,
391    scoped_ptr<base::Value>* value) {
392  base::ListValue args;
393  args.Append(CreateElement(element_id));
394  return web_view->CallFunction(
395      session->GetCurrentFrameId(),
396      "function(elem) { return elem.tagName.toLowerCase() }",
397      args,
398      value);
399}
400
401Status ExecuteIsElementSelected(
402    Session* session,
403    WebView* web_view,
404    const std::string& element_id,
405    const base::DictionaryValue& params,
406    scoped_ptr<base::Value>* value) {
407  base::ListValue args;
408  args.Append(CreateElement(element_id));
409  return web_view->CallFunction(
410      session->GetCurrentFrameId(),
411      webdriver::atoms::asString(webdriver::atoms::IS_SELECTED),
412      args,
413      value);
414}
415
416Status ExecuteIsElementEnabled(
417    Session* session,
418    WebView* web_view,
419    const std::string& element_id,
420    const base::DictionaryValue& params,
421    scoped_ptr<base::Value>* value) {
422  base::ListValue args;
423  args.Append(CreateElement(element_id));
424  return web_view->CallFunction(
425      session->GetCurrentFrameId(),
426      webdriver::atoms::asString(webdriver::atoms::IS_ENABLED),
427      args,
428      value);
429}
430
431Status ExecuteIsElementDisplayed(
432    Session* session,
433    WebView* web_view,
434    const std::string& element_id,
435    const base::DictionaryValue& params,
436    scoped_ptr<base::Value>* value) {
437  base::ListValue args;
438  args.Append(CreateElement(element_id));
439  return web_view->CallFunction(
440      session->GetCurrentFrameId(),
441      webdriver::atoms::asString(webdriver::atoms::IS_DISPLAYED),
442      args,
443      value);
444}
445
446Status ExecuteGetElementLocation(
447    Session* session,
448    WebView* web_view,
449    const std::string& element_id,
450    const base::DictionaryValue& params,
451    scoped_ptr<base::Value>* value) {
452  base::ListValue args;
453  args.Append(CreateElement(element_id));
454  return web_view->CallFunction(
455      session->GetCurrentFrameId(),
456      webdriver::atoms::asString(webdriver::atoms::GET_LOCATION),
457      args,
458      value);
459}
460
461Status ExecuteGetElementLocationOnceScrolledIntoView(
462    Session* session,
463    WebView* web_view,
464    const std::string& element_id,
465    const base::DictionaryValue& params,
466    scoped_ptr<base::Value>* value) {
467  WebPoint location;
468  Status status = ScrollElementIntoView(
469      session, web_view, element_id, &location);
470  if (status.IsError())
471    return status;
472  value->reset(CreateValueFrom(location));
473  return Status(kOk);
474}
475
476Status ExecuteGetElementSize(
477    Session* session,
478    WebView* web_view,
479    const std::string& element_id,
480    const base::DictionaryValue& params,
481    scoped_ptr<base::Value>* value) {
482  base::ListValue args;
483  args.Append(CreateElement(element_id));
484  return web_view->CallFunction(
485      session->GetCurrentFrameId(),
486      webdriver::atoms::asString(webdriver::atoms::GET_SIZE),
487      args,
488      value);
489}
490
491Status ExecuteGetElementAttribute(
492    Session* session,
493    WebView* web_view,
494    const std::string& element_id,
495    const base::DictionaryValue& params,
496    scoped_ptr<base::Value>* value) {
497  std::string name;
498  if (!params.GetString("name", &name))
499    return Status(kUnknownError, "missing 'name'");
500  return GetElementAttribute(session, web_view, element_id, name, value);
501}
502
503Status ExecuteGetElementValueOfCSSProperty(
504    Session* session,
505    WebView* web_view,
506    const std::string& element_id,
507    const base::DictionaryValue& params,
508    scoped_ptr<base::Value>* value) {
509  std::string property_name;
510  if (!params.GetString("propertyName", &property_name))
511    return Status(kUnknownError, "missing 'propertyName'");
512  std::string property_value;
513  Status status = GetElementEffectiveStyle(
514      session, web_view, element_id, property_name, &property_value);
515  if (status.IsError())
516    return status;
517  value->reset(new base::StringValue(property_value));
518  return Status(kOk);
519}
520
521Status ExecuteElementEquals(
522    Session* session,
523    WebView* web_view,
524    const std::string& element_id,
525    const base::DictionaryValue& params,
526    scoped_ptr<base::Value>* value) {
527  std::string other_element_id;
528  if (!params.GetString("other", &other_element_id))
529    return Status(kUnknownError, "'other' must be a string");
530  value->reset(new base::FundamentalValue(element_id == other_element_id));
531  return Status(kOk);
532}
533