Home | History | Annotate | Download | only in automation
      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:
     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 
     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 
    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 
    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 
    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 
    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 
    175 void AutomationProxy::InitializeHandleTracker() {
    176   tracker_.reset(new AutomationHandleTracker());
    177 }
    178 
    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 
    203 void AutomationProxy::SignalAppLaunch(const std::string& version_string) {
    204   server_version_ = version_string;
    205   app_launched_.Signal();
    206 }
    207 
    208 bool AutomationProxy::WaitForProcessLauncherThreadToGoIdle() {
    209   return Send(new AutomationMsg_WaitForProcessLauncherThreadToGoIdle());
    210 }
    211 
    212 bool AutomationProxy::WaitForInitialLoads() {
    213   return initial_loads_complete_.TimedWait(action_timeout_);
    214 }
    215 
    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 
    225 void AutomationProxy::SignalInitialLoads() {
    226   initial_loads_complete_.Signal();
    227 }
    228 
    229 void AutomationProxy::SignalNewTabUITab(int load_time) {
    230   new_tab_ui_load_time_ = load_time;
    231   new_tab_ui_load_complete_.Signal();
    232 }
    233 
    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 
    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 
    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 
    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 
    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 
    298 bool AutomationProxy::SendProxyConfig(const std::string& new_proxy_config) {
    299   return Send(new AutomationMsg_SetProxyConfig(new_proxy_config));
    300 }
    301 
    302 void AutomationProxy::Disconnect() {
    303   DCHECK(shutdown_event_.get() != NULL);
    304   shutdown_event_->Signal();
    305   channel_.reset();
    306 }
    307 
    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 
    315 void AutomationProxy::OnChannelError() {
    316   LOG(ERROR) << "Channel error in AutomationProxy.";
    317   if (disconnect_on_failure_)
    318     Disconnect();
    319 }
    320 
    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 
    330 IPC::SyncChannel* AutomationProxy::channel() {
    331   return channel_.get();
    332 }
    333 
    334 bool AutomationProxy::Send(IPC::Message* message) {
    335   return Send(message,
    336     static_cast<int>(action_timeout_.InMilliseconds()));
    337 }
    338 
    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 
    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 
    370 bool AutomationProxy::OpenNewBrowserWindow(Browser::Type type, bool show) {
    371   return Send(
    372       new AutomationMsg_OpenNewBrowserWindowOfType(static_cast<int>(type),
    373                                                    show));
    374 }
    375 
    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 
    394 void AutomationProxy::SetChannel(IPC::Channel* channel) {
    395   if (tracker_.get())
    396     tracker_->put_channel(channel);
    397 }
    398 
    399 void AutomationProxy::ResetChannel() {
    400   if (tracker_.get())
    401     tracker_->put_channel(NULL);
    402 }
    403 
    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 
    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 
    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