1// Copyright (c) 2012 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/webdriver/webdriver_session.h"
6
7#include <sstream>
8#include <vector>
9
10#include "base/bind.h"
11#include "base/callback.h"
12#include "base/command_line.h"
13#include "base/file_util.h"
14#include "base/files/file_path.h"
15#include "base/json/json_reader.h"
16#include "base/json/json_writer.h"
17#include "base/memory/scoped_ptr.h"
18#include "base/message_loop/message_loop_proxy.h"
19#include "base/process/process.h"
20#include "base/strings/string_number_conversions.h"
21#include "base/strings/string_split.h"
22#include "base/strings/string_util.h"
23#include "base/strings/stringprintf.h"
24#include "base/strings/utf_string_conversions.h"
25#include "base/synchronization/waitable_event.h"
26#include "base/test/test_timeouts.h"
27#include "base/threading/platform_thread.h"
28#include "base/time/time.h"
29#include "base/values.h"
30#include "chrome/app/chrome_command_ids.h"
31#include "chrome/common/chrome_constants.h"
32#include "chrome/common/chrome_switches.h"
33#include "chrome/test/automation/automation_json_requests.h"
34#include "chrome/test/automation/value_conversion_util.h"
35#include "chrome/test/webdriver/webdriver_capabilities_parser.h"
36#include "chrome/test/webdriver/webdriver_error.h"
37#include "chrome/test/webdriver/webdriver_key_converter.h"
38#include "chrome/test/webdriver/webdriver_logging.h"
39#include "chrome/test/webdriver/webdriver_session_manager.h"
40#include "chrome/test/webdriver/webdriver_util.h"
41#include "third_party/webdriver/atoms.h"
42
43using automation::kLeftButton;
44using automation::kMouseDown;
45using automation::kMouseMove;
46using automation::kMouseUp;
47using automation::kNoButton;
48
49namespace webdriver {
50
51namespace {
52// This is the minimum version of chrome that supports the new mouse API.
53const int kNewMouseAPIMinVersion = 1002;
54}
55
56FrameId::FrameId() {}
57
58FrameId::FrameId(const WebViewId& view_id, const FramePath& frame_path)
59    : view_id(view_id),
60      frame_path(frame_path) {
61}
62
63Session::Session()
64    : session_log_(new InMemoryLog()),
65      logger_(kAllLogLevel),
66      id_(GenerateRandomID()),
67      current_target_(FrameId(WebViewId(), FramePath())),
68      thread_(id_.c_str()),
69      async_script_timeout_(0),
70      implicit_wait_(0),
71      has_alert_prompt_text_(false),
72      sticky_modifiers_(0),
73      build_no_(0) {
74  SessionManager::GetInstance()->Add(this);
75  logger_.AddHandler(session_log_.get());
76  if (FileLog::Get())
77    logger_.AddHandler(FileLog::Get());
78}
79
80Session::~Session() {
81  SessionManager::GetInstance()->Remove(id_);
82}
83
84Error* Session::Init(const base::DictionaryValue* capabilities_dict) {
85  if (!thread_.Start()) {
86    delete this;
87    return new Error(kUnknownError, "Cannot start session thread");
88  }
89  if (!temp_dir_.CreateUniqueTempDir()) {
90    delete this;
91    return new Error(
92        kUnknownError, "Unable to create temp directory for unpacking");
93  }
94  logger_.Log(kFineLogLevel,
95              "Initializing session with capabilities " +
96                  JsonStringifyForDisplay(capabilities_dict));
97  CapabilitiesParser parser(
98      capabilities_dict, temp_dir_.path(), logger_, &capabilities_);
99  Error* error = parser.Parse();
100  if (error) {
101    delete this;
102    return error;
103  }
104  logger_.set_min_log_level(capabilities_.log_levels[LogType::kDriver]);
105
106  Automation::BrowserOptions browser_options;
107  browser_options.command = capabilities_.command;
108  browser_options.channel_id = capabilities_.channel;
109  browser_options.detach_process = capabilities_.detach;
110  browser_options.user_data_dir = capabilities_.profile;
111  browser_options.exclude_switches = capabilities_.exclude_switches;
112  if (!capabilities_.no_website_testing_defaults) {
113    browser_options.ignore_certificate_errors = true;
114  }
115  RunSessionTask(base::Bind(
116      &Session::InitOnSessionThread,
117      base::Unretained(this),
118      browser_options,
119      &build_no_,
120      &error));
121  if (!error)
122    error = PostBrowserStartInit();
123
124  if (error)
125    Terminate();
126  return error;
127}
128
129Error* Session::BeforeExecuteCommand() {
130  Error* error = AfterExecuteCommand();
131  if (!error) {
132    scoped_ptr<Error> switch_error(SwitchToTopFrameIfCurrentFrameInvalid());
133    if (switch_error.get()) {
134      std::string text;
135      scoped_ptr<Error> alert_error(GetAlertMessage(&text));
136      if (alert_error.get()) {
137        // Only return a frame checking error if a modal dialog is not present.
138        // TODO(kkania): This is ugly. Fix.
139        return switch_error.release();
140      }
141    }
142  }
143  return error;
144}
145
146Error* Session::AfterExecuteCommand() {
147  Error* error = NULL;
148  if (!capabilities_.load_async) {
149    error = WaitForAllViewsToStopLoading();
150  }
151  return error;
152}
153
154void Session::Terminate() {
155  RunSessionTask(base::Bind(
156      &Session::TerminateOnSessionThread,
157      base::Unretained(this)));
158  delete this;
159}
160
161Error* Session::ExecuteScript(const FrameId& frame_id,
162                              const std::string& script,
163                              const base::ListValue* const args,
164                              base::Value** value) {
165  std::string args_as_json;
166  base::JSONWriter::Write(static_cast<const base::Value* const>(args),
167                          &args_as_json);
168
169  // Every injected script is fed through the executeScript atom. This atom
170  // will catch any errors that are thrown and convert them to the
171  // appropriate JSON structure.
172  std::string jscript = base::StringPrintf(
173      "window.domAutomationController.send((%s).apply(null,"
174      "[function(){%s\n},%s,true]));",
175      atoms::asString(atoms::EXECUTE_SCRIPT).c_str(), script.c_str(),
176      args_as_json.c_str());
177
178  return ExecuteScriptAndParseValue(frame_id, jscript, value);
179}
180
181Error* Session::ExecuteScript(const std::string& script,
182                              const base::ListValue* const args,
183                              base::Value** value) {
184  return ExecuteScript(current_target_, script, args, value);
185}
186
187Error* Session::ExecuteScriptAndParse(const FrameId& frame_id,
188                                      const std::string& anonymous_func_script,
189                                      const std::string& script_name,
190                                      const base::ListValue* args,
191                                      const ValueParser* parser) {
192  scoped_ptr<const base::ListValue> scoped_args(args);
193  scoped_ptr<const ValueParser> scoped_parser(parser);
194  std::string called_script = base::StringPrintf(
195      "return (%s).apply(null, arguments);", anonymous_func_script.c_str());
196  base::Value* unscoped_value = NULL;
197  Error* error = ExecuteScript(frame_id, called_script, args, &unscoped_value);
198  if (error) {
199    error->AddDetails(script_name + " execution failed");
200    return error;
201  }
202
203  scoped_ptr<base::Value> value(unscoped_value);
204  std::string error_msg;
205  if (!parser->Parse(value.get())) {
206    error_msg = base::StringPrintf("%s returned invalid value: %s",
207        script_name.c_str(), JsonStringify(value.get()).c_str());
208    return new Error(kUnknownError, error_msg);
209  }
210  return NULL;
211}
212
213Error* Session::ExecuteAsyncScript(const FrameId& frame_id,
214                                   const std::string& script,
215                                   const base::ListValue* const args,
216                                   base::Value** value) {
217  std::string args_as_json;
218  base::JSONWriter::Write(static_cast<const base::Value* const>(args),
219                          &args_as_json);
220
221  int timeout_ms = async_script_timeout();
222
223  // Every injected script is fed through the executeScript atom. This atom
224  // will catch any errors that are thrown and convert them to the
225  // appropriate JSON structure.
226  std::string jscript = base::StringPrintf(
227      "(%s).apply(null, [function(){%s},%s,%d,%s,true]);",
228      atoms::asString(atoms::EXECUTE_ASYNC_SCRIPT).c_str(),
229      script.c_str(),
230      args_as_json.c_str(),
231      timeout_ms,
232      "function(result) {window.domAutomationController.send(result);}");
233
234  return ExecuteScriptAndParseValue(frame_id, jscript, value);
235}
236
237Error* Session::SendKeys(const ElementId& element, const string16& keys) {
238  bool is_displayed = false;
239  Error* error = IsElementDisplayed(
240      current_target_, element, true /* ignore_opacity */, &is_displayed);
241  if (error)
242    return error;
243  if (!is_displayed)
244    return new Error(kElementNotVisible);
245
246  bool is_enabled = false;
247  error = IsElementEnabled(current_target_, element, &is_enabled);
248  if (error)
249    return error;
250  if (!is_enabled)
251    return new Error(kInvalidElementState);
252
253  // Focus the target element in order to send keys to it.
254  // First, the currently active element is blurred, if it is different from
255  // the target element. We do not want to blur an element unnecessarily,
256  // because this may cause us to lose the current cursor position in the
257  // element.
258  // Secondly, we focus the target element.
259  // Thirdly, if the target element is newly focused and is a text input, we
260  // set the cursor position at the end.
261  // Fourthly, we check if the new active element is the target element. If not,
262  // we throw an error.
263  // Additional notes:
264  //   - |document.activeElement| is the currently focused element, or body if
265  //     no element is focused
266  //   - Even if |document.hasFocus()| returns true and the active element is
267  //     the body, sometimes we still need to focus the body element for send
268  //     keys to work. Not sure why
269  //   - You cannot focus a descendant of a content editable node
270  //   - V8 throws a TypeError when calling setSelectionRange for a non-text
271  //     input, which still have setSelectionRange defined.
272  // TODO(jleyba): Update this to use the correct atom.
273  const char* kFocusScript =
274      "function(elem) {"
275      "  var doc = elem.ownerDocument || elem;"
276      "  var prevActiveElem = doc.activeElement;"
277      "  if (elem != prevActiveElem && prevActiveElem)"
278      "    prevActiveElem.blur();"
279      "  elem.focus();"
280      "  if (elem != prevActiveElem && elem.value && elem.value.length &&"
281      "      elem.setSelectionRange) {"
282      "    try {"
283      "      elem.setSelectionRange(elem.value.length, elem.value.length);"
284      "    } catch (error) {"
285      "      if (!(error instanceof TypeError)) {"
286      "        throw error;"
287      "      }"
288      "    }"
289      "  }"
290      "  if (elem != doc.activeElement)"
291      "    throw new Error('Failed to send keys because cannot focus element');"
292      "}";
293  error = ExecuteScriptAndParse(current_target_,
294                                kFocusScript,
295                                "focusElement",
296                                CreateListValueFrom(element),
297                                CreateDirectValueParser(kSkipParsing));
298  if (error)
299    return error;
300
301  RunSessionTask(base::Bind(
302      &Session::SendKeysOnSessionThread,
303      base::Unretained(this),
304      keys,
305      true /* release_modifiers */,
306      &error));
307  return error;
308}
309
310Error* Session::SendKeys(const string16& keys) {
311  Error* error = NULL;
312  RunSessionTask(base::Bind(
313      &Session::SendKeysOnSessionThread,
314      base::Unretained(this),
315      keys,
316      false /* release_modifiers */,
317      &error));
318  return error;
319}
320
321Error* Session::DragAndDropFilePaths(
322    const Point& location,
323    const std::vector<base::FilePath::StringType>& paths) {
324  Error* error = NULL;
325  RunSessionTask(base::Bind(
326      &Automation::DragAndDropFilePaths,
327      base::Unretained(automation_.get()),
328      current_target_.view_id,
329      location,
330      paths,
331      &error));
332  return error;
333}
334
335Error* Session::NavigateToURL(const std::string& url) {
336  if (!current_target_.view_id.IsTab()) {
337    return new Error(kUnknownError,
338                     "The current target does not support navigation");
339  }
340  Error* error = NULL;
341  if (capabilities_.load_async) {
342    RunSessionTask(base::Bind(
343        &Automation::NavigateToURLAsync,
344        base::Unretained(automation_.get()),
345        current_target_.view_id,
346        url,
347        &error));
348  } else {
349    RunSessionTask(base::Bind(
350        &Automation::NavigateToURL,
351        base::Unretained(automation_.get()),
352        current_target_.view_id,
353        url,
354        &error));
355  }
356  return error;
357}
358
359Error* Session::GoForward() {
360  if (!current_target_.view_id.IsTab()) {
361    return new Error(kUnknownError,
362                     "The current target does not support navigation");
363  }
364  Error* error = NULL;
365  RunSessionTask(base::Bind(
366      &Automation::GoForward,
367      base::Unretained(automation_.get()),
368      current_target_.view_id,
369      &error));
370  return error;
371}
372
373Error* Session::GoBack() {
374  if (!current_target_.view_id.IsTab()) {
375    return new Error(kUnknownError,
376                     "The current target does not support navigation");
377  }
378  Error* error = NULL;
379  RunSessionTask(base::Bind(
380      &Automation::GoBack,
381      base::Unretained(automation_.get()),
382      current_target_.view_id,
383      &error));
384  return error;
385}
386
387Error* Session::Reload() {
388  if (!current_target_.view_id.IsTab()) {
389    return new Error(kUnknownError,
390                     "The current target does not support navigation");
391  }
392  Error* error = NULL;
393  RunSessionTask(base::Bind(
394      &Automation::Reload,
395      base::Unretained(automation_.get()),
396      current_target_.view_id,
397      &error));
398  return error;
399}
400
401Error* Session::GetURL(std::string* url) {
402  return ExecuteScriptAndParse(current_target_,
403                               "function() { return document.URL }",
404                               "getUrl",
405                               new base::ListValue(),
406                               CreateDirectValueParser(url));
407}
408
409Error* Session::GetTitle(std::string* tab_title) {
410  const char* kGetTitleScript =
411      "function() {"
412      "  if (document.title)"
413      "    return document.title;"
414      "  else"
415      "    return document.URL;"
416      "}";
417  return ExecuteScriptAndParse(FrameId(current_target_.view_id, FramePath()),
418                               kGetTitleScript,
419                               "getTitle",
420                               new base::ListValue(),
421                               CreateDirectValueParser(tab_title));
422}
423
424Error* Session::MouseMoveAndClick(const Point& location,
425                                  automation::MouseButton button) {
426  Error* error = NULL;
427  if (build_no_ >= kNewMouseAPIMinVersion) {
428    std::vector<WebMouseEvent> events;
429    events.push_back(CreateWebMouseEvent(kMouseMove, kNoButton, location, 0));
430    events.push_back(CreateWebMouseEvent(kMouseDown, button, location, 1));
431    events.push_back(CreateWebMouseEvent(kMouseUp, button, location, 1));
432    error = ProcessWebMouseEvents(events);
433  } else {
434    RunSessionTask(base::Bind(
435        &Automation::MouseClickDeprecated,
436        base::Unretained(automation_.get()),
437        current_target_.view_id,
438        location,
439        button,
440        &error));
441  }
442  if (!error)
443    mouse_position_ = location;
444  return error;
445}
446
447Error* Session::MouseMove(const Point& location) {
448  Error* error = NULL;
449  if (build_no_ >= kNewMouseAPIMinVersion) {
450    std::vector<WebMouseEvent> events;
451    events.push_back(CreateWebMouseEvent(kMouseMove, kNoButton, location, 0));
452    error = ProcessWebMouseEvents(events);
453  } else {
454    RunSessionTask(base::Bind(
455        &Automation::MouseMoveDeprecated,
456        base::Unretained(automation_.get()),
457        current_target_.view_id,
458        location,
459        &error));
460  }
461  if (!error)
462    mouse_position_ = location;
463  return error;
464}
465
466Error* Session::MouseDrag(const Point& start,
467                          const Point& end) {
468  Error* error = NULL;
469  if (build_no_ >= kNewMouseAPIMinVersion) {
470    std::vector<WebMouseEvent> events;
471    events.push_back(CreateWebMouseEvent(kMouseMove, kNoButton, start, 0));
472    events.push_back(CreateWebMouseEvent(kMouseDown, kLeftButton, start, 1));
473    events.push_back(CreateWebMouseEvent(kMouseMove, kLeftButton, end, 0));
474    events.push_back(CreateWebMouseEvent(kMouseUp, kLeftButton, end, 1));
475    error = ProcessWebMouseEvents(events);
476  } else {
477    RunSessionTask(base::Bind(
478        &Automation::MouseDragDeprecated,
479        base::Unretained(automation_.get()),
480        current_target_.view_id,
481        start,
482        end,
483        &error));
484  }
485  if (!error)
486    mouse_position_ = end;
487  return error;
488}
489
490Error* Session::MouseClick(automation::MouseButton button) {
491  if (build_no_ >= kNewMouseAPIMinVersion) {
492    std::vector<WebMouseEvent> events;
493    events.push_back(CreateWebMouseEvent(
494        kMouseDown, button, mouse_position_, 1));
495    events.push_back(CreateWebMouseEvent(
496        kMouseUp, button, mouse_position_, 1));
497    return ProcessWebMouseEvents(events);
498  } else {
499    return MouseMoveAndClick(mouse_position_, button);
500  }
501}
502
503Error* Session::MouseButtonDown() {
504  Error* error = NULL;
505  if (build_no_ >= kNewMouseAPIMinVersion) {
506    std::vector<WebMouseEvent> events;
507    events.push_back(CreateWebMouseEvent(
508        kMouseDown, kLeftButton, mouse_position_, 1));
509    error = ProcessWebMouseEvents(events);
510  } else {
511    RunSessionTask(base::Bind(
512        &Automation::MouseButtonDownDeprecated,
513        base::Unretained(automation_.get()),
514        current_target_.view_id,
515        mouse_position_,
516        &error));
517  }
518  return error;
519}
520
521Error* Session::MouseButtonUp() {
522  Error* error = NULL;
523  if (build_no_ >= kNewMouseAPIMinVersion) {
524    std::vector<WebMouseEvent> events;
525    events.push_back(CreateWebMouseEvent(
526        kMouseUp, kLeftButton, mouse_position_, 1));
527    error = ProcessWebMouseEvents(events);
528  } else {
529    RunSessionTask(base::Bind(
530        &Automation::MouseButtonUpDeprecated,
531        base::Unretained(automation_.get()),
532        current_target_.view_id,
533        mouse_position_,
534        &error));
535  }
536  return error;
537}
538
539Error* Session::MouseDoubleClick() {
540  Error* error = NULL;
541  if (build_no_ >= kNewMouseAPIMinVersion) {
542    std::vector<WebMouseEvent> events;
543    events.push_back(CreateWebMouseEvent(
544        kMouseDown, kLeftButton, mouse_position_, 1));
545    events.push_back(CreateWebMouseEvent(
546        kMouseUp, kLeftButton, mouse_position_, 1));
547    events.push_back(CreateWebMouseEvent(
548        kMouseDown, kLeftButton, mouse_position_, 2));
549    events.push_back(CreateWebMouseEvent(
550        kMouseUp, kLeftButton, mouse_position_, 2));
551    error = ProcessWebMouseEvents(events);
552  } else {
553    RunSessionTask(base::Bind(
554        &Automation::MouseDoubleClickDeprecated,
555        base::Unretained(automation_.get()),
556        current_target_.view_id,
557        mouse_position_,
558        &error));
559  }
560  return error;
561}
562
563Error* Session::GetCookies(const std::string& url,
564                           scoped_ptr<base::ListValue>* cookies) {
565  Error* error = NULL;
566  RunSessionTask(base::Bind(
567      &Automation::GetCookies,
568      base::Unretained(automation_.get()),
569      url,
570      cookies,
571      &error));
572  return error;
573}
574
575Error* Session::DeleteCookie(const std::string& url,
576                           const std::string& cookie_name) {
577  Error* error = NULL;
578  RunSessionTask(base::Bind(
579      &Automation::DeleteCookie,
580      base::Unretained(automation_.get()),
581      url,
582      cookie_name,
583      &error));
584  return error;
585}
586
587// Note that when this is called from CookieCommand::ExecutePost then
588// |cookie_dict| is destroyed as soon as the caller finishes. Therefore
589// it is essential that RunSessionTask executes synchronously.
590Error* Session::SetCookie(const std::string& url,
591                          base::DictionaryValue* cookie_dict) {
592  Error* error = NULL;
593  RunSessionTask(base::Bind(
594      &Automation::SetCookie,
595      base::Unretained(automation_.get()),
596      url,
597      cookie_dict,
598      &error));
599  return error;
600}
601
602Error* Session::GetViews(std::vector<WebViewInfo>* views) {
603  Error* error = NULL;
604  RunSessionTask(base::Bind(
605      &Automation::GetViews,
606      base::Unretained(automation_.get()),
607      views,
608      &error));
609  return error;
610}
611
612Error* Session::SwitchToView(const std::string& id_or_name) {
613  Error* error = NULL;
614  bool does_exist = false;
615
616  WebViewId new_view;
617  StringToWebViewId(id_or_name, &new_view);
618  if (new_view.IsValid()) {
619    RunSessionTask(base::Bind(
620        &Automation::DoesViewExist,
621        base::Unretained(automation_.get()),
622        new_view,
623        &does_exist,
624        &error));
625    if (error)
626      return error;
627  }
628
629  if (!does_exist) {
630    // See if any of the tab window names match |name|.
631    std::vector<WebViewInfo> views;
632    Error* error = GetViews(&views);
633    if (error)
634      return error;
635    for (size_t i = 0; i < views.size(); ++i) {
636      if (!views[i].view_id.IsTab())
637        continue;
638      std::string window_name;
639      Error* error = ExecuteScriptAndParse(
640          FrameId(views[i].view_id, FramePath()),
641          "function() { return window.name; }",
642          "getWindowName",
643          new base::ListValue(),
644          CreateDirectValueParser(&window_name));
645      if (error)
646        return error;
647      if (id_or_name == window_name) {
648        new_view = views[i].view_id;
649        does_exist = true;
650        break;
651      }
652    }
653  }
654
655  if (!does_exist)
656    return new Error(kNoSuchWindow);
657  frame_elements_.clear();
658  current_target_ = FrameId(new_view, FramePath());
659  return NULL;
660}
661
662Error* Session::SwitchToFrameWithNameOrId(const std::string& name_or_id) {
663  std::string script =
664      "function(arg) {"
665      "  var xpath = '(/html/body//iframe|/html/frameset/frame)';"
666      "  var sub = function(s) { return s.replace(/\\$/g, arg); };"
667      "  xpath += sub('[@name=\"$\" or @id=\"$\"]');"
668      "  return document.evaluate(xpath, document, null, "
669      "      XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;"
670      "}";
671  return SwitchToFrameWithJavaScriptLocatedFrame(
672      script, CreateListValueFrom(name_or_id));
673}
674
675Error* Session::SwitchToFrameWithIndex(int index) {
676  // We cannot simply index into window.frames because we need to know the
677  // tagName of the frameElement. If child frame N is from another domain, then
678  // the following will run afoul of the same origin policy:
679  //   window.frames[N].frameElement;
680  // Instead of indexing window.frames, we use an XPath expression to index
681  // into the list of all IFRAME and FRAME elements on the page - if we find
682  // something, then that XPath expression can be used as the new frame's XPath.
683  std::string script =
684      "function(index) {"
685      "  var xpathIndex = '[' + (index + 1) + ']';"
686      "  var xpath = '(/html/body//iframe|/html/frameset/frame)' + "
687      "              xpathIndex;"
688      "  return document.evaluate(xpath, document, null, "
689      "      XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;"
690      "}";
691  return SwitchToFrameWithJavaScriptLocatedFrame(
692      script, CreateListValueFrom(index));
693}
694
695Error* Session::SwitchToFrameWithElement(const ElementId& element) {
696  // TODO(jleyba): Extract this, and the other frame switch methods to an atom.
697  std::string script =
698      "function(elem) {"
699      "  if (elem.nodeType != 1 || !/^i?frame$/i.test(elem.tagName)) {"
700      "    console.error('Element is not a frame');"
701      "    return null;"
702      "  }"
703      "  for (var i = 0; i < window.frames.length; i++) {"
704      "    if (elem.contentWindow == window.frames[i]) {"
705      "      return elem;"
706      "    }"
707      "  }"
708      "  console.info('Frame is not connected to this DOM tree');"
709      "  return null;"
710      "}";
711  return SwitchToFrameWithJavaScriptLocatedFrame(
712      script, CreateListValueFrom(element));
713}
714
715void Session::SwitchToTopFrame() {
716  frame_elements_.clear();
717  current_target_.frame_path = FramePath();
718}
719
720Error* Session::SwitchToTopFrameIfCurrentFrameInvalid() {
721  std::vector<std::string> components;
722  current_target_.frame_path.GetComponents(&components);
723  if (frame_elements_.size() != components.size()) {
724    return new Error(kUnknownError,
725                     "Frame element vector out of sync with frame path");
726  }
727  FramePath frame_path;
728  // Start from the root path and check that each frame element that makes
729  // up the current frame target is valid by executing an empty script.
730  // This code should not execute script in any frame before making sure the
731  // frame element is valid, otherwise the automation hangs until a timeout.
732  for (size_t i = 0; i < frame_elements_.size(); ++i) {
733    FrameId frame_id(current_target_.view_id, frame_path);
734    scoped_ptr<Error> error(ExecuteScriptAndParse(
735        frame_id,
736        "function(){ }",
737        "emptyScript",
738        CreateListValueFrom(frame_elements_[i]),
739        CreateDirectValueParser(kSkipParsing)));
740    if (error.get() && error->code() == kStaleElementReference) {
741      SwitchToTopFrame();
742    } else if (error.get()) {
743      return error.release();
744    }
745    frame_path = frame_path.Append(components[i]);
746  }
747  return NULL;
748}
749
750Error* Session::CloseWindow() {
751  Error* error = NULL;
752  RunSessionTask(base::Bind(
753      &Automation::CloseView,
754      base::Unretained(automation_.get()),
755      current_target_.view_id,
756      &error));
757
758  if (!error) {
759    std::vector<WebViewInfo> views;
760    scoped_ptr<Error> error(GetViews(&views));
761    if (error.get() || views.empty()) {
762      // The automation connection will soon be closed, if not already,
763      // because we supposedly just closed the last window. Terminate the
764      // session.
765      // TODO(kkania): This will cause us problems if GetWindowIds fails for a
766      // reason other than the channel is disconnected. Look into having
767      // |GetWindowIds| tell us if it just closed the last window.
768      Terminate();
769    }
770  }
771  return error;
772}
773
774Error* Session::GetWindowBounds(const WebViewId& window, Rect* bounds) {
775  const char* kGetWindowBoundsScript =
776      "function() {"
777      "  return {"
778      "    'left': window.screenX,"
779      "    'top': window.screenY,"
780      "    'width': window.outerWidth,"
781      "    'height': window.outerHeight"
782      "  }"
783      "}";
784  return ExecuteScriptAndParse(
785      FrameId(window, FramePath()),
786      kGetWindowBoundsScript,
787      "getWindowBoundsScript",
788      new base::ListValue(),
789      CreateDirectValueParser(bounds));
790}
791
792Error* Session::SetWindowBounds(
793    const WebViewId& window,
794    const Rect& bounds) {
795  Error* error = NULL;
796  RunSessionTask(base::Bind(
797      &Automation::SetViewBounds,
798      base::Unretained(automation_.get()),
799      window,
800      bounds,
801      &error));
802  return error;
803}
804
805Error* Session::MaximizeWindow(const WebViewId& window) {
806  Error* error = NULL;
807  RunSessionTask(base::Bind(
808        &Automation::MaximizeView,
809        base::Unretained(automation_.get()),
810        window,
811        &error));
812  return error;
813}
814
815Error* Session::GetAlertMessage(std::string* text) {
816  Error* error = NULL;
817  RunSessionTask(base::Bind(
818      &Automation::GetAppModalDialogMessage,
819      base::Unretained(automation_.get()),
820      text,
821      &error));
822  return error;
823}
824
825Error* Session::SetAlertPromptText(const std::string& alert_prompt_text) {
826  std::string message_text;
827  // Only set the alert prompt text if an alert is actually active.
828  Error* error = GetAlertMessage(&message_text);
829  if (!error) {
830    has_alert_prompt_text_ = true;
831    alert_prompt_text_ = alert_prompt_text;
832  }
833  return error;
834}
835
836Error* Session::AcceptOrDismissAlert(bool accept) {
837  Error* error = NULL;
838  if (accept && has_alert_prompt_text_) {
839    RunSessionTask(base::Bind(
840        &Automation::AcceptPromptAppModalDialog,
841        base::Unretained(automation_.get()),
842        alert_prompt_text_,
843        &error));
844  } else {
845    RunSessionTask(base::Bind(
846        &Automation::AcceptOrDismissAppModalDialog,
847        base::Unretained(automation_.get()),
848        accept,
849        &error));
850  }
851  has_alert_prompt_text_ = false;
852  return error;
853}
854
855std::string Session::GetBrowserVersion() {
856  std::string version;
857  RunSessionTask(base::Bind(
858      &Automation::GetBrowserVersion,
859      base::Unretained(automation_.get()),
860      &version));
861  return version;
862}
863
864Error* Session::CompareBrowserVersion(int client_build_no,
865                                      int client_patch_no,
866                                      bool* is_newer_or_equal) {
867  std::string version = GetBrowserVersion();
868  std::vector<std::string> split_version;
869  base::SplitString(version, '.', &split_version);
870  if (split_version.size() != 4) {
871    return new Error(
872        kUnknownError, "Browser version has unrecognized format: " + version);
873  }
874  int build_no, patch_no;
875  if (!base::StringToInt(split_version[2], &build_no) ||
876      !base::StringToInt(split_version[3], &patch_no)) {
877    return new Error(
878        kUnknownError, "Browser version has unrecognized format: " + version);
879  }
880  if (build_no < client_build_no)
881    *is_newer_or_equal = false;
882  else if (build_no > client_build_no)
883    *is_newer_or_equal = true;
884  else
885    *is_newer_or_equal = patch_no >= client_patch_no;
886  return NULL;
887}
888
889Error* Session::FindElement(const FrameId& frame_id,
890                            const ElementId& root_element,
891                            const std::string& locator,
892                            const std::string& query,
893                            ElementId* element) {
894  std::vector<ElementId> elements;
895  Error* error = FindElementsHelper(
896      frame_id, root_element, locator, query, true, &elements);
897  if (!error)
898    *element = elements[0];
899  return error;
900}
901
902Error* Session::FindElements(const FrameId& frame_id,
903                             const ElementId& root_element,
904                             const std::string& locator,
905                             const std::string& query,
906                             std::vector<ElementId>* elements) {
907  return FindElementsHelper(
908      frame_id, root_element, locator, query, false, elements);
909}
910
911Error* Session::GetElementLocationInView(
912    const ElementId& element,
913    Point* location) {
914  Size size;
915  Error* error = GetElementSize(current_target_, element, &size);
916  if (error)
917    return error;
918  return GetElementRegionInView(
919      element, Rect(Point(0, 0), size),
920      false /* center */, false /* verify_clickable_at_middle */, location);
921}
922
923Error* Session::GetElementRegionInView(
924    const ElementId& element,
925    const Rect& region,
926    bool center,
927    bool verify_clickable_at_middle,
928    Point* location) {
929  CHECK(element.is_valid());
930
931  Point region_offset = region.origin();
932  Size region_size = region.size();
933  Error* error = GetElementRegionInViewHelper(
934      current_target_, element, region, center, verify_clickable_at_middle,
935      &region_offset);
936  if (error)
937    return error;
938
939  for (FramePath frame_path = current_target_.frame_path;
940       frame_path.IsSubframe();
941       frame_path = frame_path.Parent()) {
942    // Find the frame element for the current frame path.
943    FrameId frame_id(current_target_.view_id, frame_path.Parent());
944    ElementId frame_element;
945    error = FindElement(frame_id,
946                        ElementId(std::string()),
947                        LocatorType::kXpath,
948                        frame_path.BaseName().value(),
949                        &frame_element);
950    if (error) {
951      std::string context = base::StringPrintf(
952          "Could not find frame element (%s) in frame (%s)",
953          frame_path.BaseName().value().c_str(),
954          frame_path.Parent().value().c_str());
955      error->AddDetails(context);
956      return error;
957    }
958    // Modify |region_offset| by the frame's border.
959    int border_left, border_top;
960    error = GetElementBorder(
961        frame_id, frame_element, &border_left, &border_top);
962    if (error)
963      return error;
964    region_offset.Offset(border_left, border_top);
965
966    error = GetElementRegionInViewHelper(
967        frame_id, frame_element, Rect(region_offset, region_size),
968        center, verify_clickable_at_middle, &region_offset);
969    if (error)
970      return error;
971  }
972  *location = region_offset;
973  return NULL;
974}
975
976Error* Session::GetElementSize(const FrameId& frame_id,
977                               const ElementId& element,
978                               Size* size) {
979  return ExecuteScriptAndParse(
980      frame_id,
981      atoms::asString(atoms::GET_SIZE),
982      "getSize",
983      CreateListValueFrom(element),
984      CreateDirectValueParser(size));
985}
986
987Error* Session::GetElementFirstClientRect(const FrameId& frame_id,
988                                          const ElementId& element,
989                                          Rect* rect) {
990  return ExecuteScriptAndParse(
991      frame_id,
992      atoms::asString(atoms::GET_FIRST_CLIENT_RECT),
993      "getFirstClientRect",
994      CreateListValueFrom(element),
995      CreateDirectValueParser(rect));
996}
997
998Error* Session::GetElementEffectiveStyle(
999    const FrameId& frame_id,
1000    const ElementId& element,
1001    const std::string& prop,
1002    std::string* value) {
1003  return ExecuteScriptAndParse(
1004      frame_id,
1005      atoms::asString(atoms::GET_EFFECTIVE_STYLE),
1006      "getEffectiveStyle",
1007      CreateListValueFrom(element, prop),
1008      CreateDirectValueParser(value));
1009}
1010
1011Error* Session::GetElementBorder(const FrameId& frame_id,
1012                                 const ElementId& element,
1013                                 int* border_left,
1014                                 int* border_top) {
1015  std::string border_left_str, border_top_str;
1016  Error* error = GetElementEffectiveStyle(
1017      frame_id, element, "border-left-width", &border_left_str);
1018  if (error)
1019    return error;
1020  error = GetElementEffectiveStyle(
1021      frame_id, element, "border-top-width", &border_top_str);
1022  if (error)
1023    return error;
1024
1025  base::StringToInt(border_left_str, border_left);
1026  base::StringToInt(border_top_str, border_top);
1027  return NULL;
1028}
1029
1030Error* Session::IsElementDisplayed(const FrameId& frame_id,
1031                                   const ElementId& element,
1032                                   bool ignore_opacity,
1033                                   bool* is_displayed) {
1034  return ExecuteScriptAndParse(
1035      frame_id,
1036      atoms::asString(atoms::IS_DISPLAYED),
1037      "isDisplayed",
1038      CreateListValueFrom(element, ignore_opacity),
1039      CreateDirectValueParser(is_displayed));
1040}
1041
1042Error* Session::IsElementEnabled(const FrameId& frame_id,
1043                                 const ElementId& element,
1044                                 bool* is_enabled) {
1045  return ExecuteScriptAndParse(
1046      frame_id,
1047      atoms::asString(atoms::IS_ENABLED),
1048      "isEnabled",
1049      CreateListValueFrom(element),
1050      CreateDirectValueParser(is_enabled));
1051}
1052
1053Error* Session::IsOptionElementSelected(const FrameId& frame_id,
1054                                        const ElementId& element,
1055                                        bool* is_selected) {
1056  return ExecuteScriptAndParse(
1057      frame_id,
1058      atoms::asString(atoms::IS_SELECTED),
1059      "isSelected",
1060      CreateListValueFrom(element),
1061      CreateDirectValueParser(is_selected));
1062}
1063
1064Error* Session::SetOptionElementSelected(const FrameId& frame_id,
1065                                         const ElementId& element,
1066                                         bool selected) {
1067  // This wrapper ensures the script is started successfully and
1068  // allows for an alert to happen when the option selection occurs.
1069  // See selenium bug 2671.
1070  const char kSetSelectedWrapper[] =
1071      "var args = [].slice.apply(arguments);"
1072      "args[args.length - 1]();"
1073      "return (%s).apply(null, args.slice(0, args.length - 1));";
1074  base::Value* value = NULL;
1075  Error* error = ExecuteAsyncScript(
1076      frame_id,
1077      base::StringPrintf(kSetSelectedWrapper,
1078                         atoms::asString(atoms::CLICK).c_str()),
1079      CreateListValueFrom(element, selected),
1080      &value);
1081  scoped_ptr<base::Value> scoped_value(value);
1082  return error;
1083}
1084
1085Error* Session::ToggleOptionElement(const FrameId& frame_id,
1086                                    const ElementId& element) {
1087  bool is_selected;
1088  Error* error = IsOptionElementSelected(frame_id, element, &is_selected);
1089  if (error)
1090    return error;
1091
1092  return SetOptionElementSelected(frame_id, element, !is_selected);
1093}
1094
1095Error* Session::GetElementTagName(const FrameId& frame_id,
1096                                  const ElementId& element,
1097                                  std::string* tag_name) {
1098  return ExecuteScriptAndParse(
1099      frame_id,
1100      "function(elem) { return elem.tagName.toLowerCase() }",
1101      "getElementTagName",
1102      CreateListValueFrom(element),
1103      CreateDirectValueParser(tag_name));
1104}
1105
1106Error* Session::GetClickableLocation(const ElementId& element,
1107                                     Point* location) {
1108  bool is_displayed = false;
1109  Error* error = IsElementDisplayed(
1110      current_target_, element, true /* ignore_opacity */, &is_displayed);
1111  if (error)
1112    return error;
1113  if (!is_displayed)
1114    return new Error(kElementNotVisible, "Element must be displayed to click");
1115
1116  // We try 3 methods to determine clickable location. This mostly follows
1117  // what FirefoxDriver does. Try the first client rect, then the bounding
1118  // client rect, and lastly the size of the element (via closure).
1119  // SVG is one case that doesn't have a first client rect.
1120  Rect rect;
1121  scoped_ptr<Error> ignore_error(
1122      GetElementFirstClientRect(current_target_, element, &rect));
1123  if (ignore_error.get()) {
1124    Rect client_rect;
1125    ignore_error.reset(ExecuteScriptAndParse(
1126        current_target_,
1127        "function(elem) { return elem.getBoundingClientRect() }",
1128        "getBoundingClientRect",
1129        CreateListValueFrom(element),
1130        CreateDirectValueParser(&client_rect)));
1131    rect = Rect(0, 0, client_rect.width(), client_rect.height());
1132  }
1133  if (ignore_error.get()) {
1134    Size size;
1135    ignore_error.reset(GetElementSize(current_target_, element, &size));
1136    rect = Rect(0, 0, size.width(), size.height());
1137  }
1138  if (ignore_error.get()) {
1139    return new Error(kUnknownError,
1140                     "Unable to determine clickable location of element");
1141  }
1142
1143  error = GetElementRegionInView(
1144      element, rect, true /* center */, true /* verify_clickable_at_middle */,
1145      location);
1146  if (error)
1147    return error;
1148  location->Offset(rect.width() / 2, rect.height() / 2);
1149  return NULL;
1150}
1151
1152Error* Session::GetAttribute(const ElementId& element,
1153                             const std::string& key,
1154                             base::Value** value) {
1155  return ExecuteScriptAndParse(
1156      current_target_,
1157      atoms::asString(atoms::GET_ATTRIBUTE),
1158      "getAttribute",
1159      CreateListValueFrom(element, key),
1160      CreateDirectValueParser(value));
1161}
1162
1163Error* Session::WaitForAllViewsToStopLoading() {
1164  if (!automation_.get())
1165    return NULL;
1166
1167  logger_.Log(kFinerLogLevel, "Waiting for all views to stop loading...");
1168  Error* error = NULL;
1169  RunSessionTask(base::Bind(
1170      &Automation::WaitForAllViewsToStopLoading,
1171      base::Unretained(automation_.get()),
1172      &error));
1173  logger_.Log(kFinerLogLevel, "Done waiting for all views to stop loading");
1174  return error;
1175}
1176
1177Error* Session::InstallExtension(
1178    const base::FilePath& path, std::string* extension_id) {
1179  Error* error = NULL;
1180  RunSessionTask(base::Bind(
1181      &Automation::InstallExtension,
1182      base::Unretained(automation_.get()),
1183      path,
1184      extension_id,
1185      &error));
1186  return error;
1187}
1188
1189Error* Session::GetExtensionsInfo(base::ListValue* extensions_list) {
1190  Error* error = NULL;
1191  RunSessionTask(base::Bind(
1192      &Automation::GetExtensionsInfo,
1193      base::Unretained(automation_.get()),
1194      extensions_list,
1195      &error));
1196  return error;
1197}
1198
1199Error* Session::IsPageActionVisible(
1200    const WebViewId& tab_id,
1201    const std::string& extension_id,
1202    bool* is_visible) {
1203  if (!tab_id.IsTab()) {
1204    return new Error(
1205        kUnknownError,
1206        "The current target does not support page actions. Switch to a tab.");
1207  }
1208  Error* error = NULL;
1209  RunSessionTask(base::Bind(
1210      &Automation::IsPageActionVisible,
1211      base::Unretained(automation_.get()),
1212      tab_id,
1213      extension_id,
1214      is_visible,
1215      &error));
1216  return error;
1217}
1218
1219Error* Session::SetExtensionState(
1220    const std::string& extension_id, bool enable) {
1221  Error* error = NULL;
1222  RunSessionTask(base::Bind(
1223      &Automation::SetExtensionState,
1224      base::Unretained(automation_.get()),
1225      extension_id,
1226      enable,
1227      &error));
1228  return error;
1229}
1230
1231Error* Session::ClickExtensionButton(
1232    const std::string& extension_id, bool browser_action) {
1233  Error* error = NULL;
1234  RunSessionTask(base::Bind(
1235      &Automation::ClickExtensionButton,
1236      base::Unretained(automation_.get()),
1237      extension_id,
1238      browser_action,
1239      &error));
1240  return error;
1241}
1242
1243Error* Session::UninstallExtension(const std::string& extension_id) {
1244  Error* error = NULL;
1245  RunSessionTask(base::Bind(
1246      &Automation::UninstallExtension,
1247      base::Unretained(automation_.get()),
1248      extension_id,
1249      &error));
1250  return error;
1251}
1252
1253Error* Session::SetPreference(
1254    const std::string& pref,
1255    bool is_user_pref,
1256    base::Value* value) {
1257  Error* error = NULL;
1258  if (is_user_pref) {
1259    RunSessionTask(base::Bind(
1260        &Automation::SetPreference,
1261        base::Unretained(automation_.get()),
1262        pref,
1263        value,
1264        &error));
1265    if (error)
1266      error->AddDetails("Failed to set user pref '" + pref + "'");
1267  } else {
1268    RunSessionTask(base::Bind(
1269        &Automation::SetLocalStatePreference,
1270        base::Unretained(automation_.get()),
1271        pref,
1272        value,
1273        &error));
1274    if (error)
1275      error->AddDetails("Failed to set local state pref '" + pref + "'");
1276  }
1277  return error;
1278}
1279
1280base::ListValue* Session::GetLog() const {
1281  return session_log_->entries_list()->DeepCopy();
1282}
1283
1284Error* Session::GetBrowserConnectionState(bool* online) {
1285  return ExecuteScriptAndParse(
1286      current_target_,
1287      atoms::asString(atoms::IS_ONLINE),
1288      "isOnline",
1289      new base::ListValue(),
1290      CreateDirectValueParser(online));
1291}
1292
1293Error* Session::GetAppCacheStatus(int* status) {
1294  return ExecuteScriptAndParse(
1295      current_target_,
1296      atoms::asString(atoms::GET_APPCACHE_STATUS),
1297      "getAppcacheStatus",
1298      new base::ListValue(),
1299      CreateDirectValueParser(status));
1300}
1301
1302Error* Session::GetStorageSize(StorageType type, int* size) {
1303  std::string js = atoms::asString(
1304      type == kLocalStorageType ? atoms::GET_LOCAL_STORAGE_SIZE
1305                                : atoms::GET_SESSION_STORAGE_SIZE);
1306  return ExecuteScriptAndParse(
1307      current_target_,
1308      js,
1309      "getStorageSize",
1310      new base::ListValue(),
1311      CreateDirectValueParser(size));
1312}
1313
1314Error* Session::SetStorageItem(StorageType type,
1315                               const std::string& key,
1316                               const std::string& value) {
1317  std::string js = atoms::asString(
1318      type == kLocalStorageType ? atoms::SET_LOCAL_STORAGE_ITEM
1319                                : atoms::SET_SESSION_STORAGE_ITEM);
1320  return ExecuteScriptAndParse(
1321      current_target_,
1322      js,
1323      "setStorageItem",
1324      CreateListValueFrom(key, value),
1325      CreateDirectValueParser(kSkipParsing));
1326}
1327
1328Error* Session::ClearStorage(StorageType type) {
1329  std::string js = atoms::asString(
1330      type == kLocalStorageType ? atoms::CLEAR_LOCAL_STORAGE
1331                                : atoms::CLEAR_SESSION_STORAGE);
1332  return ExecuteScriptAndParse(
1333      current_target_,
1334      js,
1335      "clearStorage",
1336      new base::ListValue(),
1337      CreateDirectValueParser(kSkipParsing));
1338}
1339
1340Error* Session::GetStorageKeys(StorageType type, base::ListValue** keys) {
1341  std::string js = atoms::asString(
1342      type == kLocalStorageType ? atoms::GET_LOCAL_STORAGE_KEYS
1343                                : atoms::GET_SESSION_STORAGE_KEYS);
1344  return ExecuteScriptAndParse(
1345      current_target_,
1346      js,
1347      "getStorageKeys",
1348      new base::ListValue(),
1349      CreateDirectValueParser(keys));
1350}
1351
1352Error* Session::GetStorageItem(StorageType type,
1353                               const std::string& key,
1354                               std::string* value) {
1355  std::string js = atoms::asString(
1356      type == kLocalStorageType ? atoms::GET_LOCAL_STORAGE_ITEM
1357                                : atoms::GET_SESSION_STORAGE_ITEM);
1358  return ExecuteScriptAndParse(
1359      current_target_,
1360      js,
1361      "getStorageItem",
1362      CreateListValueFrom(key),
1363      CreateDirectValueParser(value));
1364}
1365
1366Error* Session::RemoveStorageItem(StorageType type,
1367                                  const std::string& key,
1368                                  std::string* value) {
1369  std::string js = atoms::asString(
1370      type == kLocalStorageType ? atoms::REMOVE_LOCAL_STORAGE_ITEM
1371                                : atoms::REMOVE_SESSION_STORAGE_ITEM);
1372  return ExecuteScriptAndParse(
1373      current_target_,
1374      js,
1375      "removeStorageItem",
1376      CreateListValueFrom(key),
1377      CreateDirectValueParser(value));
1378}
1379
1380Error* Session::GetGeolocation(
1381    scoped_ptr<base::DictionaryValue>* geolocation) {
1382  Error* error = NULL;
1383  RunSessionTask(base::Bind(
1384      &Automation::GetGeolocation,
1385      base::Unretained(automation_.get()),
1386      geolocation,
1387      &error));
1388  return error;
1389}
1390
1391Error* Session::OverrideGeolocation(const base::DictionaryValue* geolocation) {
1392  Error* error = NULL;
1393  RunSessionTask(base::Bind(
1394      &Automation::OverrideGeolocation,
1395      base::Unretained(automation_.get()),
1396      geolocation,
1397      &error));
1398  return error;
1399}
1400
1401const std::string& Session::id() const {
1402  return id_;
1403}
1404
1405const FrameId& Session::current_target() const {
1406  return current_target_;
1407}
1408
1409void Session::set_async_script_timeout(int timeout_ms) {
1410  async_script_timeout_ = timeout_ms;
1411}
1412
1413int Session::async_script_timeout() const {
1414  return async_script_timeout_;
1415}
1416
1417void Session::set_implicit_wait(int timeout_ms) {
1418  implicit_wait_ = timeout_ms;
1419}
1420
1421int Session::implicit_wait() const {
1422  return implicit_wait_;
1423}
1424
1425const Point& Session::get_mouse_position() const {
1426  return mouse_position_;
1427}
1428
1429const Logger& Session::logger() const {
1430  return logger_;
1431}
1432
1433const base::FilePath& Session::temp_dir() const {
1434  return temp_dir_.path();
1435}
1436
1437const Capabilities& Session::capabilities() const {
1438  return capabilities_;
1439}
1440
1441void Session::RunSessionTask(const base::Closure& task) {
1442  base::WaitableEvent done_event(false, false);
1443  thread_.message_loop_proxy()->PostTask(FROM_HERE, base::Bind(
1444      &Session::RunClosureOnSessionThread,
1445      base::Unretained(this),
1446      task,
1447      &done_event));
1448  // See SetCookie for why it is essential that we wait here.
1449  done_event.Wait();
1450}
1451
1452void Session::RunClosureOnSessionThread(const base::Closure& task,
1453                                        base::WaitableEvent* done_event) {
1454  task.Run();
1455  done_event->Signal();
1456}
1457
1458void Session::InitOnSessionThread(const Automation::BrowserOptions& options,
1459                                  int* build_no,
1460                                  Error** error) {
1461  automation_.reset(new Automation(logger_));
1462  automation_->Init(options, build_no, error);
1463  if (*error)
1464    return;
1465
1466  std::vector<WebViewInfo> views;
1467  automation_->GetViews(&views, error);
1468  if (*error)
1469    return;
1470  if (views.empty()) {
1471    *error = new Error(kUnknownError, "No view ids after initialization");
1472    return;
1473  }
1474  current_target_ = FrameId(views[0].view_id, FramePath());
1475}
1476
1477void Session::TerminateOnSessionThread() {
1478  if (automation_.get())
1479    automation_->Terminate();
1480  automation_.reset();
1481}
1482
1483Error* Session::ExecuteScriptAndParseValue(const FrameId& frame_id,
1484                                           const std::string& script,
1485                                           base::Value** script_result) {
1486  std::string response_json;
1487  Error* error = NULL;
1488  RunSessionTask(base::Bind(
1489      &Automation::ExecuteScript,
1490      base::Unretained(automation_.get()),
1491      frame_id.view_id,
1492      frame_id.frame_path,
1493      script,
1494      &response_json,
1495      &error));
1496  if (error)
1497    return error;
1498
1499  scoped_ptr<base::Value> value(base::JSONReader::ReadAndReturnError(
1500      response_json, base::JSON_ALLOW_TRAILING_COMMAS, NULL, NULL));
1501  if (!value.get())
1502    return new Error(kUnknownError, "Failed to parse script result");
1503  if (value->GetType() != base::Value::TYPE_DICTIONARY)
1504    return new Error(kUnknownError, "Execute script returned non-dict: " +
1505                         JsonStringify(value.get()));
1506  base::DictionaryValue* result_dict =
1507      static_cast<base::DictionaryValue*>(value.get());
1508
1509  int status;
1510  if (!result_dict->GetInteger("status", &status))
1511    return new Error(kUnknownError, "Execute script did not return status: " +
1512                         JsonStringify(result_dict));
1513  ErrorCode code = static_cast<ErrorCode>(status);
1514  if (code != kSuccess) {
1515    base::DictionaryValue* error_dict;
1516    std::string error_msg;
1517    if (result_dict->GetDictionary("value", &error_dict))
1518      error_dict->GetString("message", &error_msg);
1519    if (error_msg.empty())
1520      error_msg = "Script failed with error code: " + base::IntToString(code);
1521    return new Error(code, error_msg);
1522  }
1523
1524  base::Value* tmp;
1525  if (result_dict->Get("value", &tmp)) {
1526    *script_result= tmp->DeepCopy();
1527  } else {
1528    // "value" was not defined in the returned dictionary; set to null.
1529    *script_result= base::Value::CreateNullValue();
1530  }
1531  return NULL;
1532}
1533
1534void Session::SendKeysOnSessionThread(const string16& keys,
1535                                      bool release_modifiers, Error** error) {
1536  std::vector<WebKeyEvent> key_events;
1537  std::string error_msg;
1538  if (!ConvertKeysToWebKeyEvents(keys, logger_, release_modifiers,
1539                                 &sticky_modifiers_, &key_events, &error_msg)) {
1540    *error = new Error(kUnknownError, error_msg);
1541    return;
1542  }
1543  for (size_t i = 0; i < key_events.size(); ++i) {
1544    automation_->SendWebKeyEvent(
1545        current_target_.view_id,
1546        key_events[i], error);
1547    if (*error) {
1548      std::string details = base::StringPrintf(
1549          "Failed to send key event. Event details:\n"
1550              "Type: %d, KeyCode: %d, UnmodifiedText: %s, ModifiedText: %s, "
1551              "Modifiers: %d",
1552          key_events[i].type,
1553          key_events[i].key_code,
1554          key_events[i].unmodified_text.c_str(),
1555          key_events[i].modified_text.c_str(),
1556          key_events[i].modifiers);
1557      (*error)->AddDetails(details);
1558      return;
1559    }
1560  }
1561}
1562
1563Error* Session::ProcessWebMouseEvents(
1564    const std::vector<WebMouseEvent>& events) {
1565  for (size_t i = 0; i < events.size(); ++i) {
1566    Error* error = NULL;
1567    RunSessionTask(base::Bind(
1568        &Automation::SendWebMouseEvent,
1569        base::Unretained(automation_.get()),
1570        current_target_.view_id,
1571        events[i],
1572        &error));
1573    if (error)
1574      return error;
1575    mouse_position_ = Point(events[i].x, events[i].y);
1576  }
1577  return NULL;
1578}
1579
1580WebMouseEvent Session::CreateWebMouseEvent(
1581    automation::MouseEventType type,
1582    automation::MouseButton button,
1583    const Point& point,
1584    int click_count) {
1585  return WebMouseEvent(type, button, point.rounded_x(), point.rounded_y(),
1586                       click_count, sticky_modifiers_);
1587}
1588
1589Error* Session::SwitchToFrameWithJavaScriptLocatedFrame(
1590    const std::string& script, base::ListValue* args) {
1591  class SwitchFrameValueParser : public ValueParser {
1592   public:
1593    SwitchFrameValueParser(
1594        bool* found_frame, ElementId* frame)
1595        : found_frame_(found_frame), frame_(frame) { }
1596
1597    virtual ~SwitchFrameValueParser() { }
1598
1599    virtual bool Parse(base::Value* value) const OVERRIDE {
1600      if (value->IsType(base::Value::TYPE_NULL)) {
1601        *found_frame_ = false;
1602        return true;
1603      }
1604      ElementId id(value);
1605      if (!id.is_valid()) {
1606        return false;
1607      }
1608      *frame_ = id;
1609      *found_frame_ = true;
1610      return true;
1611    }
1612
1613   private:
1614    bool* found_frame_;
1615    ElementId* frame_;
1616  };
1617
1618  bool found_frame;
1619  ElementId new_frame_element;
1620  Error* error = ExecuteScriptAndParse(
1621      current_target_, script, "switchFrame", args,
1622      new SwitchFrameValueParser(&found_frame, &new_frame_element));
1623  if (error)
1624    return error;
1625
1626  if (!found_frame)
1627    return new Error(kNoSuchFrame);
1628
1629  std::string frame_id = GenerateRandomID();
1630  error = ExecuteScriptAndParse(
1631      current_target_,
1632      "function(elem, id) { elem.setAttribute('wd_frame_id_', id); }",
1633      "setFrameId",
1634      CreateListValueFrom(new_frame_element, frame_id),
1635      CreateDirectValueParser(kSkipParsing));
1636  if (error)
1637    return error;
1638
1639  frame_elements_.push_back(new_frame_element);
1640  current_target_.frame_path = current_target_.frame_path.Append(
1641      base::StringPrintf("//*[@wd_frame_id_ = '%s']", frame_id.c_str()));
1642  return NULL;
1643}
1644
1645Error* Session::FindElementsHelper(const FrameId& frame_id,
1646                                   const ElementId& root_element,
1647                                   const std::string& locator,
1648                                   const std::string& query,
1649                                   bool find_one,
1650                                   std::vector<ElementId>* elements) {
1651  CHECK(root_element.is_valid());
1652  base::Time start_time = base::Time::Now();
1653  while (true) {
1654    std::vector<ElementId> temp_elements;
1655    Error* error = ExecuteFindElementScriptAndParse(
1656        frame_id, root_element, locator, query, find_one, &temp_elements);
1657    if (error)
1658      return error;
1659
1660    if (temp_elements.size() > 0u) {
1661      elements->swap(temp_elements);
1662      break;
1663    }
1664
1665    if ((base::Time::Now() - start_time).InMilliseconds() > implicit_wait_) {
1666      if (find_one)
1667        return new Error(kNoSuchElement);
1668      break;
1669    }
1670    base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50));
1671  }
1672  return NULL;
1673}
1674
1675Error* Session::ExecuteFindElementScriptAndParse(
1676    const FrameId& frame_id,
1677    const ElementId& root_element,
1678    const std::string& locator,
1679    const std::string& query,
1680    bool find_one,
1681    std::vector<ElementId>* elements) {
1682  CHECK(root_element.is_valid());
1683
1684  class FindElementsParser : public ValueParser {
1685   public:
1686    explicit FindElementsParser(std::vector<ElementId>* elements)
1687        : elements_(elements) { }
1688
1689    virtual ~FindElementsParser() { }
1690
1691    virtual bool Parse(base::Value* value) const OVERRIDE {
1692      if (!value->IsType(base::Value::TYPE_LIST))
1693        return false;
1694      base::ListValue* list = static_cast<base::ListValue*>(value);
1695      for (size_t i = 0; i < list->GetSize(); ++i) {
1696        ElementId element;
1697        base::Value* element_value = NULL;
1698        if (!list->Get(i, &element_value))
1699          return false;
1700        if (!SetFromValue(element_value, &element))
1701          return false;
1702        elements_->push_back(element);
1703      }
1704      return true;
1705    }
1706   private:
1707    std::vector<ElementId>* elements_;
1708  };
1709
1710  class FindElementParser : public ValueParser {
1711   public:
1712    explicit FindElementParser(std::vector<ElementId>* elements)
1713        : elements_(elements) { }
1714
1715    virtual ~FindElementParser() { }
1716
1717    virtual bool Parse(base::Value* value) const OVERRIDE {
1718      if (value->IsType(base::Value::TYPE_NULL))
1719        return true;
1720      ElementId element;
1721      bool set = SetFromValue(value, &element);
1722      if (set)
1723        elements_->push_back(element);
1724      return set;
1725    }
1726   private:
1727    std::vector<ElementId>* elements_;
1728  };
1729
1730  base::DictionaryValue locator_dict;
1731  locator_dict.SetString(locator, query);
1732  std::vector<ElementId> temp_elements;
1733  Error* error = NULL;
1734  if (find_one) {
1735    error = ExecuteScriptAndParse(
1736          frame_id,
1737          atoms::asString(atoms::FIND_ELEMENT),
1738          "findElement",
1739          CreateListValueFrom(&locator_dict, root_element),
1740          new FindElementParser(&temp_elements));
1741  } else {
1742    error = ExecuteScriptAndParse(
1743          frame_id,
1744          atoms::asString(atoms::FIND_ELEMENTS),
1745          "findElements",
1746          CreateListValueFrom(&locator_dict, root_element),
1747          new FindElementsParser(&temp_elements));
1748  }
1749  if (!error)
1750    elements->swap(temp_elements);
1751  return error;
1752}
1753
1754Error* Session::VerifyElementIsClickable(
1755    const FrameId& frame_id,
1756    const ElementId& element,
1757    const Point& location) {
1758  class IsElementClickableParser : public ValueParser {
1759   public:
1760    IsElementClickableParser(bool* clickable, std::string* message)
1761        : clickable_(clickable), message_(message) { }
1762
1763    virtual ~IsElementClickableParser() { }
1764
1765    virtual bool Parse(base::Value* value) const OVERRIDE {
1766      if (!value->IsType(base::Value::TYPE_DICTIONARY))
1767        return false;
1768      base::DictionaryValue* dict = static_cast<base::DictionaryValue*>(value);
1769      dict->GetString("message", message_);
1770      return dict->GetBoolean("clickable", clickable_);
1771    }
1772
1773   private:
1774    bool* clickable_;
1775    std::string* message_;
1776  };
1777
1778  bool clickable;
1779  std::string message;
1780  Error* error = ExecuteScriptAndParse(
1781      frame_id,
1782      atoms::asString(atoms::IS_ELEMENT_CLICKABLE),
1783      "isElementClickable",
1784      CreateListValueFrom(element, location),
1785      new IsElementClickableParser(&clickable, &message));
1786  if (error)
1787    return error;
1788
1789  if (!clickable) {
1790    if (message.empty())
1791      message = "element is not clickable";
1792    return new Error(kUnknownError, message);
1793  }
1794  if (message.length()) {
1795    logger_.Log(kWarningLogLevel, message);
1796  }
1797  return NULL;
1798}
1799
1800Error* Session::GetElementRegionInViewHelper(
1801    const FrameId& frame_id,
1802    const ElementId& element,
1803    const Rect& region,
1804    bool center,
1805    bool verify_clickable_at_middle,
1806    Point* location) {
1807  Point temp_location;
1808  Error* error = ExecuteScriptAndParse(
1809      frame_id,
1810      atoms::asString(atoms::GET_LOCATION_IN_VIEW),
1811      "getLocationInView",
1812      CreateListValueFrom(element, center, region),
1813      CreateDirectValueParser(&temp_location));
1814
1815  if (verify_clickable_at_middle) {
1816    Point middle_point = temp_location;
1817    middle_point.Offset(region.width() / 2, region.height() / 2);
1818    error = VerifyElementIsClickable(frame_id, element, middle_point);
1819    if (error)
1820      return error;
1821  }
1822  *location = temp_location;
1823  return NULL;
1824}
1825
1826Error* Session::GetScreenShot(std::string* png) {
1827  if (!current_target_.view_id.IsTab()) {
1828    return new Error(kUnknownError,
1829                     "The current target does not support screenshot");
1830  }
1831  Error* error = NULL;
1832  base::ScopedTempDir screenshots_dir;
1833  if (!screenshots_dir.CreateUniqueTempDir()) {
1834    return new Error(kUnknownError,
1835                     "Could not create temp directory for screenshot");
1836  }
1837
1838  base::FilePath path = screenshots_dir.path().AppendASCII("screen");
1839  RunSessionTask(base::Bind(
1840      &Automation::CaptureEntirePageAsPNG,
1841      base::Unretained(automation_.get()),
1842      current_target_.view_id,
1843      path,
1844      &error));
1845  if (error)
1846    return error;
1847  if (!file_util::ReadFileToString(path, png))
1848    return new Error(kUnknownError, "Could not read screenshot file");
1849  return NULL;
1850}
1851
1852#if !defined(NO_TCMALLOC) && (defined(OS_LINUX) || defined(OS_CHROMEOS))
1853Error* Session::HeapProfilerDump(const std::string& reason) {
1854  // TODO(dmikurube): Support browser processes.
1855  Error* error = NULL;
1856  RunSessionTask(base::Bind(
1857      &Automation::HeapProfilerDump,
1858      base::Unretained(automation_.get()),
1859      current_target_.view_id,
1860      reason,
1861      &error));
1862  return error;
1863}
1864#endif  // !defined(NO_TCMALLOC) && (defined(OS_LINUX) || defined(OS_CHROMEOS))
1865
1866Error* Session::PostBrowserStartInit() {
1867  Error* error = NULL;
1868  if (!capabilities_.no_website_testing_defaults)
1869    error = InitForWebsiteTesting();
1870  if (!error)
1871    error = SetPrefs();
1872  if (error)
1873    return error;
1874
1875  // Install extensions.
1876  for (size_t i = 0; i < capabilities_.extensions.size(); ++i) {
1877    std::string extension_id;
1878    error = InstallExtension(capabilities_.extensions[i], &extension_id);
1879    if (error)
1880      return error;
1881  }
1882  return NULL;
1883}
1884
1885Error* Session::InitForWebsiteTesting() {
1886  bool has_prefs_api = false;
1887  // Don't set these prefs for Chrome 14 and below.
1888  // TODO(kkania): Remove this when Chrome 14 is unsupported.
1889  Error* error = CompareBrowserVersion(874, 0, &has_prefs_api);
1890  if (error || !has_prefs_api)
1891    return error;
1892
1893  // Disable checking for SSL certificate revocation.
1894  error = SetPreference(
1895      "ssl.rev_checking.enabled",
1896      false /* is_user_pref */,
1897      new base::FundamentalValue(false));
1898  if (error)
1899    return error;
1900
1901  // Allow content by default.
1902  // Media-stream cannot be enabled by default; we must specify
1903  // particular host patterns and devices.
1904  base::DictionaryValue* devices = new base::DictionaryValue();
1905  devices->SetString("audio", "Default");
1906  devices->SetString("video", "Default");
1907  base::DictionaryValue* content_settings = new base::DictionaryValue();
1908  content_settings->Set("media-stream", devices);
1909  base::DictionaryValue* pattern_pairs = new base::DictionaryValue();
1910  pattern_pairs->Set("https://*,*", content_settings);
1911  error = SetPreference(
1912      "profile.content_settings.pattern_pairs",
1913      true /* is_user_pref */,
1914      pattern_pairs);
1915  if (error)
1916    return error;
1917  const int kAllowContent = 1;
1918  base::DictionaryValue* default_content_settings = new base::DictionaryValue();
1919  default_content_settings->SetInteger("geolocation", kAllowContent);
1920  default_content_settings->SetInteger("mouselock", kAllowContent);
1921  default_content_settings->SetInteger("notifications", kAllowContent);
1922  default_content_settings->SetInteger("popups", kAllowContent);
1923  return SetPreference(
1924      "profile.default_content_settings",
1925      true /* is_user_pref */,
1926      default_content_settings);
1927}
1928
1929Error* Session::SetPrefs() {
1930  for (base::DictionaryValue::Iterator iter(*capabilities_.prefs);
1931       !iter.IsAtEnd(); iter.Advance()) {
1932    Error* error = SetPreference(iter.key(), true /* is_user_pref */,
1933                                 iter.value().DeepCopy());
1934    if (error)
1935      return error;
1936  }
1937  for (base::DictionaryValue::Iterator iter(*capabilities_.local_state);
1938       !iter.IsAtEnd(); iter.Advance()) {
1939    Error* error = SetPreference(iter.key(), false /* is_user_pref */,
1940                                 iter.value().DeepCopy());
1941    if (error)
1942      return error;
1943  }
1944  return NULL;
1945}
1946
1947}  // namespace webdriver
1948