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