diff options
author | slightlyoff@chromium.org <slightlyoff@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-09-24 05:11:58 +0000 |
---|---|---|
committer | slightlyoff@chromium.org <slightlyoff@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-09-24 05:11:58 +0000 |
commit | f781782dd67077478e117c61dca4ea5eefce3544 (patch) | |
tree | 4801f724123cfdcbb69c4e7fe40a565b331723ae /chrome_frame/chrome_frame_automation.cc | |
parent | 63cf4759efa2373e33436fb5df6849f930081226 (diff) | |
download | chromium_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.cc | 975 |
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; +} |