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