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