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