Allow overlapping sync and async startup requests
[chromium-blink-merge.git] / chrome / test / automation / automation_proxy.cc
blob63acdfd09206c703697e397cc1b09bda2e478090
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.
5 #include "chrome/test/automation/automation_proxy.h"
7 #include <sstream>
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
28 using base::TimeDelta;
29 using base::TimeTicks;
31 namespace {
33 const char kChannelErrorVersionString[] = "***CHANNEL_ERROR***";
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) {}
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()
57 return handled;
60 virtual void OnFilterAdded(IPC::Channel* channel) OVERRIDE {
61 server_->SetChannel(channel);
64 virtual void OnFilterRemoved() OVERRIDE {
65 server_->ResetChannel();
68 virtual void OnChannelError() OVERRIDE {
69 server_->SignalAppLaunch(kChannelErrorVersionString);
70 server_->SignalNewTabUITab(-1);
73 private:
74 void NewTabLoaded(int load_time) {
75 server_->SignalNewTabUITab(load_time);
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.";
88 server_->SignalAppLaunch(server_version);
91 AutomationProxy* server_;
93 DISALLOW_COPY_AND_ASSIGN(AutomationMessageFilter);
96 } // anonymous namespace
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();
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();
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;
136 std::ostringstream buf;
137 buf << "ChromeTestingInterface:" << base::GetCurrentProcId() <<
138 "." << ++channel_counter;
139 return buf.str();
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);
152 void AutomationProxy::InitializeChannel(const std::string& channel_id,
153 bool use_named_interface) {
154 DCHECK(shutdown_event_.get() != NULL);
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));
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 */);
176 void AutomationProxy::InitializeHandleTracker() {
177 tracker_.reset(new AutomationHandleTracker());
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());
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;
198 } else {
199 result = AUTOMATION_TIMEOUT;
201 return result;
204 void AutomationProxy::SignalAppLaunch(const std::string& version_string) {
205 server_version_ = version_string;
206 app_launched_.Signal();
209 bool AutomationProxy::WaitForProcessLauncherThreadToGoIdle() {
210 return Send(new AutomationMsg_WaitForProcessLauncherThreadToGoIdle());
213 bool AutomationProxy::WaitForInitialLoads() {
214 return initial_loads_complete_.TimedWait(action_timeout_);
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;
223 return false;
226 void AutomationProxy::SignalInitialLoads() {
227 initial_loads_complete_.Signal();
230 void AutomationProxy::SignalNewTabUITab(int load_time) {
231 new_tab_ui_load_time_ = load_time;
232 new_tab_ui_load_complete_.Signal();
235 bool AutomationProxy::GetBrowserWindowCount(int* num_windows) {
236 if (!num_windows) {
237 NOTREACHED();
238 return false;
241 return Send(new AutomationMsg_BrowserWindowCount(num_windows));
244 bool AutomationProxy::GetNormalBrowserWindowCount(int* num_windows) {
245 if (!num_windows) {
246 NOTREACHED();
247 return false;
250 return Send(new AutomationMsg_NormalBrowserWindowCount(num_windows));
253 bool AutomationProxy::WaitForWindowCountToBecome(int count) {
254 bool wait_success = false;
255 if (!Send(new AutomationMsg_WaitForBrowserWindowCountToBecome(
256 count, &wait_success))) {
257 return false;
259 return wait_success;
262 bool AutomationProxy::IsURLDisplayed(GURL url) {
263 int window_count;
264 if (!GetBrowserWindowCount(&window_count))
265 return false;
267 for (int i = 0; i < window_count; i++) {
268 scoped_refptr<BrowserProxy> window = GetBrowserWindow(i);
269 if (!window.get())
270 break;
272 int tab_count;
273 if (!window->GetTabCount(&tab_count))
274 continue;
276 for (int j = 0; j < tab_count; j++) {
277 scoped_refptr<TabProxy> tab = window->GetTab(j);
278 if (!tab.get())
279 break;
281 GURL tab_url;
282 if (!tab->GetCurrentURL(&tab_url))
283 continue;
285 if (tab_url == url)
286 return true;
290 return false;
293 bool AutomationProxy::GetMetricEventDuration(const std::string& event_name,
294 int* duration_ms) {
295 return Send(new AutomationMsg_GetMetricEventDuration(event_name,
296 duration_ms));
299 bool AutomationProxy::SendProxyConfig(const std::string& new_proxy_config) {
300 return Send(new AutomationMsg_SetProxyConfig(new_proxy_config));
303 void AutomationProxy::Disconnect() {
304 DCHECK(shutdown_event_.get() != NULL);
305 shutdown_event_->Signal();
306 channel_.reset();
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;
316 void AutomationProxy::OnChannelError() {
317 LOG(ERROR) << "Channel error in AutomationProxy.";
318 if (disconnect_on_failure_)
319 Disconnect();
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;
328 return ProxyObjectFromHandle<BrowserProxy>(handle);
331 IPC::SyncChannel* AutomationProxy::channel() {
332 return channel_.get();
335 bool AutomationProxy::Send(IPC::Message* message) {
336 return Send(message,
337 static_cast<int>(action_timeout_.InMilliseconds()));
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;
347 bool success = channel_->SendWithTimeout(message, timeout_ms);
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();
359 return success;
362 void AutomationProxy::InvalidateHandle(const IPC::Message& message) {
363 PickleIterator iter(message);
364 int handle;
366 if (message.ReadInt(&iter, &handle)) {
367 tracker_->InvalidateHandle(handle);
371 bool AutomationProxy::OpenNewBrowserWindow(Browser::Type type, bool show) {
372 return Send(
373 new AutomationMsg_OpenNewBrowserWindowOfType(static_cast<int>(type),
374 show));
377 template <class T> scoped_refptr<T> AutomationProxy::ProxyObjectFromHandle(
378 int handle) {
379 if (!handle)
380 return NULL;
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();
389 // Since there is no scoped_refptr::attach.
390 scoped_refptr<T> result;
391 result.swap(&p);
392 return result;
395 void AutomationProxy::SetChannel(IPC::Channel* channel) {
396 if (tracker_.get())
397 tracker_->put_channel(channel);
400 void AutomationProxy::ResetChannel() {
401 if (tracker_.get())
402 tracker_->put_channel(NULL);
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;
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;
419 std::string chunk;
420 base::debug::TraceResultBuffer buffer;
421 base::debug::TraceResultBuffer::SimpleOutput output;
422 buffer.SetOutputCallback(output.GetCallback());
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);
436 buffer.Finish();
438 *json_trace_output = output.json_output;
439 return true;
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;