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/automation/automation_proxy.h" 6 7 #include <sstream> 8 9 #include "base/basictypes.h" 10 #include "base/file_util.h" 11 #include "base/logging.h" 12 #include "base/memory/ref_counted.h" 13 #include "base/synchronization/waitable_event.h" 14 #include "base/threading/platform_thread.h" 15 #include "chrome/common/automation_constants.h" 16 #include "chrome/common/automation_messages.h" 17 #include "chrome/common/chrome_version_info.h" 18 #include "chrome/test/automation/browser_proxy.h" 19 #include "chrome/test/automation/tab_proxy.h" 20 #include "chrome/test/automation/window_proxy.h" 21 #include "ipc/ipc_descriptors.h" 22 #if defined(OS_WIN) 23 // TODO(port): Enable when dialog_delegate is ported. 24 #include "ui/views/window/dialog_delegate.h" 25 #endif 26 27 using base::TimeDelta; 28 using base::TimeTicks; 29 30 namespace { 31 32 const char kChannelErrorVersionString[] = "***CHANNEL_ERROR***"; 33 34 // This object allows messages received on the background thread to be 35 // properly triaged. 36 class AutomationMessageFilter : public IPC::ChannelProxy::MessageFilter { 37 public: 38 explicit AutomationMessageFilter(AutomationProxy* server) : server_(server) {} 39 40 // Return true to indicate that the message was handled, or false to let 41 // the message be handled in the default way. 42 virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE { 43 bool handled = true; 44 IPC_BEGIN_MESSAGE_MAP(AutomationMessageFilter, message) 45 IPC_MESSAGE_HANDLER_GENERIC(AutomationMsg_Hello, 46 OnAutomationHello(message)) 47 IPC_MESSAGE_HANDLER_GENERIC( 48 AutomationMsg_InitialLoadsComplete, server_->SignalInitialLoads()) 49 IPC_MESSAGE_HANDLER(AutomationMsg_InitialNewTabUILoadComplete, 50 NewTabLoaded) 51 IPC_MESSAGE_HANDLER_GENERIC( 52 AutomationMsg_InvalidateHandle, server_->InvalidateHandle(message)) 53 IPC_MESSAGE_UNHANDLED(handled = false) 54 IPC_END_MESSAGE_MAP() 55 56 return handled; 57 } 58 59 virtual void OnFilterAdded(IPC::Channel* channel) OVERRIDE { 60 server_->SetChannel(channel); 61 } 62 63 virtual void OnFilterRemoved() OVERRIDE { 64 server_->ResetChannel(); 65 } 66 67 virtual void OnChannelError() OVERRIDE { 68 server_->SignalAppLaunch(kChannelErrorVersionString); 69 server_->SignalNewTabUITab(-1); 70 } 71 72 private: 73 void NewTabLoaded(int load_time) { 74 server_->SignalNewTabUITab(load_time); 75 } 76 77 void OnAutomationHello(const IPC::Message& hello_message) { 78 std::string server_version; 79 PickleIterator iter(hello_message); 80 if (!hello_message.ReadString(&iter, &server_version)) { 81 // We got an AutomationMsg_Hello from an old automation provider 82 // that doesn't send version info. Leave server_version as an empty 83 // string to signal a version mismatch. 84 LOG(ERROR) << "Pre-versioning protocol detected in automation provider."; 85 } 86 87 server_->SignalAppLaunch(server_version); 88 } 89 90 AutomationProxy* server_; 91 92 DISALLOW_COPY_AND_ASSIGN(AutomationMessageFilter); 93 }; 94 95 } // anonymous namespace 96 97 98 AutomationProxy::AutomationProxy(base::TimeDelta action_timeout, 99 bool disconnect_on_failure) 100 : app_launched_(true, false), 101 initial_loads_complete_(true, false), 102 new_tab_ui_load_complete_(true, false), 103 shutdown_event_(new base::WaitableEvent(true, false)), 104 perform_version_check_(false), 105 disconnect_on_failure_(disconnect_on_failure), 106 channel_disconnected_on_failure_(false), 107 action_timeout_(action_timeout), 108 listener_thread_id_(0) { 109 // base::WaitableEvent::TimedWait() will choke if we give it a negative value. 110 // Zero also seems unreasonable, since we need to wait for IPC, but at 111 // least it is legal... ;-) 112 DCHECK_GE(action_timeout.InMilliseconds(), 0); 113 listener_thread_id_ = base::PlatformThread::CurrentId(); 114 InitializeHandleTracker(); 115 InitializeThread(); 116 } 117 118 AutomationProxy::~AutomationProxy() { 119 // Destruction order is important. Thread has to outlive the channel and 120 // tracker has to outlive the thread since we access the tracker inside 121 // AutomationMessageFilter::OnMessageReceived. 122 Disconnect(); 123 thread_.reset(); 124 tracker_.reset(); 125 } 126 127 std::string AutomationProxy::GenerateChannelID() { 128 // The channel counter keeps us out of trouble if we create and destroy 129 // several AutomationProxies sequentially over the course of a test run. 130 // (Creating the channel sometimes failed before when running a lot of 131 // tests in sequence, and our theory is that sometimes the channel ID 132 // wasn't getting freed up in time for the next test.) 133 static int channel_counter = 0; 134 135 std::ostringstream buf; 136 buf << "ChromeTestingInterface:" << base::GetCurrentProcId() << 137 "." << ++channel_counter; 138 return buf.str(); 139 } 140 141 void AutomationProxy::InitializeThread() { 142 scoped_ptr<base::Thread> thread( 143 new base::Thread("AutomationProxy_BackgroundThread")); 144 base::Thread::Options options; 145 options.message_loop_type = base::MessageLoop::TYPE_IO; 146 bool thread_result = thread->StartWithOptions(options); 147 DCHECK(thread_result); 148 thread_.swap(thread); 149 } 150 151 void AutomationProxy::InitializeChannel(const std::string& channel_id, 152 bool use_named_interface) { 153 DCHECK(shutdown_event_.get() != NULL); 154 155 // TODO(iyengar) 156 // The shutdown event could be global on the same lines as the automation 157 // provider, where we use the shutdown event provided by the chrome browser 158 // process. 159 channel_.reset(new IPC::SyncChannel(this, // we are the listener 160 thread_->message_loop_proxy().get(), 161 shutdown_event_.get())); 162 channel_->AddFilter(new AutomationMessageFilter(this)); 163 164 // Create the pipe synchronously so that Chrome doesn't try to connect to an 165 // unready server. Note this is done after adding a message filter to 166 // guarantee that it doesn't miss any messages when we are the client. 167 // See crbug.com/102894. 168 channel_->Init( 169 channel_id, 170 use_named_interface ? IPC::Channel::MODE_NAMED_CLIENT 171 : IPC::Channel::MODE_SERVER, 172 true /* create_pipe_now */); 173 } 174 175 void AutomationProxy::InitializeHandleTracker() { 176 tracker_.reset(new AutomationHandleTracker()); 177 } 178 179 AutomationLaunchResult AutomationProxy::WaitForAppLaunch() { 180 AutomationLaunchResult result = AUTOMATION_SUCCESS; 181 if (app_launched_.TimedWait(action_timeout_)) { 182 if (server_version_ == kChannelErrorVersionString) { 183 result = AUTOMATION_CHANNEL_ERROR; 184 } else if (perform_version_check_) { 185 // Obtain our own version number and compare it to what the automation 186 // provider sent. 187 chrome::VersionInfo version_info; 188 DCHECK(version_info.is_valid()); 189 190 // Note that we use a simple string comparison since we expect the version 191 // to be a punctuated numeric string. Consider using base/Version if we 192 // ever need something more complicated here. 193 if (server_version_ != version_info.Version()) { 194 result = AUTOMATION_VERSION_MISMATCH; 195 } 196 } 197 } else { 198 result = AUTOMATION_TIMEOUT; 199 } 200 return result; 201 } 202 203 void AutomationProxy::SignalAppLaunch(const std::string& version_string) { 204 server_version_ = version_string; 205 app_launched_.Signal(); 206 } 207 208 bool AutomationProxy::WaitForProcessLauncherThreadToGoIdle() { 209 return Send(new AutomationMsg_WaitForProcessLauncherThreadToGoIdle()); 210 } 211 212 bool AutomationProxy::WaitForInitialLoads() { 213 return initial_loads_complete_.TimedWait(action_timeout_); 214 } 215 216 bool AutomationProxy::WaitForInitialNewTabUILoad(int* load_time) { 217 if (new_tab_ui_load_complete_.TimedWait(action_timeout_)) { 218 *load_time = new_tab_ui_load_time_; 219 new_tab_ui_load_complete_.Reset(); 220 return true; 221 } 222 return false; 223 } 224 225 void AutomationProxy::SignalInitialLoads() { 226 initial_loads_complete_.Signal(); 227 } 228 229 void AutomationProxy::SignalNewTabUITab(int load_time) { 230 new_tab_ui_load_time_ = load_time; 231 new_tab_ui_load_complete_.Signal(); 232 } 233 234 bool AutomationProxy::GetBrowserWindowCount(int* num_windows) { 235 if (!num_windows) { 236 NOTREACHED(); 237 return false; 238 } 239 240 return Send(new AutomationMsg_BrowserWindowCount(num_windows)); 241 } 242 243 bool AutomationProxy::GetNormalBrowserWindowCount(int* num_windows) { 244 if (!num_windows) { 245 NOTREACHED(); 246 return false; 247 } 248 249 return Send(new AutomationMsg_NormalBrowserWindowCount(num_windows)); 250 } 251 252 bool AutomationProxy::WaitForWindowCountToBecome(int count) { 253 bool wait_success = false; 254 if (!Send(new AutomationMsg_WaitForBrowserWindowCountToBecome( 255 count, &wait_success))) { 256 return false; 257 } 258 return wait_success; 259 } 260 261 bool AutomationProxy::IsURLDisplayed(GURL url) { 262 int window_count; 263 if (!GetBrowserWindowCount(&window_count)) 264 return false; 265 266 for (int i = 0; i < window_count; i++) { 267 scoped_refptr<BrowserProxy> window = GetBrowserWindow(i); 268 if (!window.get()) 269 break; 270 271 int tab_count; 272 if (!window->GetTabCount(&tab_count)) 273 continue; 274 275 for (int j = 0; j < tab_count; j++) { 276 scoped_refptr<TabProxy> tab = window->GetTab(j); 277 if (!tab.get()) 278 break; 279 280 GURL tab_url; 281 if (!tab->GetCurrentURL(&tab_url)) 282 continue; 283 284 if (tab_url == url) 285 return true; 286 } 287 } 288 289 return false; 290 } 291 292 bool AutomationProxy::GetMetricEventDuration(const std::string& event_name, 293 int* duration_ms) { 294 return Send(new AutomationMsg_GetMetricEventDuration(event_name, 295 duration_ms)); 296 } 297 298 bool AutomationProxy::SendProxyConfig(const std::string& new_proxy_config) { 299 return Send(new AutomationMsg_SetProxyConfig(new_proxy_config)); 300 } 301 302 void AutomationProxy::Disconnect() { 303 DCHECK(shutdown_event_.get() != NULL); 304 shutdown_event_->Signal(); 305 channel_.reset(); 306 } 307 308 bool AutomationProxy::OnMessageReceived(const IPC::Message& msg) { 309 // This won't get called unless AutomationProxy is run from 310 // inside a message loop. 311 NOTREACHED(); 312 return false; 313 } 314 315 void AutomationProxy::OnChannelError() { 316 LOG(ERROR) << "Channel error in AutomationProxy."; 317 if (disconnect_on_failure_) 318 Disconnect(); 319 } 320 321 scoped_refptr<BrowserProxy> AutomationProxy::GetBrowserWindow( 322 int window_index) { 323 int handle = 0; 324 if (!Send(new AutomationMsg_BrowserWindow(window_index, &handle))) 325 return NULL; 326 327 return ProxyObjectFromHandle<BrowserProxy>(handle); 328 } 329 330 IPC::SyncChannel* AutomationProxy::channel() { 331 return channel_.get(); 332 } 333 334 bool AutomationProxy::Send(IPC::Message* message) { 335 return Send(message, 336 static_cast<int>(action_timeout_.InMilliseconds())); 337 } 338 339 bool AutomationProxy::Send(IPC::Message* message, int timeout_ms) { 340 if (!channel_.get()) { 341 LOG(ERROR) << "Automation channel has been closed; dropping message!"; 342 delete message; 343 return false; 344 } 345 346 bool success = channel_->SendWithTimeout(message, timeout_ms); 347 348 if (!success && disconnect_on_failure_) { 349 // Send failed (possibly due to a timeout). Browser is likely in a weird 350 // state, and further IPC requests are extremely likely to fail (possibly 351 // timeout, which would make tests slower). Disconnect the channel now 352 // to avoid the slowness. 353 channel_disconnected_on_failure_ = true; 354 LOG(ERROR) << "Disconnecting channel after error!"; 355 Disconnect(); 356 } 357 358 return success; 359 } 360 361 void AutomationProxy::InvalidateHandle(const IPC::Message& message) { 362 PickleIterator iter(message); 363 int handle; 364 365 if (message.ReadInt(&iter, &handle)) { 366 tracker_->InvalidateHandle(handle); 367 } 368 } 369 370 bool AutomationProxy::OpenNewBrowserWindow(Browser::Type type, bool show) { 371 return Send( 372 new AutomationMsg_OpenNewBrowserWindowOfType(static_cast<int>(type), 373 show)); 374 } 375 376 template <class T> scoped_refptr<T> AutomationProxy::ProxyObjectFromHandle( 377 int handle) { 378 if (!handle) 379 return NULL; 380 381 // Get AddRef-ed pointer to the object if handle is already seen. 382 T* p = static_cast<T*>(tracker_->GetResource(handle)); 383 if (!p) { 384 p = new T(this, tracker_.get(), handle); 385 p->AddRef(); 386 } 387 388 // Since there is no scoped_refptr::attach. 389 scoped_refptr<T> result; 390 result.swap(&p); 391 return result; 392 } 393 394 void AutomationProxy::SetChannel(IPC::Channel* channel) { 395 if (tracker_.get()) 396 tracker_->put_channel(channel); 397 } 398 399 void AutomationProxy::ResetChannel() { 400 if (tracker_.get()) 401 tracker_->put_channel(NULL); 402 } 403 404 bool AutomationProxy::BeginTracing(const std::string& category_patterns) { 405 bool result = false; 406 bool send_success = Send(new AutomationMsg_BeginTracing(category_patterns, 407 &result)); 408 return send_success && result; 409 } 410 411 bool AutomationProxy::EndTracing(std::string* json_trace_output) { 412 bool success = false; 413 base::FilePath path; 414 if (!Send(new AutomationMsg_EndTracing(&path, &success)) || !success) 415 return false; 416 417 bool ok = base::ReadFileToString(path, json_trace_output); 418 DCHECK(ok); 419 base::DeleteFile(path, false); 420 return true; 421 } 422 423 bool AutomationProxy::SendJSONRequest(const std::string& request, 424 int timeout_ms, 425 std::string* response) { 426 bool result = false; 427 if (!Send(new AutomationMsg_SendJSONRequest(-1, request, response, &result), 428 timeout_ms)) 429 return false; 430 return result; 431 } 432