普通文本  |  432行  |  13.27 KB

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/test/automation/automation_proxy.h"

#include <sstream>

#include "base/basictypes.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/platform_thread.h"
#include "chrome/common/automation_constants.h"
#include "chrome/common/automation_messages.h"
#include "chrome/common/chrome_version_info.h"
#include "chrome/test/automation/browser_proxy.h"
#include "chrome/test/automation/tab_proxy.h"
#include "chrome/test/automation/window_proxy.h"
#include "ipc/ipc_descriptors.h"
#if defined(OS_WIN)
// TODO(port): Enable when dialog_delegate is ported.
#include "ui/views/window/dialog_delegate.h"
#endif

using base::TimeDelta;
using base::TimeTicks;

namespace {

const char kChannelErrorVersionString[] = "***CHANNEL_ERROR***";

// This object allows messages received on the background thread to be
// properly triaged.
class AutomationMessageFilter : public IPC::ChannelProxy::MessageFilter {
 public:
  explicit AutomationMessageFilter(AutomationProxy* server) : server_(server) {}

  // Return true to indicate that the message was handled, or false to let
  // the message be handled in the default way.
  virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE {
    bool handled = true;
    IPC_BEGIN_MESSAGE_MAP(AutomationMessageFilter, message)
      IPC_MESSAGE_HANDLER_GENERIC(AutomationMsg_Hello,
                                  OnAutomationHello(message))
      IPC_MESSAGE_HANDLER_GENERIC(
        AutomationMsg_InitialLoadsComplete, server_->SignalInitialLoads())
      IPC_MESSAGE_HANDLER(AutomationMsg_InitialNewTabUILoadComplete,
                          NewTabLoaded)
      IPC_MESSAGE_HANDLER_GENERIC(
        AutomationMsg_InvalidateHandle, server_->InvalidateHandle(message))
      IPC_MESSAGE_UNHANDLED(handled = false)
    IPC_END_MESSAGE_MAP()

    return handled;
  }

  virtual void OnFilterAdded(IPC::Channel* channel) OVERRIDE {
    server_->SetChannel(channel);
  }

  virtual void OnFilterRemoved() OVERRIDE {
    server_->ResetChannel();
  }

  virtual void OnChannelError() OVERRIDE {
    server_->SignalAppLaunch(kChannelErrorVersionString);
    server_->SignalNewTabUITab(-1);
  }

 private:
  void NewTabLoaded(int load_time) {
    server_->SignalNewTabUITab(load_time);
  }

  void OnAutomationHello(const IPC::Message& hello_message) {
    std::string server_version;
    PickleIterator iter(hello_message);
    if (!hello_message.ReadString(&iter, &server_version)) {
      // We got an AutomationMsg_Hello from an old automation provider
      // that doesn't send version info. Leave server_version as an empty
      // string to signal a version mismatch.
      LOG(ERROR) << "Pre-versioning protocol detected in automation provider.";
    }

    server_->SignalAppLaunch(server_version);
  }

  AutomationProxy* server_;

  DISALLOW_COPY_AND_ASSIGN(AutomationMessageFilter);
};

}  // anonymous namespace


AutomationProxy::AutomationProxy(base::TimeDelta action_timeout,
                                 bool disconnect_on_failure)
    : app_launched_(true, false),
      initial_loads_complete_(true, false),
      new_tab_ui_load_complete_(true, false),
      shutdown_event_(new base::WaitableEvent(true, false)),
      perform_version_check_(false),
      disconnect_on_failure_(disconnect_on_failure),
      channel_disconnected_on_failure_(false),
      action_timeout_(action_timeout),
      listener_thread_id_(0) {
  // base::WaitableEvent::TimedWait() will choke if we give it a negative value.
  // Zero also seems unreasonable, since we need to wait for IPC, but at
  // least it is legal... ;-)
  DCHECK_GE(action_timeout.InMilliseconds(), 0);
  listener_thread_id_ = base::PlatformThread::CurrentId();
  InitializeHandleTracker();
  InitializeThread();
}

AutomationProxy::~AutomationProxy() {
  // Destruction order is important. Thread has to outlive the channel and
  // tracker has to outlive the thread since we access the tracker inside
  // AutomationMessageFilter::OnMessageReceived.
  Disconnect();
  thread_.reset();
  tracker_.reset();
}

std::string AutomationProxy::GenerateChannelID() {
  // The channel counter keeps us out of trouble if we create and destroy
  // several AutomationProxies sequentially over the course of a test run.
  // (Creating the channel sometimes failed before when running a lot of
  // tests in sequence, and our theory is that sometimes the channel ID
  // wasn't getting freed up in time for the next test.)
  static int channel_counter = 0;

  std::ostringstream buf;
  buf << "ChromeTestingInterface:" << base::GetCurrentProcId() <<
         "." << ++channel_counter;
  return buf.str();
}

void AutomationProxy::InitializeThread() {
  scoped_ptr<base::Thread> thread(
      new base::Thread("AutomationProxy_BackgroundThread"));
  base::Thread::Options options;
  options.message_loop_type = base::MessageLoop::TYPE_IO;
  bool thread_result = thread->StartWithOptions(options);
  DCHECK(thread_result);
  thread_.swap(thread);
}

void AutomationProxy::InitializeChannel(const std::string& channel_id,
                                        bool use_named_interface) {
  DCHECK(shutdown_event_.get() != NULL);

  // TODO(iyengar)
  // The shutdown event could be global on the same lines as the automation
  // provider, where we use the shutdown event provided by the chrome browser
  // process.
  channel_.reset(new IPC::SyncChannel(this,  // we are the listener
                                      thread_->message_loop_proxy().get(),
                                      shutdown_event_.get()));
  channel_->AddFilter(new AutomationMessageFilter(this));

  // Create the pipe synchronously so that Chrome doesn't try to connect to an
  // unready server. Note this is done after adding a message filter to
  // guarantee that it doesn't miss any messages when we are the client.
  // See crbug.com/102894.
  channel_->Init(
      channel_id,
      use_named_interface ? IPC::Channel::MODE_NAMED_CLIENT
                          : IPC::Channel::MODE_SERVER,
      true /* create_pipe_now */);
}

void AutomationProxy::InitializeHandleTracker() {
  tracker_.reset(new AutomationHandleTracker());
}

AutomationLaunchResult AutomationProxy::WaitForAppLaunch() {
  AutomationLaunchResult result = AUTOMATION_SUCCESS;
  if (app_launched_.TimedWait(action_timeout_)) {
    if (server_version_ == kChannelErrorVersionString) {
      result = AUTOMATION_CHANNEL_ERROR;
    } else if (perform_version_check_) {
      // Obtain our own version number and compare it to what the automation
      // provider sent.
      chrome::VersionInfo version_info;
      DCHECK(version_info.is_valid());

      // Note that we use a simple string comparison since we expect the version
      // to be a punctuated numeric string. Consider using base/Version if we
      // ever need something more complicated here.
      if (server_version_ != version_info.Version()) {
        result = AUTOMATION_VERSION_MISMATCH;
      }
    }
  } else {
    result = AUTOMATION_TIMEOUT;
  }
  return result;
}

void AutomationProxy::SignalAppLaunch(const std::string& version_string) {
  server_version_ = version_string;
  app_launched_.Signal();
}

bool AutomationProxy::WaitForProcessLauncherThreadToGoIdle() {
  return Send(new AutomationMsg_WaitForProcessLauncherThreadToGoIdle());
}

bool AutomationProxy::WaitForInitialLoads() {
  return initial_loads_complete_.TimedWait(action_timeout_);
}

bool AutomationProxy::WaitForInitialNewTabUILoad(int* load_time) {
  if (new_tab_ui_load_complete_.TimedWait(action_timeout_)) {
    *load_time = new_tab_ui_load_time_;
    new_tab_ui_load_complete_.Reset();
    return true;
  }
  return false;
}

void AutomationProxy::SignalInitialLoads() {
  initial_loads_complete_.Signal();
}

void AutomationProxy::SignalNewTabUITab(int load_time) {
  new_tab_ui_load_time_ = load_time;
  new_tab_ui_load_complete_.Signal();
}

bool AutomationProxy::GetBrowserWindowCount(int* num_windows) {
  if (!num_windows) {
    NOTREACHED();
    return false;
  }

  return Send(new AutomationMsg_BrowserWindowCount(num_windows));
}

bool AutomationProxy::GetNormalBrowserWindowCount(int* num_windows) {
  if (!num_windows) {
    NOTREACHED();
    return false;
  }

  return Send(new AutomationMsg_NormalBrowserWindowCount(num_windows));
}

bool AutomationProxy::WaitForWindowCountToBecome(int count) {
  bool wait_success = false;
  if (!Send(new AutomationMsg_WaitForBrowserWindowCountToBecome(
                count, &wait_success))) {
    return false;
  }
  return wait_success;
}

bool AutomationProxy::IsURLDisplayed(GURL url) {
  int window_count;
  if (!GetBrowserWindowCount(&window_count))
    return false;

  for (int i = 0; i < window_count; i++) {
    scoped_refptr<BrowserProxy> window = GetBrowserWindow(i);
    if (!window.get())
      break;

    int tab_count;
    if (!window->GetTabCount(&tab_count))
      continue;

    for (int j = 0; j < tab_count; j++) {
      scoped_refptr<TabProxy> tab = window->GetTab(j);
      if (!tab.get())
        break;

      GURL tab_url;
      if (!tab->GetCurrentURL(&tab_url))
        continue;

      if (tab_url == url)
        return true;
    }
  }

  return false;
}

bool AutomationProxy::GetMetricEventDuration(const std::string& event_name,
                                             int* duration_ms) {
  return Send(new AutomationMsg_GetMetricEventDuration(event_name,
                                                       duration_ms));
}

bool AutomationProxy::SendProxyConfig(const std::string& new_proxy_config) {
  return Send(new AutomationMsg_SetProxyConfig(new_proxy_config));
}

void AutomationProxy::Disconnect() {
  DCHECK(shutdown_event_.get() != NULL);
  shutdown_event_->Signal();
  channel_.reset();
}

bool AutomationProxy::OnMessageReceived(const IPC::Message& msg) {
  // This won't get called unless AutomationProxy is run from
  // inside a message loop.
  NOTREACHED();
  return false;
}

void AutomationProxy::OnChannelError() {
  LOG(ERROR) << "Channel error in AutomationProxy.";
  if (disconnect_on_failure_)
    Disconnect();
}

scoped_refptr<BrowserProxy> AutomationProxy::GetBrowserWindow(
    int window_index) {
  int handle = 0;
  if (!Send(new AutomationMsg_BrowserWindow(window_index, &handle)))
    return NULL;

  return ProxyObjectFromHandle<BrowserProxy>(handle);
}

IPC::SyncChannel* AutomationProxy::channel() {
  return channel_.get();
}

bool AutomationProxy::Send(IPC::Message* message) {
  return Send(message,
    static_cast<int>(action_timeout_.InMilliseconds()));
}

bool AutomationProxy::Send(IPC::Message* message, int timeout_ms) {
  if (!channel_.get()) {
    LOG(ERROR) << "Automation channel has been closed; dropping message!";
    delete message;
    return false;
  }

  bool success = channel_->SendWithTimeout(message, timeout_ms);

  if (!success && disconnect_on_failure_) {
    // Send failed (possibly due to a timeout). Browser is likely in a weird
    // state, and further IPC requests are extremely likely to fail (possibly
    // timeout, which would make tests slower). Disconnect the channel now
    // to avoid the slowness.
    channel_disconnected_on_failure_ = true;
    LOG(ERROR) << "Disconnecting channel after error!";
    Disconnect();
  }

  return success;
}

void AutomationProxy::InvalidateHandle(const IPC::Message& message) {
  PickleIterator iter(message);
  int handle;

  if (message.ReadInt(&iter, &handle)) {
    tracker_->InvalidateHandle(handle);
  }
}

bool AutomationProxy::OpenNewBrowserWindow(Browser::Type type, bool show) {
  return Send(
      new AutomationMsg_OpenNewBrowserWindowOfType(static_cast<int>(type),
                                                   show));
}

template <class T> scoped_refptr<T> AutomationProxy::ProxyObjectFromHandle(
    int handle) {
  if (!handle)
    return NULL;

  // Get AddRef-ed pointer to the object if handle is already seen.
  T* p = static_cast<T*>(tracker_->GetResource(handle));
  if (!p) {
    p = new T(this, tracker_.get(), handle);
    p->AddRef();
  }

  // Since there is no scoped_refptr::attach.
  scoped_refptr<T> result;
  result.swap(&p);
  return result;
}

void AutomationProxy::SetChannel(IPC::Channel* channel) {
  if (tracker_.get())
    tracker_->put_channel(channel);
}

void AutomationProxy::ResetChannel() {
  if (tracker_.get())
    tracker_->put_channel(NULL);
}

bool AutomationProxy::BeginTracing(const std::string& category_patterns) {
  bool result = false;
  bool send_success = Send(new AutomationMsg_BeginTracing(category_patterns,
                                                          &result));
  return send_success && result;
}

bool AutomationProxy::EndTracing(std::string* json_trace_output) {
  bool success = false;
  base::FilePath path;
  if (!Send(new AutomationMsg_EndTracing(&path, &success)) || !success)
    return false;

  bool ok = base::ReadFileToString(path, json_trace_output);
  DCHECK(ok);
  base::DeleteFile(path, false);
  return true;
}

bool AutomationProxy::SendJSONRequest(const std::string& request,
                                      int timeout_ms,
                                      std::string* response) {
  bool result = false;
  if (!Send(new AutomationMsg_SendJSONRequest(-1, request, response, &result),
            timeout_ms))
    return false;
  return result;
}