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 30 void 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 49 void 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 75 namespace { 76 77 void 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 93 void 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 124 namespace { 125 126 void TerminateSessionThreadOnCommandThread(SessionThreadMap* session_thread_map, 127 const std::string& session_id) { 128 session_thread_map->erase(session_id); 129 } 130 131 void 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 229 void 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 257 namespace internal { 258 259 void CreateSessionOnSessionThreadForTesting(const std::string& id) { 260 SetThreadLocalSession(make_scoped_ptr(new Session(id))); 261 } 262 263 } // namespace internal 264