blob: 89099230266ca5c845145299f54f61a79d48a389 [file] [log] [blame]
// Copyright (c) 2010 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/trace_event.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_switches.h"
#include "chrome/test/automation/tab_proxy.h"
#include "chrome_frame/chrome_launcher_utils.h"
#include "chrome_frame/crash_reporting/crash_metrics.h"
#include "chrome_frame/custom_sync_call_context.h"
#include "chrome_frame/utils.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 ChromeFrameAutomationProxyImpl::TabProxyNotificationMessageFilter
: public IPC::ChannelProxy::MessageFilter {
public:
explicit TabProxyNotificationMessageFilter(AutomationHandleTracker* tracker)
: tracker_(tracker) {
}
void AddTabProxy(AutomationHandle tab_proxy) {
tabs_list_.push_back(tab_proxy);
}
void RemoveTabProxy(AutomationHandle tab_proxy) {
tabs_list_.remove(tab_proxy);
}
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();
} else {
DLOG(ERROR) << "Failed to find TabProxy for tab:" << tab_handle;
}
return true;
}
virtual void OnChannelError() {
std::list<AutomationHandle>::const_iterator iter = tabs_list_.begin();
for (; iter != tabs_list_.end(); ++iter) {
// Get AddRef-ed pointer to corresponding TabProxy object
TabProxy* tab = static_cast<TabProxy*>(tracker_->GetResource(*iter));
if (tab) {
tab->OnChannelError();
tab->Release();
}
}
}
private:
AutomationHandleTracker* tracker_;
std::list<AutomationHandle> tabs_list_;
};
class ChromeFrameAutomationProxyImpl::CFMsgDispatcher
: public SyncMessageReplyDispatcher {
public:
CFMsgDispatcher() : SyncMessageReplyDispatcher() {}
protected:
virtual bool HandleMessageType(const IPC::Message& msg,
SyncMessageCallContext* context) {
switch (context->message_type()) {
case AutomationMsg_CreateExternalTab::ID:
case AutomationMsg_ConnectExternalTab::ID:
InvokeCallback<CreateExternalTabContext>(msg, context);
break;
case AutomationMsg_NavigateExternalTabAtIndex::ID:
case AutomationMsg_NavigateInExternalTab::ID:
InvokeCallback<BeginNavigateContext>(msg, context);
break;
case AutomationMsg_InstallExtension::ID:
InvokeCallback<InstallExtensionContext>(msg, context);
break;
case AutomationMsg_LoadExpandedExtension::ID:
InvokeCallback<InstallExtensionContext>(msg, context);
break;
case AutomationMsg_GetEnabledExtensions::ID:
InvokeCallback<GetEnabledExtensionsContext>(msg, context);
break;
default:
NOTREACHED();
}
return true;
}
};
ChromeFrameAutomationProxyImpl::ChromeFrameAutomationProxyImpl(
int launch_timeout)
: AutomationProxy(launch_timeout) {
TRACE_EVENT_BEGIN("chromeframe.automationproxy", this, "");
sync_ = new CFMsgDispatcher();
message_filter_ = new TabProxyNotificationMessageFilter(tracker_.get());
// Order of filters is not important.
channel_->AddFilter(message_filter_.get());
channel_->AddFilter(sync_.get());
}
ChromeFrameAutomationProxyImpl::~ChromeFrameAutomationProxyImpl() {
TRACE_EVENT_END("chromeframe.automationproxy", this, "");
}
void ChromeFrameAutomationProxyImpl::SendAsAsync(
IPC::SyncMessage* msg,
SyncMessageReplyDispatcher::SyncMessageCallContext* context, void* key) {
sync_->Push(msg, context, 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);
TabProxy* tab_proxy = new TabProxy(this, tracker_.get(), handle);
if (tab_proxy != NULL)
message_filter_->AddTabProxy(handle);
return tab_proxy;
}
void ChromeFrameAutomationProxyImpl::ReleaseTabProxy(AutomationHandle handle) {
message_filter_->RemoveTabProxy(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();
}
DISABLE_RUNNABLE_METHOD_REFCOUNT(ProxyFactory);
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) {
TRACE_EVENT_BEGIN("chromeframe.createproxy", this, "");
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));
// IE uses the chrome frame provided UMA data uploading scheme. NPAPI
// continues to use Chrome to upload UMA data.
if (!CrashMetricsReporter::GetInstance()->active()) {
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
// In headless mode runs like reliability test runs we want full crash dumps
// from chrome.
if (IsHeadlessMode())
command_line->AppendSwitch(switches::kFullMemoryCrashReport);
DLOG(INFO) << "Profile path: " << params.profile_path.value();
command_line->AppendSwitchWithValue(switches::kUserDataDir,
params.profile_path.value());
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);
}
TRACE_EVENT_END("chromeframe.createproxy", this, "");
// Finally set the proxy.
entry->proxy = proxy;
delegate->LaunchComplete(proxy, entry->launch_result);
}
bool ProxyFactory::ReleaseAutomationServer(void* server_id) {
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) {
// IE uses the chrome frame provided UMA data uploading scheme. NPAPI
// continues to use Chrome to upload UMA data.
if (CrashMetricsReporter::GetInstance()->active()) {
return;
}
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),
url_fetcher_flags_(PluginUrlRequestManager::NOT_THREADSAFE),
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,
const ChromeFrameLaunchParams& chrome_launch_params) {
DCHECK(!IsWindow());
chrome_frame_delegate_ = chrome_frame_delegate;
chrome_launch_params_ = chrome_launch_params;
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.
chrome_launch_params_.automation_server_launch_timeout = 60 * 60 * 1000;
} else {
DCHECK_LT(chrome_launch_params_.automation_server_launch_timeout,
MAXINT / 2000);
chrome_launch_params_.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;
if (chrome_launch_params_.url.is_valid())
navigate_after_initialization_ = false;
if (!navigate_after_initialization_) {
chrome_launch_params_.url = url_;
}
proxy_factory_->GetAutomationServer(
static_cast<ProxyFactory::LaunchDelegate*>(this),
chrome_launch_params_, &automation_server_id_);
return true;
}
void ChromeFrameAutomationClient::Uninitialize() {
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();
url_fetcher_ = NULL;
}
if (tab_.get()) {
tab_->RemoveObserver(this);
if (automation_server_)
automation_server_->ReleaseTabProxy(tab_->handle());
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 (::IsWindow(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, new BeginNavigateContext(this),
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, new BeginNavigateContext(this), 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::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, ctx, 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);
}
}
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, ctx, 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, ctx, 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,
!chrome_launch_params_.is_widget_mode // Infobars disabled in widget mode.
};
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, new CreateExternalTabContext(this),
this);
}
AutomationLaunchResult 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 AUTOMATION_CREATE_TAB_FAILED;
}
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;
}
return 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,
m_hWnd, NULL, NULL, NULL);
automation_server_->SendAsAsync(message,
new CreateExternalTabContext(this),
this);
DLOG(INFO) << __FUNCTION__ << ": sending CreateExternalTabComplete";
}
}
} 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.
uint16 msg_type = msg.type();
switch (msg_type) {
default:
return false;
case AutomationMsg_RequestStart::ID:
if (ui_thread || (url_fetcher_flags_ &
PluginUrlRequestManager::START_REQUEST_THREADSAFE)) {
AutomationMsg_RequestStart::Dispatch(&msg, url_fetcher_,
&PluginUrlRequestManager::StartUrlRequest);
return true;
}
break;
case AutomationMsg_RequestRead::ID:
if (ui_thread || (url_fetcher_flags_ &
PluginUrlRequestManager::READ_REQUEST_THREADSAFE)) {
AutomationMsg_RequestRead::Dispatch(&msg, url_fetcher_,
&PluginUrlRequestManager::ReadUrlRequest);
return true;
}
break;
case AutomationMsg_RequestEnd::ID:
if (ui_thread || (url_fetcher_flags_ &
PluginUrlRequestManager::STOP_REQUEST_THREADSAFE)) {
AutomationMsg_RequestEnd::Dispatch(&msg, url_fetcher_,
&PluginUrlRequestManager::EndUrlRequest);
return true;
}
break;
case AutomationMsg_DownloadRequestInHost::ID:
if (ui_thread || (url_fetcher_flags_ &
PluginUrlRequestManager::DOWNLOAD_REQUEST_THREADSAFE)) {
AutomationMsg_DownloadRequestInHost::Dispatch(&msg, url_fetcher_,
&PluginUrlRequestManager::DownloadUrlRequestInHost);
return true;
}
break;
case AutomationMsg_GetCookiesFromHost::ID:
if (ui_thread || (url_fetcher_flags_ &
PluginUrlRequestManager::COOKIE_REQUEST_THREADSAFE)) {
AutomationMsg_GetCookiesFromHost::Dispatch(&msg, url_fetcher_,
&PluginUrlRequestManager::GetCookiesFromHost);
return true;
}
break;
case AutomationMsg_SetCookieAsync::ID:
if (ui_thread || (url_fetcher_flags_ &
PluginUrlRequestManager::COOKIE_REQUEST_THREADSAFE)) {
AutomationMsg_SetCookieAsync::Dispatch(&msg, url_fetcher_,
&PluginUrlRequestManager::SetCookiesInHost);
return true;
}
break;
}
PostTask(FROM_HERE, NewRunnableMethod(this,
&ChromeFrameAutomationClient::ProcessUrlRequestMessage, tab, msg, true));
return true;
}
// These are 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::OnChannelError(TabProxy* tab) {
DCHECK(tab == tab_.get());
// Early check to avoid needless marshaling
if (chrome_frame_delegate_ == NULL)
return;
PostTask(FROM_HERE, NewRunnableMethod(this,
&ChromeFrameAutomationClient::OnChannelErrorUIThread));
}
void ChromeFrameAutomationClient::OnMessageReceivedUIThread(
const IPC::Message& msg) {
DCHECK_EQ(PlatformThread::CurrentId(), ui_thread_id_);
// Forward to the delegate.
if (chrome_frame_delegate_)
chrome_frame_delegate_->OnMessageReceived(msg);
}
void ChromeFrameAutomationClient::OnChannelErrorUIThread() {
DCHECK_EQ(PlatformThread::CurrentId(), ui_thread_id_);
// Forward to the delegate.
if (chrome_frame_delegate_)
chrome_frame_delegate_->OnChannelError();
}
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() {
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 could cause an external tab to be
// created. Ideally the callbacks should be dropped.
// TODO(ananta)
// Refactor the ChromeFrameAutomationProxy code to not depend on
// AutomationProxy and simplify the whole mess.
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);
}
proxy_factory_->ReleaseAutomationServer(server_id);
automation_server_ = NULL;
// 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 (url_fetcher_) {
// Clean up any outstanding requests
url_fetcher_->StopAllRequests();
url_fetcher_ = NULL;
}
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;
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, m_hWnd,
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));
}
void ChromeFrameAutomationClient::RemoveBrowsingData(int remove_mask) {
automation_server_->Send(
new AutomationMsg_RemoveBrowsingData(0, remove_mask));
}
void ChromeFrameAutomationClient::RunUnloadHandlers(HWND notification_window,
int notification_message) {
if (automation_server_) {
automation_server_->Send(
new AutomationMsg_RunUnloadHandlers(0, tab_handle_,
notification_window,
notification_message));
} else {
// Post this message to ensure that the caller exits his message loop.
::PostMessage(notification_window, notification_message, 0, 0);
}
}
//////////////////////////////////////////////////////////////////////////
// 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 std::string& data) {
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));
}
void ChromeFrameAutomationClient::OnCookiesRetrieved(bool success,
const GURL& url, const std::string& cookie_string, int cookie_id) {
automation_server_->Send(new AutomationMsg_GetCookiesHostResponse(0,
tab_->handle(), success, url, cookie_string, cookie_id));
}