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