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