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"
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"
22 // TODO(port): Enable when dialog_delegate is ported.
23 #include "ui/views/window/dialog_delegate.h"
26 using base::TimeDelta
;
27 using base::TimeTicks
;
31 const char kChannelErrorVersionString
[] = "***CHANNEL_ERROR***";
33 // This object allows messages received on the background thread to be
35 class AutomationMessageFilter
: public IPC::ChannelProxy::MessageFilter
{
37 explicit AutomationMessageFilter(AutomationProxy
* server
) : server_(server
) {}
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
{
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
,
50 IPC_MESSAGE_HANDLER_GENERIC(
51 AutomationMsg_InvalidateHandle
, server_
->InvalidateHandle(message
))
52 IPC_MESSAGE_UNHANDLED(handled
= false)
58 virtual void OnFilterAdded(IPC::Channel
* channel
) OVERRIDE
{
59 server_
->SetChannel(channel
);
62 virtual void OnFilterRemoved() OVERRIDE
{
63 server_
->ResetChannel();
66 virtual void OnChannelError() OVERRIDE
{
67 server_
->SignalAppLaunch(kChannelErrorVersionString
);
68 server_
->SignalNewTabUITab(-1);
72 void NewTabLoaded(int load_time
) {
73 server_
->SignalNewTabUITab(load_time
);
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.";
86 server_
->SignalAppLaunch(server_version
);
89 AutomationProxy
* server_
;
91 DISALLOW_COPY_AND_ASSIGN(AutomationMessageFilter
);
94 } // anonymous namespace
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();
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.
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;
134 std::ostringstream buf
;
135 buf
<< "ChromeTestingInterface:" << base::GetCurrentProcId() <<
136 "." << ++channel_counter
;
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
);
150 void AutomationProxy::InitializeChannel(const std::string
& channel_id
,
151 bool use_named_interface
) {
152 DCHECK(shutdown_event_
.get() != NULL
);
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
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));
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.
169 use_named_interface
? IPC::Channel::MODE_NAMED_CLIENT
170 : IPC::Channel::MODE_SERVER
,
171 true /* create_pipe_now */);
174 void AutomationProxy::InitializeHandleTracker() {
175 tracker_
.reset(new AutomationHandleTracker());
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
186 chrome::VersionInfo version_info
;
187 DCHECK(version_info
.is_valid());
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
;
197 result
= AUTOMATION_TIMEOUT
;
202 void AutomationProxy::SignalAppLaunch(const std::string
& version_string
) {
203 server_version_
= version_string
;
204 app_launched_
.Signal();
207 bool AutomationProxy::WaitForProcessLauncherThreadToGoIdle() {
208 return Send(new AutomationMsg_WaitForProcessLauncherThreadToGoIdle());
211 bool AutomationProxy::WaitForInitialLoads() {
212 return initial_loads_complete_
.TimedWait(action_timeout_
);
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();
224 void AutomationProxy::SignalInitialLoads() {
225 initial_loads_complete_
.Signal();
228 void AutomationProxy::SignalNewTabUITab(int load_time
) {
229 new_tab_ui_load_time_
= load_time
;
230 new_tab_ui_load_complete_
.Signal();
233 bool AutomationProxy::GetBrowserWindowCount(int* num_windows
) {
239 return Send(new AutomationMsg_BrowserWindowCount(num_windows
));
242 bool AutomationProxy::GetNormalBrowserWindowCount(int* num_windows
) {
248 return Send(new AutomationMsg_NormalBrowserWindowCount(num_windows
));
251 bool AutomationProxy::WaitForWindowCountToBecome(int count
) {
252 bool wait_success
= false;
253 if (!Send(new AutomationMsg_WaitForBrowserWindowCountToBecome(
254 count
, &wait_success
))) {
260 bool AutomationProxy::IsURLDisplayed(GURL url
) {
262 if (!GetBrowserWindowCount(&window_count
))
265 for (int i
= 0; i
< window_count
; i
++) {
266 scoped_refptr
<BrowserProxy
> window
= GetBrowserWindow(i
);
271 if (!window
->GetTabCount(&tab_count
))
274 for (int j
= 0; j
< tab_count
; j
++) {
275 scoped_refptr
<TabProxy
> tab
= window
->GetTab(j
);
280 if (!tab
->GetCurrentURL(&tab_url
))
291 bool AutomationProxy::GetMetricEventDuration(const std::string
& event_name
,
293 return Send(new AutomationMsg_GetMetricEventDuration(event_name
,
297 void AutomationProxy::Disconnect() {
298 DCHECK(shutdown_event_
.get() != NULL
);
299 shutdown_event_
->Signal();
303 bool AutomationProxy::OnMessageReceived(const IPC::Message
& msg
) {
304 // This won't get called unless AutomationProxy is run from
305 // inside a message loop.
310 void AutomationProxy::OnChannelError() {
311 LOG(ERROR
) << "Channel error in AutomationProxy.";
312 if (disconnect_on_failure_
)
316 scoped_refptr
<BrowserProxy
> AutomationProxy::GetBrowserWindow(
319 if (!Send(new AutomationMsg_BrowserWindow(window_index
, &handle
)))
322 return ProxyObjectFromHandle
<BrowserProxy
>(handle
);
325 IPC::SyncChannel
* AutomationProxy::channel() {
326 return channel_
.get();
329 bool AutomationProxy::Send(IPC::Message
* message
) {
331 static_cast<int>(action_timeout_
.InMilliseconds()));
334 bool AutomationProxy::Send(IPC::Message
* message
, int timeout_ms
) {
335 if (!channel_
.get()) {
336 LOG(ERROR
) << "Automation channel has been closed; dropping message!";
341 bool success
= channel_
->SendWithTimeout(message
, timeout_ms
);
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!";
356 void AutomationProxy::InvalidateHandle(const IPC::Message
& message
) {
357 PickleIterator
iter(message
);
360 if (message
.ReadInt(&iter
, &handle
)) {
361 tracker_
->InvalidateHandle(handle
);
365 bool AutomationProxy::OpenNewBrowserWindow(Browser::Type type
, bool show
) {
367 new AutomationMsg_OpenNewBrowserWindowOfType(static_cast<int>(type
),
371 template <class T
> scoped_refptr
<T
> AutomationProxy::ProxyObjectFromHandle(
376 // Get AddRef-ed pointer to the object if handle is already seen.
377 T
* p
= static_cast<T
*>(tracker_
->GetResource(handle
));
379 p
= new T(this, tracker_
.get(), handle
);
383 // Since there is no scoped_refptr::attach.
384 scoped_refptr
<T
> result
;
389 void AutomationProxy::SetChannel(IPC::Channel
* channel
) {
391 tracker_
->put_channel(channel
);
394 void AutomationProxy::ResetChannel() {
396 tracker_
->put_channel(NULL
);
399 bool AutomationProxy::BeginTracing(const std::string
& category_patterns
) {
401 bool send_success
= Send(new AutomationMsg_BeginTracing(category_patterns
,
403 return send_success
&& result
;
406 bool AutomationProxy::EndTracing(std::string
* json_trace_output
) {
407 bool success
= false;
409 if (!Send(new AutomationMsg_EndTracing(&path
, &success
)) || !success
)
412 bool ok
= base::ReadFileToString(path
, json_trace_output
);
414 base::DeleteFile(path
, false);
418 bool AutomationProxy::SendJSONRequest(const std::string
& request
,
420 std::string
* response
) {
422 if (!Send(new AutomationMsg_SendJSONRequest(-1, request
, response
, &result
),