summaryrefslogtreecommitdiffstats
path: root/chrome_frame/chrome_frame_automation.cc
diff options
context:
space:
mode:
authorslightlyoff@chromium.org <slightlyoff@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-09-24 05:11:58 +0000
committerslightlyoff@chromium.org <slightlyoff@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-09-24 05:11:58 +0000
commitf781782dd67077478e117c61dca4ea5eefce3544 (patch)
tree4801f724123cfdcbb69c4e7fe40a565b331723ae /chrome_frame/chrome_frame_automation.cc
parent63cf4759efa2373e33436fb5df6849f930081226 (diff)
downloadchromium_src-f781782dd67077478e117c61dca4ea5eefce3544.zip
chromium_src-f781782dd67077478e117c61dca4ea5eefce3544.tar.gz
chromium_src-f781782dd67077478e117c61dca4ea5eefce3544.tar.bz2
Initial import of the Chrome Frame codebase. Integration in chrome.gyp coming in a separate CL.
BUG=None TEST=None Review URL: http://codereview.chromium.org/218019 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@27042 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome_frame/chrome_frame_automation.cc')
-rw-r--r--chrome_frame/chrome_frame_automation.cc975
1 files changed, 975 insertions, 0 deletions
diff --git a/chrome_frame/chrome_frame_automation.cc b/chrome_frame/chrome_frame_automation.cc
new file mode 100644
index 0000000..9da5e43
--- /dev/null
+++ b/chrome_frame/chrome_frame_automation.cc
@@ -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;
+}