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
27using base::TimeDelta;
28using base::TimeTicks;
29
30namespace {
31
32const char kChannelErrorVersionString[] = "***CHANNEL_ERROR***";
33
34// This object allows messages received on the background thread to be
35// properly triaged.
36class AutomationMessageFilter : public IPC::ChannelProxy::MessageFilter {
37 public:
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.
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
59  virtual void OnFilterAdded(IPC::Channel* channel) OVERRIDE {
60    server_->SetChannel(channel);
61  }
62
63  virtual void OnFilterRemoved() OVERRIDE {
64    server_->ResetChannel();
65  }
66
67  virtual void OnChannelError() OVERRIDE {
68    server_->SignalAppLaunch(kChannelErrorVersionString);
69    server_->SignalNewTabUITab(-1);
70  }
71
72 private:
73  void NewTabLoaded(int load_time) {
74    server_->SignalNewTabUITab(load_time);
75  }
76
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
98AutomationProxy::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
118AutomationProxy::~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
127std::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
141void 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
151void 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
175void AutomationProxy::InitializeHandleTracker() {
176  tracker_.reset(new AutomationHandleTracker());
177}
178
179AutomationLaunchResult 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
203void AutomationProxy::SignalAppLaunch(const std::string& version_string) {
204  server_version_ = version_string;
205  app_launched_.Signal();
206}
207
208bool AutomationProxy::WaitForProcessLauncherThreadToGoIdle() {
209  return Send(new AutomationMsg_WaitForProcessLauncherThreadToGoIdle());
210}
211
212bool AutomationProxy::WaitForInitialLoads() {
213  return initial_loads_complete_.TimedWait(action_timeout_);
214}
215
216bool 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
225void AutomationProxy::SignalInitialLoads() {
226  initial_loads_complete_.Signal();
227}
228
229void AutomationProxy::SignalNewTabUITab(int load_time) {
230  new_tab_ui_load_time_ = load_time;
231  new_tab_ui_load_complete_.Signal();
232}
233
234bool 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
243bool 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
252bool 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
261bool 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
292bool AutomationProxy::GetMetricEventDuration(const std::string& event_name,
293                                             int* duration_ms) {
294  return Send(new AutomationMsg_GetMetricEventDuration(event_name,
295                                                       duration_ms));
296}
297
298bool AutomationProxy::SendProxyConfig(const std::string& new_proxy_config) {
299  return Send(new AutomationMsg_SetProxyConfig(new_proxy_config));
300}
301
302void AutomationProxy::Disconnect() {
303  DCHECK(shutdown_event_.get() != NULL);
304  shutdown_event_->Signal();
305  channel_.reset();
306}
307
308bool 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
315void AutomationProxy::OnChannelError() {
316  LOG(ERROR) << "Channel error in AutomationProxy.";
317  if (disconnect_on_failure_)
318    Disconnect();
319}
320
321scoped_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
330IPC::SyncChannel* AutomationProxy::channel() {
331  return channel_.get();
332}
333
334bool AutomationProxy::Send(IPC::Message* message) {
335  return Send(message,
336    static_cast<int>(action_timeout_.InMilliseconds()));
337}
338
339bool 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
361void 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
370bool AutomationProxy::OpenNewBrowserWindow(Browser::Type type, bool show) {
371  return Send(
372      new AutomationMsg_OpenNewBrowserWindowOfType(static_cast<int>(type),
373                                                   show));
374}
375
376template <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
394void AutomationProxy::SetChannel(IPC::Channel* channel) {
395  if (tracker_.get())
396    tracker_->put_channel(channel);
397}
398
399void AutomationProxy::ResetChannel() {
400  if (tracker_.get())
401    tracker_->put_channel(NULL);
402}
403
404bool 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
411bool 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
423bool 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