| Index: chrome_frame/chrome_frame_automation.cc
|
| ===================================================================
|
| --- chrome_frame/chrome_frame_automation.cc (revision 0)
|
| +++ chrome_frame/chrome_frame_automation.cc (revision 0)
|
| @@ -0,0 +1,975 @@
|
| +// 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/command_line.h"
|
| +#include "base/compiler_specific.h"
|
| +#include "base/file_util.h"
|
| +#include "base/file_version_info.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 "chrome/app/client_util.h"
|
| +#include "chrome/common/chrome_constants.h"
|
| +#include "chrome/common/chrome_paths.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";
|
| +
|
| +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_NavigateInExternalTab::ID:
|
| + InvokeCallback<Tuple1<AutomationMsg_NavigationResponseValues> >(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_;
|
| + 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> {
|
| + static void RetainCallee(ProxyFactory* obj) {}
|
| + static void ReleaseCallee(ProxyFactory* obj) {}
|
| +};
|
| +
|
| +ProxyFactory::ProxyFactory()
|
| + : uma_send_interval_(0) {
|
| + uma_send_interval_ = GetConfigInt(kDefaultSendUMADataInterval,
|
| + kUmaSendIntervalValue);
|
| +}
|
| +
|
| +ProxyFactory::~ProxyFactory() {
|
| + DCHECK_EQ(proxies_.container().size(), 0);
|
| +}
|
| +
|
| +void* ProxyFactory::GetAutomationServer(int launch_timeout,
|
| + const std::wstring& profile_name,
|
| + const std::wstring& extra_argument,
|
| + bool perform_version_check,
|
| + LaunchDelegate* delegate) {
|
| + 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(), profile_name.c_str())) {
|
| + entry = proxies_[i];
|
| + DCHECK(entry->thread.get() != NULL);
|
| + break;
|
| + }
|
| + }
|
| +
|
| + if (entry == NULL) {
|
| + entry = new ProxyCacheEntry(profile_name);
|
| + proxies_.container().push_back(entry);
|
| + } else {
|
| + entry->ref_count++;
|
| + }
|
| +
|
| +
|
| + // 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,
|
| + launch_timeout, extra_argument,
|
| + perform_version_check, delegate));
|
| +
|
| + entry->thread->message_loop()->PostDelayedTask(FROM_HERE,
|
| + NewRunnableMethod(this, &ProxyFactory::SendUMAData, entry),
|
| + uma_send_interval_);
|
| +
|
| + return entry;
|
| +}
|
| +
|
| +void ProxyFactory::CreateProxy(ProxyFactory::ProxyCacheEntry* entry,
|
| + int launch_timeout,
|
| + const std::wstring& extra_chrome_arguments,
|
| + bool perform_version_check,
|
| + 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(launch_timeout);
|
| +
|
| + // Launch browser
|
| + scoped_ptr<CommandLine> command_line(
|
| + chrome_launcher::CreateLaunchCommandLine());
|
| + command_line->AppendSwitchWithValue(switches::kAutomationClientChannelID,
|
| + ASCIIToWide(proxy->channel_id()));
|
| +
|
| + // The metrics bug out because they attempt to use URLFetcher with a
|
| + // null URLRequestContext::default_request_context_. Turn them off for now.
|
| + // TODO(robertshield): Figure out why this is. It appears to have something
|
| + // to do with an improperly set up profile...
|
| + command_line->AppendSwitch(switches::kDisableMetrics);
|
| +
|
| + // Chrome Frame never wants Chrome to start up with a First Run UI.
|
| + command_line->AppendSwitch(switches::kNoFirstRun);
|
| +
|
| + // Place the profile directory in
|
| + // "<chrome_exe_path>\..\User Data\<profile-name>"
|
| + if (!entry->profile_name.empty()) {
|
| + std::wstring profile_path;
|
| + if (GetUserProfileBaseDirectory(&profile_path)) {
|
| + file_util::AppendToPath(&profile_path, entry->profile_name);
|
| + command_line->AppendSwitchWithValue(switches::kUserDataDir,
|
| + profile_path);
|
| + } 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 (!extra_chrome_arguments.empty()) {
|
| + command_line_string += L' ' + 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) {
|
| + UMA_HISTOGRAM_TIMES("ChromeFrame.AutomationServerLaunchSuccessTime",
|
| + delta);
|
| + } else {
|
| + UMA_HISTOGRAM_TIMES("ChromeFrame.AutomationServerLaunchFailedTime",
|
| + delta);
|
| + }
|
| +
|
| + 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);
|
| +
|
| + 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());
|
| + if (--entry->ref_count == 0) {
|
| + proxies_.container().erase(it);
|
| + }
|
| +
|
| + lock_.Release();
|
| +
|
| + // Destroy it.
|
| + if (entry->ref_count == 0) {
|
| + entry->thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this,
|
| + &ProxyFactory::DestroyProxy, entry));
|
| + // Wait until thread exits
|
| + entry->thread.reset();
|
| + DCHECK(entry->proxy == NULL);
|
| + delete entry;
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +void ProxyFactory::DestroyProxy(ProxyCacheEntry* entry) {
|
| + DCHECK(entry->thread->thread_id() == PlatformThread::CurrentId());
|
| + // Send pending UMA data if any.
|
| + SendUMAData(entry);
|
| + delete entry->proxy;
|
| + entry->proxy = NULL;
|
| +}
|
| +
|
| +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),
|
| + incognito_(false),
|
| + init_state_(UNINITIALIZED),
|
| + use_chrome_network_(false),
|
| + proxy_factory_(g_proxy_factory.get()),
|
| + handle_top_level_requests_(false),
|
| + tab_handle_(-1),
|
| + external_tab_cookie_(NULL) {
|
| +}
|
| +
|
| +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;
|
| + incognito_ = incognito;
|
| + ui_thread_id_ = PlatformThread::CurrentId();
|
| +
|
| +#ifndef NDEBUG
|
| + // In debug mode give more time to work with a debugger.
|
| + if (automation_server_launch_timeout != INFINITE)
|
| + 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;
|
| + }
|
| +
|
| + // Mark our state as initializing. We'll reach initialized once
|
| + // InitializeComplete is called successfully.
|
| + init_state_ = INITIALIZING;
|
| +
|
| + automation_server_id_ = proxy_factory_->GetAutomationServer(
|
| + automation_server_launch_timeout,
|
| + profile_name, extra_chrome_arguments, perform_version_check,
|
| + static_cast<ProxyFactory::LaunchDelegate*>(this));
|
| +
|
| + return true;
|
| +}
|
| +
|
| +void ChromeFrameAutomationClient::Uninitialize() {
|
| + DLOG(INFO) << __FUNCTION__;
|
| +
|
| + 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 (tab_.get()) {
|
| + tab_->RemoveObserver(this);
|
| + tab_ = NULL; // scoped_refptr::Release
|
| + }
|
| +
|
| + // Clean up any outstanding requests
|
| + CleanupRequests();
|
| +
|
| + // 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) {
|
| + if (url.empty())
|
| + return false;
|
| +
|
| + url_ = GURL(url);
|
| +
|
| + // Catch invalid URLs early.
|
| + if (!url_.is_valid()) {
|
| + DLOG(ERROR) << "Invalid URL passed to InitiateNavigation: " << url;
|
| + return false;
|
| + }
|
| +
|
| + if (is_initialized()) {
|
| + BeginNavigate(GURL(url));
|
| + }
|
| +
|
| + 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) {
|
| + // 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, 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);
|
| +}
|
| +
|
| +void ChromeFrameAutomationClient::CreateExternalTab() {
|
| + AutomationLaunchResult launch_result = AUTOMATION_SUCCESS;
|
| + DCHECK(IsWindow());
|
| + DCHECK(automation_server_ != NULL);
|
| +
|
| + const IPC::ExternalTabSettings settings = {
|
| + m_hWnd,
|
| + gfx::Rect(),
|
| + WS_CHILD,
|
| + incognito_,
|
| + !use_chrome_network_,
|
| + handle_top_level_requests_,
|
| + GURL(url_)
|
| + };
|
| +
|
| + UMA_HISTOGRAM_CUSTOM_COUNTS(
|
| + "ChromeFrame.HostNetworking", !use_chrome_network_, 0, 1, 2);
|
| +
|
| + 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) ||
|
| + !::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(
|
| + bool enable_automation) {
|
| + if (!is_initialized())
|
| + return;
|
| +
|
| + automation_server_->SetEnableExtensionAutomation(enable_automation);
|
| +}
|
| +
|
| +// 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_ == NULL) {
|
| + // 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_, 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(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 (chrome_frame_delegate_) {
|
| + if (result == AUTOMATION_SUCCESS) {
|
| + chrome_frame_delegate_->OnAutomationServerReady();
|
| + } else {
|
| + chrome_frame_delegate_->OnAutomationServerLaunchFailed(result, version);
|
| + }
|
| + }
|
| +}
|
| +
|
| +// 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());
|
| +
|
| + // Early check to avoid needless marshaling
|
| + if (chrome_frame_delegate_ == NULL)
|
| + return;
|
| +
|
| + CallDelegate(FROM_HERE, NewRunnableMethod(chrome_frame_delegate_,
|
| + &ChromeFrameDelegate::OnMessageReceived, msg));
|
| +}
|
| +
|
| +void ChromeFrameAutomationClient::ReportNavigationError(
|
| + AutomationMsg_NavigationResponseValues error_code,
|
| + const std::string& url) {
|
| + CallDelegate(FROM_HERE, NewRunnableMethod(chrome_frame_delegate_,
|
| + &ChromeFrameDelegate::OnLoadFailed,
|
| + 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)) {
|
| + NOTREACHED();
|
| + 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::CallDelegate(
|
| + const tracked_objects::Location& from_here, Task* delegate_task ) {
|
| + delegate_task->SetBirthPlace(from_here);
|
| + PostTask(FROM_HERE, NewRunnableMethod(this,
|
| + &ChromeFrameAutomationClient::CallDelegateImpl,
|
| + delegate_task));
|
| +}
|
| +
|
| +void ChromeFrameAutomationClient::CallDelegateImpl(Task* delegate_task) {
|
| + if (chrome_frame_delegate_) {
|
| + // task's object should be == chrome_frame_delegate_
|
| + delegate_task->Run();
|
| + }
|
| +
|
| + delete delegate_task;
|
| +}
|
| +
|
| +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();
|
| +}
|
| +
|
| +// IPC:Message::Sender implementation
|
| +bool ChromeFrameAutomationClient::Send(IPC::Message* msg) {
|
| + return automation_server_->Send(msg);
|
| +}
|
| +
|
| +bool ChromeFrameAutomationClient::AddRequest(PluginUrlRequest* request) {
|
| + if (!request) {
|
| + NOTREACHED();
|
| + return false;
|
| + }
|
| +
|
| + DCHECK(request_map_.end() == request_map_.find(request->id()));
|
| + request_map_[request->id()] = request;
|
| + return true;
|
| +}
|
| +
|
| +bool ChromeFrameAutomationClient::ReadRequest(
|
| + int request_id, int bytes_to_read) {
|
| + bool result = false;
|
| + PluginUrlRequest* request = LookupRequest(request_id);
|
| + if (request)
|
| + result = request->Read(bytes_to_read);
|
| +
|
| + return result;
|
| +}
|
| +
|
| +void ChromeFrameAutomationClient::RemoveRequest(PluginUrlRequest* request) {
|
| + DCHECK(request_map_.end() != request_map_.find(request->id()));
|
| + request_map_.erase(request->id());
|
| +}
|
| +
|
| +void ChromeFrameAutomationClient::RemoveRequest(
|
| + int request_id, int reason, bool abort) {
|
| + PluginUrlRequest* request = LookupRequest(request_id);
|
| + if (request) {
|
| + if (abort) {
|
| + request->Stop();
|
| + DCHECK(request_map_.end() == request_map_.find(request_id));
|
| + } else {
|
| + request_map_.erase(request_id);
|
| + }
|
| + }
|
| +}
|
| +
|
| +PluginUrlRequest* ChromeFrameAutomationClient::LookupRequest(
|
| + int request_id) const {
|
| + PluginUrlRequest* request = NULL;
|
| + RequestMap::const_iterator it = request_map_.find(request_id);
|
| + if (request_map_.end() != it)
|
| + request = (*it).second;
|
| + return request;
|
| +}
|
| +
|
| +bool ChromeFrameAutomationClient::IsValidRequest(
|
| + PluginUrlRequest* request) const {
|
| + bool is_valid = false;
|
| + // if request is invalid then request->id() won't work
|
| + // hence perform reverse map lookup for validity of the
|
| + // request pointer.
|
| + if (request) {
|
| + for (RequestMap::const_iterator it = request_map_.begin();
|
| + it != request_map_.end(); it++) {
|
| + if (request == (*it).second) {
|
| + is_valid = true;
|
| + break;
|
| + }
|
| + }
|
| + }
|
| +
|
| + return is_valid;
|
| +}
|
| +
|
| +void ChromeFrameAutomationClient::CleanupRequests() {
|
| + while (request_map_.size()) {
|
| + PluginUrlRequest* request = request_map_.begin()->second;
|
| + if (request) {
|
| + int request_id = request->id();
|
| + request->Stop();
|
| + DCHECK(request_map_.end() == request_map_.find(request_id));
|
| + }
|
| + }
|
| +
|
| + DCHECK(request_map_.empty());
|
| + request_map_.clear();
|
| +}
|
| +
|
| +bool ChromeFrameAutomationClient::Reinitialize(
|
| + ChromeFrameDelegate* delegate) {
|
| + if (!tab_.get() || !::IsWindow(chrome_window_)) {
|
| + NOTREACHED();
|
| + DLOG(WARNING) << "ChromeFrameAutomationClient instance reused "
|
| + << "with invalid tab";
|
| + return false;
|
| + }
|
| +
|
| + if (!delegate) {
|
| + NOTREACHED();
|
| + return false;
|
| + }
|
| +
|
| + chrome_frame_delegate_ = delegate;
|
| + SetParentWindow(NULL);
|
| + return true;
|
| +}
|
| +
|
| +void ChromeFrameAutomationClient::AttachExternalTab(
|
| + intptr_t external_tab_cookie) {
|
| + DCHECK(tab_.get() == NULL);
|
| + DCHECK(tab_handle_ == -1);
|
| +
|
| + external_tab_cookie_ = external_tab_cookie;
|
| +}
|
|
|