// Copyright (c) 2009 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_frame/chrome_frame_automation.h"

#include "base/callback.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/file_util.h"
#include "base/file_version_info.h"
#include "base/lock.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/process_util.h"
#include "base/singleton.h"
#include "base/string_util.h"
#include "base/sys_info.h"
#include "base/waitable_event.h"
#include "chrome/app/client_util.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_paths_internal.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/automation/tab_proxy.h"
#include "chrome_frame/chrome_launcher.h"
#include "chrome_frame/utils.h"
#include "chrome_frame/sync_msg_reply_dispatcher.h"

#ifdef NDEBUG
int64 kAutomationServerReasonableLaunchDelay = 1000;  // in milliseconds
#else
int64 kAutomationServerReasonableLaunchDelay = 1000 * 10;
#endif

int kDefaultSendUMADataInterval = 20000;  // in milliseconds.

static const wchar_t kUmaSendIntervalValue[] = L"UmaSendInterval";

// This lock ensures that histograms created by ChromeFrame are thread safe.
// The histograms created in ChromeFrame can be initialized on multiple
// threads.
Lock g_ChromeFrameHistogramLock;

class TabProxyNotificationMessageFilter
    : public IPC::ChannelProxy::MessageFilter {
 public:
  explicit TabProxyNotificationMessageFilter(AutomationHandleTracker* tracker)
      : tracker_(tracker) {
  }

  virtual bool OnMessageReceived(const IPC::Message& message) {
    if (message.is_reply())
      return false;

    int tab_handle = 0;
    if (!ChromeFrameDelegateImpl::IsTabMessage(message, &tab_handle))
      return false;

    // Get AddRef-ed pointer to corresponding TabProxy object
    TabProxy* tab = static_cast<TabProxy*>(tracker_->GetResource(tab_handle));
    if (tab) {
      tab->OnMessageReceived(message);
      tab->Release();
    }
    return true;
  }


 private:
  AutomationHandleTracker* tracker_;
};

class ChromeFrameAutomationProxyImpl::CFMsgDispatcher
    : public SyncMessageReplyDispatcher {
 public:
  CFMsgDispatcher() : SyncMessageReplyDispatcher() {}
 protected:
  virtual bool HandleMessageType(const IPC::Message& msg,
                                 const MessageSent& origin) {
    switch (origin.type) {
      case AutomationMsg_CreateExternalTab::ID:
      case AutomationMsg_ConnectExternalTab::ID:
        InvokeCallback<Tuple3<HWND, HWND, int> >(msg, origin);
        break;
      case AutomationMsg_NavigateExternalTabAtIndex::ID:
      case AutomationMsg_NavigateInExternalTab::ID:
        InvokeCallback<Tuple1<AutomationMsg_NavigationResponseValues> >(msg,
              origin);
        break;
      case AutomationMsg_InstallExtension::ID:
        InvokeCallback<Tuple1<AutomationMsg_ExtensionResponseValues> >(msg,
              origin);
        break;
      case AutomationMsg_LoadExpandedExtension::ID:
        InvokeCallback<Tuple1<AutomationMsg_ExtensionResponseValues> >(msg,
              origin);
        break;
      case AutomationMsg_GetEnabledExtensions::ID:
        InvokeCallback<Tuple1<std::vector<FilePath> > >(msg, origin);
        break;
      default:
        NOTREACHED();
    }
    return true;
  }
};

ChromeFrameAutomationProxyImpl::ChromeFrameAutomationProxyImpl(
    int launch_timeout)
    : AutomationProxy(launch_timeout) {
  sync_ = new CFMsgDispatcher();
  // Order of filters is not important.
  channel_->AddFilter(new TabProxyNotificationMessageFilter(tracker_.get()));
  channel_->AddFilter(sync_.get());
}

ChromeFrameAutomationProxyImpl::~ChromeFrameAutomationProxyImpl() {
}

void ChromeFrameAutomationProxyImpl::SendAsAsync(IPC::SyncMessage* msg,
                                                 void* callback, void* key) {
  sync_->Push(msg, callback, key);
  channel_->ChannelProxy::Send(msg);
}

void ChromeFrameAutomationProxyImpl::CancelAsync(void* key) {
  sync_->Cancel(key);
}

scoped_refptr<TabProxy> ChromeFrameAutomationProxyImpl::CreateTabProxy(
    int handle) {
  DCHECK(tracker_->GetResource(handle) == NULL);
  return new TabProxy(this, tracker_.get(), handle);
}

struct LaunchTimeStats {
#ifndef NDEBUG
  LaunchTimeStats() {
    launch_time_begin_ = base::Time::Now();
  }

  void Dump() {
    base::TimeDelta launch_time = base::Time::Now() - launch_time_begin_;
    THREAD_SAFE_UMA_HISTOGRAM_TIMES("ChromeFrame.AutomationServerLaunchTime",
                                    launch_time);
    const int64 launch_milliseconds = launch_time.InMilliseconds();
    if (launch_milliseconds > kAutomationServerReasonableLaunchDelay) {
      LOG(WARNING) << "Automation server launch took longer than expected: " <<
          launch_milliseconds << " ms.";
    }
  }

  base::Time launch_time_begin_;
#else
  void Dump() {}
#endif
};

ProxyFactory::ProxyCacheEntry::ProxyCacheEntry(const std::wstring& profile)
    : proxy(NULL), profile_name(profile), ref_count(1),
    launch_result(AutomationLaunchResult(-1)) {
  thread.reset(new base::Thread(WideToASCII(profile_name).c_str()));
  thread->Start();
}

template <> struct RunnableMethodTraits<ProxyFactory> {
  void RetainCallee(ProxyFactory* obj) {}
  void ReleaseCallee(ProxyFactory* obj) {}
};

ProxyFactory::ProxyFactory()
    : uma_send_interval_(0) {
  uma_send_interval_ = GetConfigInt(kDefaultSendUMADataInterval,
                                    kUmaSendIntervalValue);
}

ProxyFactory::~ProxyFactory() {
  for (size_t i = 0; i < proxies_.container().size(); ++i) {
    DWORD result = WaitForSingleObject(proxies_[i]->thread->thread_handle(), 0);
    if (WAIT_OBJECT_0 != result)
      // TODO(stoyan): Don't leak proxies on exit.
      DLOG(ERROR) << "Proxies leaked on exit.";
  }
}

void ProxyFactory::GetAutomationServer(
    LaunchDelegate* delegate, const ChromeFrameLaunchParams& params,
    void** automation_server_id) {
  ProxyCacheEntry* entry = NULL;
  // Find already existing launcher thread for given profile
  AutoLock lock(lock_);
  for (size_t i = 0; i < proxies_.container().size(); ++i) {
    if (!lstrcmpiW(proxies_[i]->profile_name.c_str(),
                   params.profile_name.c_str())) {
      entry = proxies_[i];
      DCHECK(entry->thread.get() != NULL);
      break;
    }
  }

  if (entry == NULL) {
    entry = new ProxyCacheEntry(params.profile_name);
    proxies_.container().push_back(entry);
  } else {
    entry->ref_count++;
  }

  DCHECK(delegate != NULL);
  DCHECK(automation_server_id != NULL);

  *automation_server_id = entry;
  // Note we always queue request to the launch thread, even if we already
  // have established proxy object. A simple lock around entry->proxy = proxy
  // would allow calling LaunchDelegate directly from here if
  // entry->proxy != NULL. Drawback is that callback may be invoked either in
  // main thread or in background thread, which may confuse the client.
  entry->thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this,
      &ProxyFactory::CreateProxy, entry, params, delegate));

  entry->thread->message_loop()->PostDelayedTask(FROM_HERE,
      NewRunnableMethod(this, &ProxyFactory::SendUMAData, entry),
      uma_send_interval_);
}

void ProxyFactory::CreateProxy(ProxyFactory::ProxyCacheEntry* entry,
                               const ChromeFrameLaunchParams& params,
                               LaunchDelegate* delegate) {
  DCHECK(entry->thread->thread_id() == PlatformThread::CurrentId());
  if (entry->proxy) {
    delegate->LaunchComplete(entry->proxy, entry->launch_result);
    return;
  }

  // We *must* create automationproxy in a thread that has message loop,
  // since SyncChannel::Context construction registers event to be watched
  // through ObjectWatcher which subscribes for the current thread message loop
  // destruction notification.

  // At same time we must destroy/stop the thread from another thread.
  ChromeFrameAutomationProxyImpl* proxy =
      new ChromeFrameAutomationProxyImpl(
          params.automation_server_launch_timeout);

  // Launch browser
  scoped_ptr<CommandLine> command_line(
      chrome_launcher::CreateLaunchCommandLine());
  command_line->AppendSwitchWithValue(switches::kAutomationClientChannelID,
      ASCIIToWide(proxy->channel_id()));

  // Run Chrome in Chrome Frame mode. In practice, this modifies the paths
  // and registry keys that Chrome looks in via the BrowserDistribution
  // mechanism.
  command_line->AppendSwitch(switches::kChromeFrame);

  // Chrome Frame never wants Chrome to start up with a First Run UI.
  command_line->AppendSwitch(switches::kNoFirstRun);

  command_line->AppendSwitch(switches::kDisablePopupBlocking);

  // Disable the "Whoa! Chrome has crashed." dialog, because that isn't very
  // useful for Chrome Frame users.
#ifndef NDEBUG
  command_line->AppendSwitch(switches::kNoErrorDialogs);
#endif

  command_line->AppendSwitch(switches::kEnableRendererAccessibility);

  // In headless mode runs like reliability test runs we want full crash dumps
  // from chrome.
  if (IsHeadlessMode())
    command_line->AppendSwitch(switches::kFullMemoryCrashReport);

  // Place the profile directory in
  // "<chrome_exe_path>\..\User Data\<profile-name>"
  if (!entry->profile_name.empty()) {
    FilePath profile_path;
    if (chrome::GetChromeFrameUserDataDirectory(&profile_path)) {
      profile_path = profile_path.Append(entry->profile_name);
      command_line->AppendSwitchWithValue(switches::kUserDataDir,
          profile_path.value());
    } else {
      // Can't get the profile dir :-( We need one to work, so fail.
      // We have no code for launch failure.
      entry->launch_result = AutomationLaunchResult(-1);
    }
  }

  std::wstring command_line_string(command_line->command_line_string());
  // If there are any extra arguments, append them to the command line.
  if (!params.extra_chrome_arguments.empty()) {
    command_line_string += L' ' + params.extra_chrome_arguments;
  }

  automation_server_launch_start_time_ = base::TimeTicks::Now();

  if (!base::LaunchApp(command_line_string, false, false, NULL)) {
    // We have no code for launch failure.
    entry->launch_result = AutomationLaunchResult(-1);
  } else {
    // Launch timeout may happen if the new instance tries to communicate
    // with an existing Chrome instance that is hung and displays msgbox
    // asking to kill the previous one. This could be easily observed if the
    // already running Chrome instance is running as high-integrity process
    // (started with "Run as Administrator" or launched by another high
    // integrity process) hence our medium-integrity process
    // cannot SendMessage to it with request to activate itself.

    // TODO(stoyan) AutomationProxy eats Hello message, hence installing
    // message filter is pointless, we can leverage ObjectWatcher and use
    // system thread pool to notify us when proxy->AppLaunch event is signaled.
    LaunchTimeStats launch_stats;
    // Wait for the automation server launch result, then stash away the
    // version string it reported.
    entry->launch_result = proxy->WaitForAppLaunch();
    launch_stats.Dump();

    base::TimeDelta delta =
        base::TimeTicks::Now() - automation_server_launch_start_time_;

    if (entry->launch_result == AUTOMATION_SUCCESS) {
      THREAD_SAFE_UMA_HISTOGRAM_TIMES(
          "ChromeFrame.AutomationServerLaunchSuccessTime", delta);
    } else {
      THREAD_SAFE_UMA_HISTOGRAM_TIMES(
          "ChromeFrame.AutomationServerLaunchFailedTime", delta);
    }

    THREAD_SAFE_UMA_HISTOGRAM_CUSTOM_COUNTS("ChromeFrame.LaunchResult",
                                            entry->launch_result,
                                            AUTOMATION_SUCCESS,
                                            AUTOMATION_CREATE_TAB_FAILED,
                                            AUTOMATION_CREATE_TAB_FAILED + 1);
  }

  // Finally set the proxy.
  entry->proxy = proxy;
  delegate->LaunchComplete(proxy, entry->launch_result);
}

bool ProxyFactory::ReleaseAutomationServer(void* server_id) {
  DLOG(INFO) << __FUNCTION__;

  if (!server_id) {
    NOTREACHED();
    return false;
  }

  ProxyCacheEntry* entry = reinterpret_cast<ProxyCacheEntry*>(server_id);

#ifndef NDEBUG
  lock_.Acquire();
  Vector::ContainerType::iterator it = std::find(proxies_.container().begin(),
                                                 proxies_.container().end(),
                                                 entry);
  DCHECK(it != proxies_.container().end());
  DCHECK(entry->thread->thread_id() != PlatformThread::CurrentId());
  DCHECK_GT(entry->ref_count, 0);

  lock_.Release();
#endif

  base::WaitableEvent done(true, false);
  entry->thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this,
      &ProxyFactory::ReleaseProxy, entry, &done));
  done.Wait();

  // Stop the thread and destroy the entry if there is no more clients.
  if (entry->ref_count == 0) {
    DCHECK(entry->proxy == NULL);
    entry->thread.reset();
    delete entry;
  }

  return true;
}

void ProxyFactory::ReleaseProxy(ProxyCacheEntry* entry,
                                base::WaitableEvent* done) {
  DCHECK(entry->thread->thread_id() == PlatformThread::CurrentId());

  lock_.Acquire();
  if (!--entry->ref_count) {
    Vector::ContainerType::iterator it = std::find(proxies_.container().begin(),
                                                   proxies_.container().end(),
                                                   entry);
    proxies_->erase(it);
  }
  lock_.Release();

  // Send pending UMA data if any.
  if (!entry->ref_count) {
    SendUMAData(entry);
    delete entry->proxy;
    entry->proxy = NULL;
  }

  done->Signal();
}

Singleton<ProxyFactory> g_proxy_factory;

void ProxyFactory::SendUMAData(ProxyCacheEntry* proxy_entry) {
  if (!proxy_entry) {
    NOTREACHED() << __FUNCTION__ << " Invalid proxy entry";
    return;
  }

  DCHECK(proxy_entry->thread->thread_id() == PlatformThread::CurrentId());

  if (proxy_entry->proxy) {
    ChromeFrameHistogramSnapshots::HistogramPickledList histograms =
        chrome_frame_histograms_.GatherAllHistograms();

    if (!histograms.empty()) {
      proxy_entry->proxy->Send(
          new AutomationMsg_RecordHistograms(0, histograms));
    }
  } else {
    DLOG(INFO) << __FUNCTION__ << " No proxy available to service the request";
    return;
  }

  MessageLoop::current()->PostDelayedTask(FROM_HERE, NewRunnableMethod(
      this, &ProxyFactory::SendUMAData, proxy_entry), uma_send_interval_);
}

template <> struct RunnableMethodTraits<ChromeFrameAutomationClient> {
  static void RetainCallee(ChromeFrameAutomationClient* obj) {}
  static void ReleaseCallee(ChromeFrameAutomationClient* obj) {}
};

ChromeFrameAutomationClient::ChromeFrameAutomationClient()
    : chrome_frame_delegate_(NULL),
      chrome_window_(NULL),
      tab_window_(NULL),
      parent_window_(NULL),
      automation_server_(NULL),
      automation_server_id_(NULL),
      ui_thread_id_(NULL),
      init_state_(UNINITIALIZED),
      use_chrome_network_(false),
      proxy_factory_(g_proxy_factory.get()),
      handle_top_level_requests_(false),
      tab_handle_(-1),
      external_tab_cookie_(0),
      url_fetcher_(NULL),
      navigate_after_initialization_(false) {
}

ChromeFrameAutomationClient::~ChromeFrameAutomationClient() {
  // Uninitialize must be called prior to the destructor
  DCHECK(automation_server_ == NULL);
}

bool ChromeFrameAutomationClient::Initialize(
    ChromeFrameDelegate* chrome_frame_delegate,
    int automation_server_launch_timeout,
    bool perform_version_check,
    const std::wstring& profile_name,
    const std::wstring& extra_chrome_arguments,
    bool incognito) {
  DCHECK(!IsWindow());
  chrome_frame_delegate_ = chrome_frame_delegate;
  ui_thread_id_ = PlatformThread::CurrentId();
#ifndef NDEBUG
  // In debug mode give more time to work with a debugger.
  if (IsDebuggerPresent()) {
    // Don't use INFINITE (which is -1) or even MAXINT since we will convert
    // from milliseconds to microseconds when stored in a base::TimeDelta,
    // thus * 1000. An hour should be enough.
    automation_server_launch_timeout = 60 * 60 * 1000;
  } else {
    DCHECK_LT(automation_server_launch_timeout, MAXINT / 2000);
    automation_server_launch_timeout *= 2;
  }
#endif  // NDEBUG

  // Create a window on the UI thread for marshaling messages back and forth
  // from the IPC thread. This window cannot be a message only window as the
  // external chrome tab window is created as a child of this window. This
  // window is eventually reparented to the ActiveX/NPAPI plugin window.
  if (!Create(GetDesktopWindow(), NULL, NULL,
              WS_CHILDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
              WS_EX_TOOLWINDOW)) {
    NOTREACHED();
    return false;
  }

  // Keep object in memory, while the window is alive.
  // Corresponsing Release is in OnFinalMessage();
  AddRef();

  // Mark our state as initializing.  We'll reach initialized once
  // InitializeComplete is called successfully.
  init_state_ = INITIALIZING;

  chrome_launch_params_.automation_server_launch_timeout =
      automation_server_launch_timeout;
  chrome_launch_params_.profile_name = profile_name;
  chrome_launch_params_.extra_chrome_arguments = extra_chrome_arguments;
  chrome_launch_params_.perform_version_check = perform_version_check;
  chrome_launch_params_.url = navigate_after_initialization_ ? GURL() : url_;
  chrome_launch_params_.incognito_mode = incognito;

  proxy_factory_->GetAutomationServer(
      static_cast<ProxyFactory::LaunchDelegate*>(this),
      chrome_launch_params_, &automation_server_id_);

  return true;
}

void ChromeFrameAutomationClient::Uninitialize() {
  DLOG(INFO) << __FUNCTION__;

  if (init_state_ == UNINITIALIZED) {
    DLOG(WARNING) << __FUNCTION__ << ": Automation client not initialized";
    return;
  }

  init_state_ = UNINITIALIZING;

  // Called from client's FinalRelease() / destructor
  // ChromeFrameAutomationClient may wait for the initialization (launch)
  // to complete while Uninitialize is called.
  // We either have to:
  // 1) Make ReleaseAutomationServer blocking call (wait until thread exits)
  // 2) Behave like a COM object i.e. increase module lock count.
  // Otherwise the DLL may get unloaded while we have running threads.
  // Unfortunately in NPAPI case we cannot increase module lock count, hence
  // we stick with blocking/waiting
  if (url_fetcher_) {
    // Clean up any outstanding requests
    url_fetcher_->StopAllRequests();
  }

  if (tab_.get()) {
    tab_->RemoveObserver(this);
    tab_ = NULL;    // scoped_refptr::Release
  }

  // Wait for the background thread to exit.
  ReleaseAutomationServer();

  // We must destroy the window, since if there are pending tasks
  // window procedure may be invoked after DLL is unloaded.
  // Unfortunately pending tasks are leaked.
  if (m_hWnd)
    DestroyWindow();

  chrome_frame_delegate_ = NULL;
  init_state_ = UNINITIALIZED;
}

bool ChromeFrameAutomationClient::InitiateNavigation(
    const std::string& url, const std::string& referrer, bool is_privileged) {
  if (url.empty())
    return false;

  GURL parsed_url(url);
  // Catch invalid URLs early.
  if (!parsed_url.is_valid() ||
      !IsValidUrlScheme(UTF8ToWide(url), is_privileged)) {
    DLOG(ERROR) << "Invalid URL passed to InitiateNavigation: " << url
                << " is_privileged=" << is_privileged;
    return false;
  }

  // Important: Since we will be using the referrer_ variable from a different
  // thread, we need to force a new std::string buffer instance for the
  // referrer_ GURL variable.  Otherwise we can run into strangeness when the
  // GURL is accessed and it could result in a bad URL that can cause the
  // referrer to be dropped or something worse.
  referrer_ = GURL(referrer.c_str());
  url_ = parsed_url;
  navigate_after_initialization_ = false;

  if (is_initialized()) {
    BeginNavigate(url_, referrer_);
  } else {
    navigate_after_initialization_ = true;
  }

  return true;
}

bool ChromeFrameAutomationClient::NavigateToIndex(int index) {
  // Could be NULL if we failed to launch Chrome in LaunchAutomationServer()
  if (!automation_server_ || !tab_.get() || !tab_->is_valid()) {
    return false;
  }

  DCHECK(::IsWindow(chrome_window_));

  IPC::SyncMessage* msg = new AutomationMsg_NavigateExternalTabAtIndex(
      0, tab_->handle(), index, NULL);
  automation_server_->SendAsAsync(msg, NewCallback(this,
      &ChromeFrameAutomationClient::BeginNavigateCompleted), this);
  return true;
}

bool ChromeFrameAutomationClient::ForwardMessageFromExternalHost(
    const std::string& message, const std::string& origin,
    const std::string& target) {
  // Could be NULL if we failed to launch Chrome in LaunchAutomationServer()
  if (!is_initialized())
    return false;

  tab_->HandleMessageFromExternalHost(message, origin, target);
  return true;
}

bool ChromeFrameAutomationClient::SetProxySettings(
    const std::string& json_encoded_proxy_settings) {
  if (!is_initialized())
    return false;
  automation_server_->SendProxyConfig(json_encoded_proxy_settings);
  return true;
}

void ChromeFrameAutomationClient::BeginNavigate(const GURL& url,
                                                const GURL& referrer) {
  // Could be NULL if we failed to launch Chrome in LaunchAutomationServer()
  if (!automation_server_ || !tab_.get()) {
    DLOG(WARNING) << "BeginNavigate - can't navigate.";
    ReportNavigationError(AUTOMATION_MSG_NAVIGATION_ERROR, url_.spec());
    return;
  }

  DCHECK(::IsWindow(chrome_window_));

  if (!tab_->is_valid()) {
    DLOG(WARNING) << "BeginNavigate - tab isn't valid.";
    return;
  }

  IPC::SyncMessage* msg =
      new AutomationMsg_NavigateInExternalTab(0, tab_->handle(), url,
                                              referrer, NULL);
  automation_server_->SendAsAsync(msg, NewCallback(this,
      &ChromeFrameAutomationClient::BeginNavigateCompleted), this);

  RECT client_rect = {0};
  chrome_frame_delegate_->GetBounds(&client_rect);
  Resize(client_rect.right - client_rect.left,
         client_rect.bottom - client_rect.top,
         SWP_NOACTIVATE | SWP_NOZORDER);
}

void ChromeFrameAutomationClient::BeginNavigateCompleted(
    AutomationMsg_NavigationResponseValues result) {
  if (result == AUTOMATION_MSG_NAVIGATION_ERROR)
     ReportNavigationError(AUTOMATION_MSG_NAVIGATION_ERROR, url_.spec());
}

void ChromeFrameAutomationClient::FindInPage(const std::wstring& search_string,
                                             FindInPageDirection forward,
                                             FindInPageCase match_case,
                                             bool find_next) {
  DCHECK(tab_.get());

  // What follows is quite similar to TabProxy::FindInPage() but uses
  // the SyncMessageReplyDispatcher to avoid concerns about blocking
  // synchronous messages.
  AutomationMsg_Find_Params params;
  params.unused = 0;
  params.search_string = WideToUTF16Hack(search_string);
  params.find_next = find_next;
  params.match_case = (match_case == CASE_SENSITIVE);
  params.forward = (forward == FWD);

  IPC::SyncMessage* msg =
      new AutomationMsg_Find(0, tab_->handle(), params, NULL, NULL);
  automation_server_->SendAsAsync(msg, NULL, this);
}

// Class that maintains context during the async load/install extension
// operation.  When done, InstallExtensionComplete is posted back to the UI
// thread so that the users of ChromeFrameAutomationClient can be notified.
class InstallExtensionContext {
 public:
  InstallExtensionContext(ChromeFrameAutomationClient* client,
      const FilePath& crx_path, void* user_data) : client_(client),
      crx_path_(crx_path), user_data_(user_data) {
  }

  ~InstallExtensionContext() {
  }

  void InstallExtensionComplete(AutomationMsg_ExtensionResponseValues res) {
    client_->PostTask(FROM_HERE, NewRunnableMethod(client_.get(),
        &ChromeFrameAutomationClient::InstallExtensionComplete, crx_path_,
        user_data_, res));
    delete this;
  }

 private:
  scoped_refptr<ChromeFrameAutomationClient> client_;
  FilePath crx_path_;
  void* user_data_;
};

void ChromeFrameAutomationClient::InstallExtension(
    const FilePath& crx_path,
    void* user_data) {
  if (automation_server_ == NULL) {
    InstallExtensionComplete(crx_path,
                             user_data,
                             AUTOMATION_MSG_EXTENSION_INSTALL_FAILED);
    return;
  }

  InstallExtensionContext* ctx = new InstallExtensionContext(
      this, crx_path, user_data);

  IPC::SyncMessage* msg =
      new AutomationMsg_InstallExtension(0, crx_path, NULL);

  // The context will delete itself after it is called.
  automation_server_->SendAsAsync(msg, NewCallback(ctx,
      &InstallExtensionContext::InstallExtensionComplete), this);
}

void ChromeFrameAutomationClient::InstallExtensionComplete(
    const FilePath& crx_path,
    void* user_data,
    AutomationMsg_ExtensionResponseValues res) {
  DCHECK_EQ(PlatformThread::CurrentId(), ui_thread_id_);

  if (chrome_frame_delegate_) {
    chrome_frame_delegate_->OnExtensionInstalled(crx_path, user_data, res);
  }
}

// Class that maintains context during the async retrieval of fetching the
// list of enabled extensions.  When done, GetEnabledExtensionsComplete is
// posted back to the UI thread so that the users of
// ChromeFrameAutomationClient can be notified.
class GetEnabledExtensionsContext {
 public:
  GetEnabledExtensionsContext(
      ChromeFrameAutomationClient* client, void* user_data) : client_(client),
          user_data_(user_data) {
    extension_directories_ = new std::vector<FilePath>();
  }

  ~GetEnabledExtensionsContext() {
    // ChromeFrameAutomationClient::GetEnabledExtensionsComplete takes
    // ownership of extension_directories_.
  }

  std::vector<FilePath>* extension_directories() {
    return extension_directories_;
  }

  void GetEnabledExtensionsComplete(
      std::vector<FilePath> result) {
    (*extension_directories_) = result;
    client_->PostTask(FROM_HERE, NewRunnableMethod(client_.get(),
      &ChromeFrameAutomationClient::GetEnabledExtensionsComplete,
      user_data_, extension_directories_));
    delete this;
  }

 private:
  scoped_refptr<ChromeFrameAutomationClient> client_;
  std::vector<FilePath>* extension_directories_;
  void* user_data_;
};

void ChromeFrameAutomationClient::GetEnabledExtensions(void* user_data) {
    if (automation_server_ == NULL) {
      GetEnabledExtensionsComplete(user_data, &std::vector<FilePath>());
      return;
    }

    GetEnabledExtensionsContext* ctx = new GetEnabledExtensionsContext(
        this, user_data);

    IPC::SyncMessage* msg = new AutomationMsg_GetEnabledExtensions(
        0, ctx->extension_directories());

    // The context will delete itself after it is called.
    automation_server_->SendAsAsync(msg, NewCallback(ctx,
        &GetEnabledExtensionsContext::GetEnabledExtensionsComplete), this);
}

void ChromeFrameAutomationClient::GetEnabledExtensionsComplete(
    void* user_data,
    std::vector<FilePath>* extension_directories) {
  DCHECK_EQ(PlatformThread::CurrentId(), ui_thread_id_);

  if (chrome_frame_delegate_) {
    chrome_frame_delegate_->OnGetEnabledExtensionsComplete(
        user_data, *extension_directories);
  }

  delete extension_directories;
}

void ChromeFrameAutomationClient::OnChromeFrameHostMoved() {
  // Use a local var to avoid the small possibility of getting the tab_
  // member be cleared while we try to use it.
  // Note that TabProxy is a RefCountedThreadSafe object, so we should be OK.
  scoped_refptr<TabProxy> tab(tab_);
  // There also is a possibility that tab_ has not been set yet,
  // so we still need to test for NULL.
  if (tab.get() != NULL)
    tab->OnHostMoved();
}

void ChromeFrameAutomationClient::LoadExpandedExtension(
    const FilePath& path,
    void* user_data) {
  if (automation_server_ == NULL) {
    InstallExtensionComplete(path,
                             user_data,
                             AUTOMATION_MSG_EXTENSION_INSTALL_FAILED);
    return;
  }

  InstallExtensionContext* ctx = new InstallExtensionContext(
      this, path, user_data);

  IPC::SyncMessage* msg =
      new AutomationMsg_LoadExpandedExtension(0, path, NULL);

  // The context will delete itself after it is called.
  automation_server_->SendAsAsync(msg, NewCallback(ctx,
      &InstallExtensionContext::InstallExtensionComplete), this);
}

void ChromeFrameAutomationClient::CreateExternalTab() {
  AutomationLaunchResult launch_result = AUTOMATION_SUCCESS;
  DCHECK(IsWindow());
  DCHECK(automation_server_ != NULL);

  // TODO(ananta)
  // We should pass in the referrer for the initial navigation.
  const IPC::ExternalTabSettings settings = {
    m_hWnd,
    gfx::Rect(),
    WS_CHILD,
    chrome_launch_params_.incognito_mode,
    !use_chrome_network_,
    handle_top_level_requests_,
    chrome_launch_params_.url,
    chrome_launch_params_.referrer,
  };

  THREAD_SAFE_UMA_HISTOGRAM_CUSTOM_COUNTS(
      "ChromeFrame.HostNetworking", !use_chrome_network_, 0, 1, 2);

  THREAD_SAFE_UMA_HISTOGRAM_CUSTOM_COUNTS(
      "ChromeFrame.HandleTopLevelRequests", handle_top_level_requests_, 0, 1,
      2);

  IPC::SyncMessage* message =
      new AutomationMsg_CreateExternalTab(0, settings, NULL, NULL, NULL);
  automation_server_->SendAsAsync(message, NewCallback(this,
      &ChromeFrameAutomationClient::CreateExternalTabComplete), this);
}

void ChromeFrameAutomationClient::CreateExternalTabComplete(HWND chrome_window,
    HWND tab_window, int tab_handle) {
  if (!automation_server_) {
    // If we receive this notification while shutting down, do nothing.
    DLOG(ERROR) << "CreateExternalTabComplete called when automation server "
                << "was null!";
    return;
  }

  AutomationLaunchResult launch_result = AUTOMATION_SUCCESS;
  if (tab_handle == 0 || !::IsWindow(chrome_window)) {
    launch_result = AUTOMATION_CREATE_TAB_FAILED;
  } else {
    chrome_window_ = chrome_window;
    tab_window_ = tab_window;
    tab_ = automation_server_->CreateTabProxy(tab_handle);
    tab_->AddObserver(this);
    tab_handle_ = tab_handle;
  }

  PostTask(FROM_HERE, NewRunnableMethod(this,
      &ChromeFrameAutomationClient::InitializeComplete, launch_result));
}

void ChromeFrameAutomationClient::SetEnableExtensionAutomation(
    const std::vector<std::string>& functions_enabled) {
  if (!is_initialized())
    return;

  // We are doing initialization, so there is no need to reset extension
  // automation, only to set it.  Also, we want to avoid resetting extension
  // automation that some other automation client has set up.  Therefore only
  // send the message if we are going to enable automation of some functions.
  if (functions_enabled.size() > 0) {
    tab_->SetEnableExtensionAutomation(functions_enabled);
  }
}

// Invoked in launch background thread.
void ChromeFrameAutomationClient::LaunchComplete(
    ChromeFrameAutomationProxy* proxy,
    AutomationLaunchResult result) {
  // If we're shutting down we don't keep a pointer to the automation server.
  if (init_state_ != UNINITIALIZING) {
    DCHECK(init_state_ == INITIALIZING);
    automation_server_ = proxy;
  } else {
    DLOG(INFO) << "Not storing automation server pointer due to shutting down";
  }

  if (result == AUTOMATION_SUCCESS) {
    // NOTE: A potential problem here is that Uninitialize() may just have
    // been called so we need to be careful and check the automation_server_
    // pointer.
    if (automation_server_ != NULL) {
      // If we have a valid tab_handle here it means that we are attaching to
      // an existing ExternalTabContainer instance, in which case we don't
      // want to create an external tab instance in Chrome.
      if (external_tab_cookie_ == 0) {
        // Continue with Initialization - Create external tab
        CreateExternalTab();
      } else {
        // Send a notification to Chrome that we are ready to connect to the
        // ExternalTab.
        IPC::SyncMessage* message =
            new AutomationMsg_ConnectExternalTab(0, external_tab_cookie_, true,
              NULL, NULL, NULL);
        automation_server_->SendAsAsync(message, NewCallback(this,
            &ChromeFrameAutomationClient::CreateExternalTabComplete), this);
      }
    }
  } else {
    // Launch failed. Note, we cannot delete proxy here.
    PostTask(FROM_HERE, NewRunnableMethod(this,
        &ChromeFrameAutomationClient::InitializeComplete, result));
  }
}

void ChromeFrameAutomationClient::InitializeComplete(
    AutomationLaunchResult result) {
  DCHECK_EQ(PlatformThread::CurrentId(), ui_thread_id_);
  std::string version = automation_server_->server_version();

  if (result != AUTOMATION_SUCCESS) {
    DLOG(WARNING) << "InitializeComplete: failure " << result;
    ReleaseAutomationServer();
  } else {
    init_state_ = INITIALIZED;

    // If the host already have a window, ask Chrome to re-parent.
    if (parent_window_)
      SetParentWindow(parent_window_);

    // If host specified destination URL - navigate. Apparently we do not use
    // accelerator table.
    if (navigate_after_initialization_) {
      BeginNavigate(url_, referrer_);
    }
  }

  if (chrome_frame_delegate_) {
    if (result == AUTOMATION_SUCCESS) {
      chrome_frame_delegate_->OnAutomationServerReady();
    } else {
      chrome_frame_delegate_->OnAutomationServerLaunchFailed(result, version);
    }
  }
}

bool ChromeFrameAutomationClient::ProcessUrlRequestMessage(TabProxy* tab,
    const IPC::Message& msg, bool ui_thread) {
  // Either directly call appropriate url_fetcher function
  // or postpone call to the UI thread.
  bool invoke = ui_thread || thread_safe_url_fetcher_;
  uint16 msg_type = msg.type();
  switch (msg_type) {
    default:
      return false;

    case AutomationMsg_RequestStart::ID:
      if (invoke)
        AutomationMsg_RequestStart::Dispatch(&msg, url_fetcher_,
            &PluginUrlRequestManager::StartUrlRequest);
      break;

    case AutomationMsg_RequestRead::ID:
      if (invoke)
        AutomationMsg_RequestRead::Dispatch(&msg, url_fetcher_,
            &PluginUrlRequestManager::ReadUrlRequest);
      break;

    case AutomationMsg_RequestEnd::ID:
      if (invoke)
        AutomationMsg_RequestEnd::Dispatch(&msg, url_fetcher_,
            &PluginUrlRequestManager::EndUrlRequest);
      break;

    case AutomationMsg_DownloadRequestInHost::ID:
      if (invoke)
        AutomationMsg_DownloadRequestInHost::Dispatch(&msg, url_fetcher_,
            &PluginUrlRequestManager::DownloadUrlRequestInHost);
      break;
  }

  if (!invoke) {
    PostTask(FROM_HERE, NewRunnableMethod(this,
        &ChromeFrameAutomationClient::ProcessUrlRequestMessage,
        tab, msg, true));
  }

  return true;
}

// This is invoked in channel's background thread.
// Cannot call any method of the activex/npapi here since they are STA
// kind of beings.
// By default we marshal the IPC message to the main/GUI thread and from there
// we safely invoke chrome_frame_delegate_->OnMessageReceived(msg).
void ChromeFrameAutomationClient::OnMessageReceived(TabProxy* tab,
                                                    const IPC::Message& msg) {
  DCHECK(tab == tab_.get());
  // Quickly process network related messages.
  if (url_fetcher_ && ProcessUrlRequestMessage(tab, msg, false))
    return;

  // Early check to avoid needless marshaling
  if (chrome_frame_delegate_ == NULL)
    return;

  PostTask(FROM_HERE, NewRunnableMethod(this,
      &ChromeFrameAutomationClient::OnMessageReceivedUIThread, msg));
}

void ChromeFrameAutomationClient::OnMessageReceivedUIThread(
    const IPC::Message& msg) {
  // Forward to the delegate.
  if (chrome_frame_delegate_)
    chrome_frame_delegate_->OnMessageReceived(msg);
}

void ChromeFrameAutomationClient::ReportNavigationError(
    AutomationMsg_NavigationResponseValues error_code,
    const std::string& url) {
  if (!chrome_frame_delegate_)
    return;

  if (ui_thread_id_ == PlatformThread::CurrentId()) {
    chrome_frame_delegate_->OnLoadFailed(error_code, url);
  } else {
    PostTask(FROM_HERE, NewRunnableMethod(this,
        &ChromeFrameAutomationClient::ReportNavigationError,
        error_code, url));
  }
}

void ChromeFrameAutomationClient::Resize(int width, int height,
                                         int flags) {
  if (tab_.get() && ::IsWindow(chrome_window())) {
    SetWindowPos(HWND_TOP, 0, 0, width, height, flags);
    tab_->Reposition(chrome_window(), HWND_TOP, 0, 0, width, height,
                     flags, m_hWnd);
  }
}

void ChromeFrameAutomationClient::SetParentWindow(HWND parent_window) {
  parent_window_ = parent_window;
  // If we're done with the initialization step, go ahead
  if (is_initialized()) {
    if (parent_window == NULL) {
      // Hide and reparent the automation window. This window will get
      // reparented to the new ActiveX/Active document window when it gets
      // created.
      ShowWindow(SW_HIDE);
      SetParent(GetDesktopWindow());
    } else {
      if (!::IsWindow(chrome_window())) {
        DLOG(WARNING) << "Invalid Chrome Window handle in SetParentWindow";
        return;
      }

      if (!SetParent(parent_window)) {
        DLOG(WARNING) << "Failed to set parent window for automation window. "
                      << "Error = "
                      << GetLastError();
        return;
      }

      RECT parent_client_rect = {0};
      ::GetClientRect(parent_window, &parent_client_rect);
      int width = parent_client_rect.right - parent_client_rect.left;
      int height = parent_client_rect.bottom - parent_client_rect.top;

      Resize(width, height, SWP_SHOWWINDOW | SWP_NOZORDER);
    }
  }
}

void ChromeFrameAutomationClient::ReleaseAutomationServer() {
  DLOG(INFO) << __FUNCTION__;
  if (automation_server_id_) {
    // Cache the server id and clear the automation_server_id_ before
    // calling ReleaseAutomationServer.  The reason we do this is that
    // we must cancel pending messages before we release the automation server.
    // Furthermore, while ReleaseAutomationServer is running, we could get
    // a callback to LaunchComplete which is where we normally get our pointer
    // to the automation server and there we check the server id for NULLness
    // and do nothing if it is NULL.
    void* server_id = automation_server_id_;
    automation_server_id_ = NULL;

    if (automation_server_) {
      // Make sure to clean up any pending sync messages before we go away.
      automation_server_->CancelAsync(this);
      automation_server_ = NULL;
    }

    proxy_factory_->ReleaseAutomationServer(server_id);

    // automation_server_ must not have been set to non NULL.
    // (if this regresses, start by looking at LaunchComplete()).
    DCHECK(automation_server_ == NULL);
  } else {
    DCHECK(automation_server_ == NULL);
  }
}

void ChromeFrameAutomationClient::SendContextMenuCommandToChromeFrame(
  int selected_command) {
  DCHECK(tab_ != NULL);
  tab_->SendContextMenuCommand(selected_command);
}

std::wstring ChromeFrameAutomationClient::GetVersion() const {
  static FileVersionInfo* version_info =
      FileVersionInfo::CreateFileVersionInfoForCurrentModule();

  std::wstring version;
  if (version_info)
    version = version_info->product_version();

  return version;
}

void ChromeFrameAutomationClient::Print(HDC print_dc,
                                        const RECT& print_bounds) {
  if (!tab_window_) {
    NOTREACHED();
    return;
  }

  HDC window_dc = ::GetDC(tab_window_);

  BitBlt(print_dc, print_bounds.left, print_bounds.top,
         print_bounds.right - print_bounds.left,
         print_bounds.bottom - print_bounds.top,
         window_dc, print_bounds.left, print_bounds.top,
         SRCCOPY);

  ::ReleaseDC(tab_window_, window_dc);
}

void ChromeFrameAutomationClient::PrintTab() {
  tab_->PrintAsync();
}

bool ChromeFrameAutomationClient::Reinitialize(
    ChromeFrameDelegate* delegate,
    PluginUrlRequestManager* url_fetcher) {
  if (!tab_.get() || !::IsWindow(chrome_window_)) {
    NOTREACHED();
    DLOG(WARNING) << "ChromeFrameAutomationClient instance reused "
                  << "with invalid tab";
    return false;
  }

  if (!delegate) {
    NOTREACHED();
    return false;
  }

  url_fetcher_->StopAllRequests();
  chrome_frame_delegate_ = delegate;
  DeleteAllPendingTasks();
  SetUrlFetcher(url_fetcher);
  SetParentWindow(NULL);
  return true;
}

void ChromeFrameAutomationClient::AttachExternalTab(
    uint64 external_tab_cookie) {
  DCHECK_EQ(static_cast<TabProxy*>(NULL), tab_.get());
  DCHECK_EQ(-1, tab_handle_);

  external_tab_cookie_ = external_tab_cookie;
}

void ChromeFrameAutomationClient::BlockExternalTab(uint64 cookie) {
  // The host does not want this tab to be shown (due popup blocker).
  IPC::SyncMessage* message =
      new AutomationMsg_ConnectExternalTab(0, cookie, false, NULL, NULL, NULL);
  automation_server_->SendAsAsync(message, NULL, this);
}

void ChromeFrameAutomationClient::SetPageFontSize(
    enum AutomationPageFontSize font_size) {
  if (font_size < SMALLEST_FONT ||
      font_size > LARGEST_FONT) {
      NOTREACHED() << "Invalid font size specified : "
                   << font_size;
      return;
  }

  automation_server_->Send(
      new AutomationMsg_SetPageFontSize(0, tab_handle_, font_size));
}


//////////////////////////////////////////////////////////////////////////
// PluginUrlRequestDelegate implementation.
// Forward network related responses to Chrome.

void ChromeFrameAutomationClient::OnResponseStarted(int request_id,
    const char* mime_type,  const char* headers, int size,
    base::Time last_modified, const std::string& redirect_url,
    int redirect_status) {
  const IPC::AutomationURLResponse response = {
      mime_type,
      headers ? headers : "",
      size,
      last_modified,
      redirect_url,
      redirect_status
  };

  automation_server_->Send(new AutomationMsg_RequestStarted(0,
      tab_->handle(), request_id, response));
}

void ChromeFrameAutomationClient::OnReadComplete(int request_id,
                                                 const void* buffer, int len) {
  std::string data(reinterpret_cast<const char*>(buffer), len);
  automation_server_->Send(new AutomationMsg_RequestData(0, tab_->handle(),
      request_id, data));
}

void ChromeFrameAutomationClient::OnResponseEnd(int request_id,
    const URLRequestStatus& status) {
  automation_server_->Send(new AutomationMsg_RequestEnd(0, tab_->handle(),
      request_id, status));
}