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/chromedriver/commands.h"
6
7#include <algorithm>
8#include <list>
9#include <utility>
10
11#include "base/bind.h"
12#include "base/bind_helpers.h"
13#include "base/logging.h"
14#include "base/memory/linked_ptr.h"
15#include "base/message_loop/message_loop.h"
16#include "base/message_loop/message_loop_proxy.h"
17#include "base/run_loop.h"
18#include "base/strings/stringprintf.h"
19#include "base/sys_info.h"
20#include "base/values.h"
21#include "chrome/test/chromedriver/capabilities.h"
22#include "chrome/test/chromedriver/chrome/browser_info.h"
23#include "chrome/test/chromedriver/chrome/chrome.h"
24#include "chrome/test/chromedriver/chrome/status.h"
25#include "chrome/test/chromedriver/logging.h"
26#include "chrome/test/chromedriver/session.h"
27#include "chrome/test/chromedriver/session_thread_map.h"
28#include "chrome/test/chromedriver/util.h"
29
30void ExecuteGetStatus(
31    const base::DictionaryValue& params,
32    const std::string& session_id,
33    const CommandCallback& callback) {
34  base::DictionaryValue build;
35  build.SetString("version", "alpha");
36
37  base::DictionaryValue os;
38  os.SetString("name", base::SysInfo::OperatingSystemName());
39  os.SetString("version", base::SysInfo::OperatingSystemVersion());
40  os.SetString("arch", base::SysInfo::OperatingSystemArchitecture());
41
42  base::DictionaryValue info;
43  info.Set("build", build.DeepCopy());
44  info.Set("os", os.DeepCopy());
45  callback.Run(
46      Status(kOk), scoped_ptr<base::Value>(info.DeepCopy()), std::string());
47}
48
49void ExecuteCreateSession(
50    SessionThreadMap* session_thread_map,
51    const Command& init_session_cmd,
52    const base::DictionaryValue& params,
53    const std::string& session_id,
54    const CommandCallback& callback) {
55  std::string new_id = session_id;
56  if (new_id.empty())
57    new_id = GenerateId();
58  scoped_ptr<Session> session(new Session(new_id));
59  scoped_ptr<base::Thread> thread(new base::Thread(new_id));
60  if (!thread->Start()) {
61    callback.Run(
62        Status(kUnknownError, "failed to start a thread for the new session"),
63        scoped_ptr<base::Value>(),
64        std::string());
65    return;
66  }
67
68  thread->message_loop()->PostTask(
69      FROM_HERE, base::Bind(&SetThreadLocalSession, base::Passed(&session)));
70  session_thread_map
71      ->insert(std::make_pair(new_id, make_linked_ptr(thread.release())));
72  init_session_cmd.Run(params, new_id, callback);
73}
74
75namespace {
76
77void OnSessionQuit(const base::WeakPtr<size_t>& quit_remaining_count,
78                   const base::Closure& all_quit_func,
79                   const Status& status,
80                   scoped_ptr<base::Value> value,
81                   const std::string& session_id) {
82  // |quit_remaining_count| may no longer be valid if a timeout occurred.
83  if (!quit_remaining_count)
84    return;
85
86  (*quit_remaining_count)--;
87  if (!*quit_remaining_count)
88    all_quit_func.Run();
89}
90
91}  // namespace
92
93void ExecuteQuitAll(
94    const Command& quit_command,
95    SessionThreadMap* session_thread_map,
96    const base::DictionaryValue& params,
97    const std::string& session_id,
98    const CommandCallback& callback) {
99  size_t quit_remaining_count = session_thread_map->size();
100  base::WeakPtrFactory<size_t> weak_ptr_factory(&quit_remaining_count);
101  if (!quit_remaining_count) {
102    callback.Run(Status(kOk), scoped_ptr<base::Value>(), session_id);
103    return;
104  }
105  base::RunLoop run_loop;
106  for (SessionThreadMap::const_iterator iter = session_thread_map->begin();
107       iter != session_thread_map->end();
108       ++iter) {
109    quit_command.Run(params,
110                     iter->first,
111                     base::Bind(&OnSessionQuit,
112                                weak_ptr_factory.GetWeakPtr(),
113                                run_loop.QuitClosure()));
114  }
115  base::MessageLoop::current()->PostDelayedTask(
116      FROM_HERE, run_loop.QuitClosure(), base::TimeDelta::FromSeconds(10));
117  // Uses a nested run loop to block this thread until all the quit
118  // commands have executed, or the timeout expires.
119  base::MessageLoop::current()->SetNestableTasksAllowed(true);
120  run_loop.Run();
121  callback.Run(Status(kOk), scoped_ptr<base::Value>(), session_id);
122}
123
124namespace {
125
126void TerminateSessionThreadOnCommandThread(SessionThreadMap* session_thread_map,
127                                           const std::string& session_id) {
128  session_thread_map->erase(session_id);
129}
130
131void ExecuteSessionCommandOnSessionThread(
132    const char* command_name,
133    const SessionCommand& command,
134    bool return_ok_without_session,
135    scoped_ptr<base::DictionaryValue> params,
136    scoped_refptr<base::SingleThreadTaskRunner> cmd_task_runner,
137    const CommandCallback& callback_on_cmd,
138    const base::Closure& terminate_on_cmd) {
139  Session* session = GetThreadLocalSession();
140  if (!session) {
141    cmd_task_runner->PostTask(
142        FROM_HERE,
143        base::Bind(callback_on_cmd,
144                   Status(return_ok_without_session ? kOk : kNoSuchSession),
145                   base::Passed(scoped_ptr<base::Value>()),
146                   std::string()));
147    return;
148  }
149
150  if (IsVLogOn(0)) {
151    VLOG(0) << "COMMAND " << command_name << " "
152            << FormatValueForDisplay(*params);
153  }
154
155  // Notify |session|'s |CommandListener|s of the command.
156  // Will mark |session| for deletion if an error is encountered.
157  Status status = NotifyCommandListenersBeforeCommand(session, command_name);
158
159  // Only run the command if we were able to notify all listeners successfully.
160  // Otherwise, pass error to callback, delete |session|, and do not continue.
161  scoped_ptr<base::Value> value;
162  if (status.IsError()) {
163    LOG(ERROR) << status.message();
164  } else {
165    status = command.Run(session, *params, &value);
166
167    if (status.IsError() && session->chrome) {
168      if (!session->quit && session->chrome->HasCrashedWebView()) {
169        session->quit = true;
170        std::string message("session deleted because of page crash");
171        if (!session->detach) {
172          Status quit_status = session->chrome->Quit();
173          if (quit_status.IsError())
174            message += ", but failed to kill browser:" + quit_status.message();
175        }
176        status = Status(kUnknownError, message, status);
177      } else if (status.code() == kDisconnected) {
178        // Some commands, like clicking a button or link which closes the
179        // window, may result in a kDisconnected error code.
180        std::list<std::string> web_view_ids;
181        Status status_tmp = session->chrome->GetWebViewIds(&web_view_ids);
182        if (status_tmp.IsError() && status_tmp.code() != kChromeNotReachable) {
183          status.AddDetails(
184              "failed to check if window was closed: " + status_tmp.message());
185        } else if (std::find(web_view_ids.begin(),
186                             web_view_ids.end(),
187                             session->window) == web_view_ids.end()) {
188          status = Status(kOk);
189        }
190      }
191      if (status.IsError()) {
192        const BrowserInfo* browser_info = session->chrome->GetBrowserInfo();
193        status.AddDetails("Session info: " + browser_info->browser_name + "=" +
194                          browser_info->browser_version);
195      }
196    }
197
198    if (IsVLogOn(0)) {
199      std::string result;
200      if (status.IsError()) {
201        result = status.message();
202      } else if (value) {
203        result = FormatValueForDisplay(*value);
204      }
205      VLOG(0) << "RESPONSE " << command_name
206              << (result.length() ? " " + result : "");
207    }
208
209    if (status.IsOk() && session->auto_reporting_enabled) {
210      std::string message = session->GetFirstBrowserError();
211      if (!message.empty())
212        status = Status(kUnknownError, message);
213    }
214  }
215
216  cmd_task_runner->PostTask(
217      FROM_HERE,
218      base::Bind(callback_on_cmd, status, base::Passed(&value), session->id));
219
220  if (session->quit) {
221    SetThreadLocalSession(scoped_ptr<Session>());
222    delete session;
223    cmd_task_runner->PostTask(FROM_HERE, terminate_on_cmd);
224  }
225}
226
227}  // namespace
228
229void ExecuteSessionCommand(
230    SessionThreadMap* session_thread_map,
231    const char* command_name,
232    const SessionCommand& command,
233    bool return_ok_without_session,
234    const base::DictionaryValue& params,
235    const std::string& session_id,
236    const CommandCallback& callback) {
237  SessionThreadMap::iterator iter = session_thread_map->find(session_id);
238  if (iter == session_thread_map->end()) {
239    Status status(return_ok_without_session ? kOk : kNoSuchSession);
240    callback.Run(status, scoped_ptr<base::Value>(), session_id);
241  } else {
242    iter->second->message_loop()
243        ->PostTask(FROM_HERE,
244                   base::Bind(&ExecuteSessionCommandOnSessionThread,
245                              command_name,
246                              command,
247                              return_ok_without_session,
248                              base::Passed(make_scoped_ptr(params.DeepCopy())),
249                              base::MessageLoopProxy::current(),
250                              callback,
251                              base::Bind(&TerminateSessionThreadOnCommandThread,
252                                         session_thread_map,
253                                         session_id)));
254  }
255}
256
257namespace internal {
258
259void CreateSessionOnSessionThreadForTesting(const std::string& id) {
260  SetThreadLocalSession(make_scoped_ptr(new Session(id)));
261}
262
263}  // namespace internal
264