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 <list> 8 #include <utility> 9 10 #include "base/bind.h" 11 #include "base/bind_helpers.h" 12 #include "base/lazy_instance.h" 13 #include "base/logging.h" // For CHECK macros. 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/threading/thread_local.h" 21 #include "base/values.h" 22 #include "chrome/test/chromedriver/capabilities.h" 23 #include "chrome/test/chromedriver/chrome/chrome.h" 24 #include "chrome/test/chromedriver/chrome/chrome_android_impl.h" 25 #include "chrome/test/chromedriver/chrome/chrome_desktop_impl.h" 26 #include "chrome/test/chromedriver/chrome/device_manager.h" 27 #include "chrome/test/chromedriver/chrome/devtools_event_listener.h" 28 #include "chrome/test/chromedriver/chrome/status.h" 29 #include "chrome/test/chromedriver/chrome/version.h" 30 #include "chrome/test/chromedriver/chrome/web_view.h" 31 #include "chrome/test/chromedriver/chrome_launcher.h" 32 #include "chrome/test/chromedriver/logging.h" 33 #include "chrome/test/chromedriver/net/net_util.h" 34 #include "chrome/test/chromedriver/net/url_request_context_getter.h" 35 #include "chrome/test/chromedriver/session.h" 36 #include "chrome/test/chromedriver/session_thread_map.h" 37 #include "chrome/test/chromedriver/util.h" 38 39 void ExecuteGetStatus( 40 const base::DictionaryValue& params, 41 const std::string& session_id, 42 const CommandCallback& callback) { 43 base::DictionaryValue build; 44 build.SetString("version", "alpha"); 45 46 base::DictionaryValue os; 47 os.SetString("name", base::SysInfo::OperatingSystemName()); 48 os.SetString("version", base::SysInfo::OperatingSystemVersion()); 49 os.SetString("arch", base::SysInfo::OperatingSystemArchitecture()); 50 51 base::DictionaryValue info; 52 info.Set("build", build.DeepCopy()); 53 info.Set("os", os.DeepCopy()); 54 callback.Run( 55 Status(kOk), scoped_ptr<base::Value>(info.DeepCopy()), std::string()); 56 } 57 58 NewSessionParams::NewSessionParams( 59 Log* log, 60 SessionThreadMap* session_thread_map, 61 scoped_refptr<URLRequestContextGetter> context_getter, 62 const SyncWebSocketFactory& socket_factory, 63 DeviceManager* device_manager) 64 : log(log), 65 session_thread_map(session_thread_map), 66 context_getter(context_getter), 67 socket_factory(socket_factory), 68 device_manager(device_manager) {} 69 70 NewSessionParams::~NewSessionParams() {} 71 72 namespace { 73 74 base::LazyInstance<base::ThreadLocalPointer<Session> > 75 lazy_tls_session = LAZY_INSTANCE_INITIALIZER; 76 77 Status CreateSessionOnSessionThreadHelper( 78 const NewSessionParams& bound_params, 79 const base::DictionaryValue& params, 80 const std::string& session_id, 81 scoped_ptr<base::Value>* out_value) { 82 int port; 83 if (!FindOpenPort(&port)) 84 return Status(kUnknownError, "failed to find an open port for Chrome"); 85 86 const base::DictionaryValue* desired_caps; 87 if (!params.GetDictionary("desiredCapabilities", &desired_caps)) 88 return Status(kUnknownError, "cannot find dict 'desiredCapabilities'"); 89 90 Capabilities capabilities; 91 Status status = capabilities.Parse(*desired_caps, bound_params.log); 92 if (status.IsError()) 93 return status; 94 95 // Create Log's and DevToolsEventListener's for ones that are DevTools-based. 96 // Session will own the Log's, Chrome will own the listeners. 97 ScopedVector<WebDriverLog> devtools_logs; 98 ScopedVector<DevToolsEventListener> devtools_event_listeners; 99 status = CreateLogs(capabilities, &devtools_logs, &devtools_event_listeners); 100 if (status.IsError()) 101 return status; 102 103 scoped_ptr<Chrome> chrome; 104 status = LaunchChrome(bound_params.context_getter.get(), 105 port, 106 bound_params.socket_factory, 107 bound_params.log, 108 bound_params.device_manager, 109 capabilities, 110 devtools_event_listeners, 111 &chrome); 112 if (status.IsError()) 113 return status; 114 115 std::list<std::string> web_view_ids; 116 status = chrome->GetWebViewIds(&web_view_ids); 117 if (status.IsError() || web_view_ids.empty()) { 118 chrome->Quit(); 119 return status.IsError() ? status : 120 Status(kUnknownError, "unable to discover open window in chrome"); 121 } 122 123 scoped_ptr<Session> session(new Session(session_id, chrome.Pass())); 124 session->devtools_logs.swap(devtools_logs); 125 session->window = web_view_ids.front(); 126 session->detach = capabilities.detach; 127 out_value->reset(session->capabilities->DeepCopy()); 128 lazy_tls_session.Pointer()->Set(session.release()); 129 return Status(kOk); 130 } 131 132 void CreateSessionOnSessionThread( 133 const scoped_refptr<base::SingleThreadTaskRunner>& cmd_task_runner, 134 const NewSessionParams& bound_params, 135 scoped_ptr<base::DictionaryValue> params, 136 const std::string& session_id, 137 const CommandCallback& callback_on_cmd) { 138 scoped_ptr<base::Value> value; 139 Status status = CreateSessionOnSessionThreadHelper( 140 bound_params, *params, session_id, &value); 141 cmd_task_runner->PostTask( 142 FROM_HERE, 143 base::Bind(callback_on_cmd, status, base::Passed(&value), session_id)); 144 } 145 146 } // namespace 147 148 void ExecuteNewSession( 149 const NewSessionParams& bound_params, 150 const base::DictionaryValue& params, 151 const std::string& session_id, 152 const CommandCallback& callback) { 153 std::string new_id = session_id; 154 if (new_id.empty()) 155 new_id = GenerateId(); 156 scoped_ptr<base::Thread> thread(new base::Thread(new_id.c_str())); 157 if (!thread->Start()) { 158 callback.Run( 159 Status(kUnknownError, "failed to start a thread for the new session"), 160 scoped_ptr<base::Value>(), 161 std::string()); 162 return; 163 } 164 165 thread->message_loop() 166 ->PostTask(FROM_HERE, 167 base::Bind(&CreateSessionOnSessionThread, 168 base::MessageLoopProxy::current(), 169 bound_params, 170 base::Passed(make_scoped_ptr(params.DeepCopy())), 171 new_id, 172 callback)); 173 bound_params.session_thread_map 174 ->insert(std::make_pair(new_id, make_linked_ptr(thread.release()))); 175 } 176 177 namespace { 178 179 void OnSessionQuit(const base::WeakPtr<size_t>& quit_remaining_count, 180 const base::Closure& all_quit_func, 181 const Status& status, 182 scoped_ptr<base::Value> value, 183 const std::string& session_id) { 184 // |quit_remaining_count| may no longer be valid if a timeout occurred. 185 if (!quit_remaining_count) 186 return; 187 188 (*quit_remaining_count)--; 189 if (!*quit_remaining_count) 190 all_quit_func.Run(); 191 } 192 193 } // namespace 194 195 void ExecuteQuitAll( 196 const Command& quit_command, 197 SessionThreadMap* session_thread_map, 198 const base::DictionaryValue& params, 199 const std::string& session_id, 200 const CommandCallback& callback) { 201 size_t quit_remaining_count = session_thread_map->size(); 202 base::WeakPtrFactory<size_t> weak_ptr_factory(&quit_remaining_count); 203 if (!quit_remaining_count) { 204 callback.Run(Status(kOk), scoped_ptr<base::Value>(), session_id); 205 return; 206 } 207 base::RunLoop run_loop; 208 for (SessionThreadMap::const_iterator iter = session_thread_map->begin(); 209 iter != session_thread_map->end(); 210 ++iter) { 211 quit_command.Run(params, 212 iter->first, 213 base::Bind(&OnSessionQuit, 214 weak_ptr_factory.GetWeakPtr(), 215 run_loop.QuitClosure())); 216 } 217 base::MessageLoop::current()->PostDelayedTask( 218 FROM_HERE, run_loop.QuitClosure(), base::TimeDelta::FromSeconds(10)); 219 // Uses a nested run loop to block this thread until all the quit 220 // commands have executed, or the timeout expires. 221 base::MessageLoop::current()->SetNestableTasksAllowed(true); 222 run_loop.Run(); 223 callback.Run(Status(kOk), scoped_ptr<base::Value>(), session_id); 224 } 225 226 namespace { 227 228 void TerminateSessionThreadOnCommandThread(SessionThreadMap* session_thread_map, 229 const std::string& session_id) { 230 session_thread_map->erase(session_id); 231 } 232 233 void ExecuteSessionCommandOnSessionThread( 234 const SessionCommand& command, 235 bool return_ok_without_session, 236 scoped_ptr<base::DictionaryValue> params, 237 scoped_refptr<base::SingleThreadTaskRunner> cmd_task_runner, 238 const CommandCallback& callback_on_cmd, 239 const base::Closure& terminate_on_cmd) { 240 Session* session = lazy_tls_session.Pointer()->Get(); 241 if (!session) { 242 cmd_task_runner->PostTask( 243 FROM_HERE, 244 base::Bind(callback_on_cmd, 245 Status(return_ok_without_session ? kOk : kNoSuchSession), 246 base::Passed(scoped_ptr<base::Value>()), 247 std::string())); 248 return; 249 } 250 251 scoped_ptr<base::Value> value; 252 Status status = command.Run(session, *params, &value); 253 if (status.IsError() && session->chrome) 254 status.AddDetails("Session info: chrome=" + session->chrome->GetVersion()); 255 256 cmd_task_runner->PostTask( 257 FROM_HERE, 258 base::Bind(callback_on_cmd, status, base::Passed(&value), session->id)); 259 260 if (session->quit) { 261 lazy_tls_session.Pointer()->Set(NULL); 262 delete session; 263 cmd_task_runner->PostTask(FROM_HERE, terminate_on_cmd); 264 } 265 } 266 267 } // namespace 268 269 void ExecuteSessionCommand( 270 SessionThreadMap* session_thread_map, 271 const SessionCommand& command, 272 bool return_ok_without_session, 273 const base::DictionaryValue& params, 274 const std::string& session_id, 275 const CommandCallback& callback) { 276 SessionThreadMap::iterator iter = session_thread_map->find(session_id); 277 if (iter == session_thread_map->end()) { 278 Status status(return_ok_without_session ? kOk : kNoSuchSession); 279 callback.Run(status, scoped_ptr<base::Value>(), session_id); 280 } else { 281 iter->second->message_loop() 282 ->PostTask(FROM_HERE, 283 base::Bind(&ExecuteSessionCommandOnSessionThread, 284 command, 285 return_ok_without_session, 286 base::Passed(make_scoped_ptr(params.DeepCopy())), 287 base::MessageLoopProxy::current(), 288 callback, 289 base::Bind(&TerminateSessionThreadOnCommandThread, 290 session_thread_map, 291 session_id))); 292 } 293 } 294 295 namespace internal { 296 297 void CreateSessionOnSessionThreadForTesting(const std::string& id) { 298 lazy_tls_session.Pointer()->Set(new Session(id)); 299 } 300 301 } // namespace internal 302