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