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