diff options
-rw-r--r-- | chrome_frame/cfproxy.h | 227 | ||||
-rw-r--r-- | chrome_frame/cfproxy_factory.cc | 79 | ||||
-rw-r--r-- | chrome_frame/cfproxy_private.h | 192 | ||||
-rw-r--r-- | chrome_frame/cfproxy_proxy.cc | 246 | ||||
-rw-r--r-- | chrome_frame/cfproxy_support.cc | 450 | ||||
-rw-r--r-- | chrome_frame/cfproxy_test.cc | 316 | ||||
-rw-r--r-- | chrome_frame/chrome_frame.gyp | 10 | ||||
-rw-r--r-- | chrome_frame/external_tab.cc | 131 | ||||
-rw-r--r-- | chrome_frame/external_tab.h | 213 | ||||
-rw-r--r-- | chrome_frame/task_marshaller.cc | 133 | ||||
-rw-r--r-- | chrome_frame/task_marshaller.h | 63 |
11 files changed, 2060 insertions, 0 deletions
diff --git a/chrome_frame/cfproxy.h b/chrome_frame/cfproxy.h new file mode 100644 index 0000000..b45e30f --- /dev/null +++ b/chrome_frame/cfproxy.h @@ -0,0 +1,227 @@ +// 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. + +#ifndef CHROME_FRAME_CFPROXY_H_ +#define CHROME_FRAME_CFPROXY_H_ +#pragma once + +#include <windows.h> +#include <map> // for proxy factory +#include <vector> +#include <string> +#include "base/lock.h" +#include "base/time.h" // for base::TimeDelta +#include "base/file_path.h" +#include "chrome/common/page_zoom.h" +#include "chrome/test/automation/automation_constants.h" + +enum FindInPageDirection { BACK = 0, FWD = 1 }; +enum FindInPageCase { IGNORE_CASE = 0, CASE_SENSITIVE = 1 }; +// Specifies the font size on a page which is requested by an automation +// client. +enum AutomationPageFontSize { + SMALLEST_FONT = 8, + SMALL_FONT = 12, + MEDIUM_FONT = 16, + LARGE_FONT = 24, + LARGEST_FONT = 36 +}; + +class URLRequestStatus; +namespace IPC { + struct ExternalTabSettings; + struct NavigationInfo; + struct AutomationURLRequest; + struct AttachExternalTabParams; +}; + +class GURL; +class ChromeProxyFactory; +class ChromeProxyDelegate; +struct ProxyParams; + +// Some callers of synchronous messages wants a context to be passed back +// in order to identify the call they made. Presumably one can make +// multiple sync calls of same type (as async) and want to identify what +// is what. +struct SyncMessageContext { + virtual ~SyncMessageContext() {} +}; + + +/* +[npapi] UIDelegate (UI_THREAD) +[activex] <---------------+ +[activedoc] | + | + | ChromeProxy (UI_THREAD) + +----------------+ --------------> +-------+ + URL_FETCHER <---------|ExternalTabProxy| |CFProxy| + +----------------+ +-------+ + | + ^ | + | | + +-----------------------------+ + + ChromeProxyDelegate (IPC_THREAD) + +*/ + +// ChromeProxy is an abstract class. Is forwards the commands to an +// instance of the running Chromium browser. +// A pointer to ChromeProxy instance is obtained through a +// ChromeProxyFactory object. +class ChromeProxy { + public: + // General + virtual void RemoveBrowsingData(int remove_mask) = 0; // async + virtual void InstallExtension(ChromeProxyDelegate* delegate, + const FilePath& crx_path, + SyncMessageContext* ctx) = 0; + virtual void LoadExtension(ChromeProxyDelegate* delegate, + const FilePath& path, + SyncMessageContext* ctx) = 0; + virtual void GetEnabledExtensions(ChromeProxyDelegate* delegate, + SyncMessageContext* ctx) = 0; + virtual void SetProxyConfig(const std::string& json_encoded_settings) = 0; + + // Tab management. + virtual void CreateTab(ChromeProxyDelegate* delegate, + const IPC::ExternalTabSettings& settings) = 0; + virtual void ConnectTab(ChromeProxyDelegate* delegate, HWND hwnd, + uint64 cookie) = 0; + virtual void BlockTab(uint64 cookie) = 0; + + // Tab related. + virtual void Tab_PostMessage(int tab, const std::string& message, + const std::string& origin, + const std::string& target) = 0; + virtual void Tab_Reload(int tab) = 0; + virtual void Tab_Stop(int tab) = 0; + virtual void Tab_SaveAs(int tab) = 0; + virtual void Tab_Print(int tab) = 0; + virtual void Tab_Cut(int tab) = 0; + virtual void Tab_Copy(int tab) = 0; + virtual void Tab_Paste(int tab) = 0; + virtual void Tab_SelectAll(int tab) = 0; + virtual void Tab_Find(int tab, const string16& search_string, + FindInPageDirection forward, FindInPageCase match_case, + bool find_next) = 0; + virtual void Tab_MenuCommand(int tab, int selected_command) = 0; + + // UI + virtual void Tab_Zoom(int tab, PageZoom::Function zoom_level) = 0; + virtual void Tab_FontSize(int tab, enum AutomationPageFontSize font_size) = 0; + virtual void Tab_SetInitialFocus(int tab, + bool reverse, bool restore_focus_to_view) = 0; + virtual void Tab_SetParentWindow(int tab) = 0; + virtual void Tab_Resize(int tab) = 0; + virtual void Tab_ProcessAccelerator(int tab, const MSG& msg) = 0; + + // Misc. + virtual void Tab_OnHostMoved(int tab) = 0; + virtual void Tab_RunUnloadHandlers(int tab) = 0; + virtual void Tab_SetEnableExtensionAutomation(int tab, + const std::vector<std::string>& functions_enabled) = 0; + virtual void Tab_Navigate(int tab, const GURL& url, const GURL& referrer) = 0; + virtual void Tab_OverrideEncoding(int tab, const char* encoding) = 0; + + protected: + // Accessible by ChromeProxyFactory + friend class ChromeProxyFactory; + ~ChromeProxy() {} + virtual void Init(const ProxyParams& params) = 0; + virtual int AddDelegate(ChromeProxyDelegate* delegate) = 0; + virtual int RemoveDelegate(ChromeProxyDelegate* delegate) = 0; +}; + +// The object that uses ChromeProxy should implement ChromeProxyDelegate in +// order to get notified about important events/requests coming from the +// instance of Chromium. +// Allow only one delegate per tab, i.e. delegate can handle only a single tab. +// Note: all of the methods are invoked always in a background IPC thread. +class ChromeProxyDelegate { + public: + enum DisconnectReason { + CHROME_EXE_LAUNCH_FAILED, + CHROME_EXE_LAUNCH_TIMEOUT, + CHANNEL_ERROR + }; + + virtual void Connected(ChromeProxy* proxy) = 0; + virtual void Disconnected() = 0; + virtual void PeerLost(ChromeProxy* proxy, DisconnectReason reason) = 0; + virtual int tab_handle() = 0; // to avoid reverse lookup :) + + // Sync message responses. + virtual void Completed_CreateTab(bool success, HWND chrome_wnd, + HWND tab_window, int tab_handle) = 0; + virtual void Completed_ConnectToTab(bool success, HWND chrome_window, + HWND tab_window, int tab_handle) = 0; + virtual void Completed_Navigate(bool success, + enum AutomationMsg_NavigationResponseValues res) = 0; + virtual void Completed_InstallExtension(bool success, + AutomationMsg_ExtensionResponseValues res, SyncMessageContext* ctx) = 0; + virtual void Completed_LoadExpandedExtension(bool success, + AutomationMsg_ExtensionResponseValues res, SyncMessageContext* ctx) = 0; + virtual void Completed_GetEnabledExtensions(bool success, + const std::vector<FilePath>* extensions) = 0; + + // Network requests from Chrome. + virtual void Network_Start(int request_id, + const IPC::AutomationURLRequest& request_info) = 0; + virtual void Network_Read(int request_id, int bytes_to_read) = 0; + virtual void Network_End(int request_id, const URLRequestStatus& status) = 0; + virtual void Network_DownloadInHost(int request_id) = 0; + virtual void GetCookies(const GURL& url, int cookie_id) = 0; + virtual void SetCookie(const GURL& url, const std::string& cookie) = 0; + + // Navigation progress notifications. + virtual void NavigationStateChanged(int flags, + const IPC::NavigationInfo& nav_info) = 0; + virtual void UpdateTargetUrl(const std::wstring& url) = 0; + virtual void NavigationFailed(int error_code, const GURL& gurl) = 0; + virtual void DidNavigate(const IPC::NavigationInfo& navigation_info) = 0; + virtual void TabLoaded(const GURL& url) = 0; + + // Navigation and messaging. + virtual void OpenURL(const GURL& url_to_open, const GURL& referrer, + int open_disposition) = 0; + virtual void GoToHistoryOffset(int offset) = 0; + virtual void MessageToHost(const std::string& message, + const std::string& origin, + const std::string& target) = 0; + // Misc. UI. + virtual void HandleAccelerator(const MSG& accel_message) = 0; + virtual void TabbedOut(bool reverse) = 0; + + // Tab related. + virtual void TabClosed() = 0; + virtual void AttachTab(const IPC::AttachExternalTabParams& attach_params) = 0; + protected: + ~ChromeProxyDelegate() {} +}; + +// a way to obtain a ChromeProxy implementation +struct ProxyParams { + std::string profile; + std::wstring extra_params; + FilePath profile_path; + base::TimeDelta timeout; +}; + +class ChromeProxyFactory { + public: + ChromeProxyFactory(); + ~ChromeProxyFactory(); + void GetProxy(ChromeProxyDelegate* delegate, const ProxyParams& params); + bool ReleaseProxy(ChromeProxyDelegate* delegate, const std::string& profile); + protected: + virtual ChromeProxy* CreateProxy(); + typedef std::map<std::string, ChromeProxy*> ProxyMap; + ProxyMap proxies_; + Lock lock_; +}; + +#endif // CHROME_FRAME_CFPROXY_H_ diff --git a/chrome_frame/cfproxy_factory.cc b/chrome_frame/cfproxy_factory.cc new file mode 100644 index 0000000..c936873 --- /dev/null +++ b/chrome_frame/cfproxy_factory.cc @@ -0,0 +1,79 @@ +// 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/cfproxy_private.h" +#include "base/process_util.h" + +IPC::Message::Sender* CFProxyTraits::CreateChannel(const std::string& id, + IPC::Channel::Listener* listener) { + IPC::Channel* c = new IPC::Channel(id, IPC::Channel::MODE_SERVER, listener); + if (c) + c->Connect(); // must be called on the IPC thread. + return c; +} + +void CFProxyTraits::CloseChannel(IPC::Message::Sender* s) { + IPC::Channel *c = static_cast<IPC::Channel*>(s); + delete c; +} + +bool CFProxyTraits::LaunchApp(const std::wstring& cmd_line) { + bool ok = base::LaunchApp(cmd_line, false, false, NULL); + return ok; +} + +////////////////////////////////////////////////////////// +// ChromeProxyFactory + +ChromeProxyFactory::ChromeProxyFactory() { +} + +ChromeProxyFactory::~ChromeProxyFactory() { + AutoLock lock(lock_); + ProxyMap::iterator it = proxies_.begin(); + for (; it != proxies_.end(); ++it) { + delete it->second; + } + proxies_.clear(); +} + +void ChromeProxyFactory::GetProxy(ChromeProxyDelegate* delegate, + const ProxyParams& params) { + AutoLock lock(lock_); + ChromeProxy* proxy = NULL; + // TODO(stoyan): consider extra_params/timeout + ProxyMap::iterator it = proxies_.find(params.profile); + if (it == proxies_.end()) { + proxy = CreateProxy(); + proxy->Init(params); + proxies_.insert(make_pair(params.profile, proxy)); + } else { + proxy = it->second; + } + + proxy->AddDelegate(delegate); + // TODO(stoyan): ::DeleteTimerQueueTimer (if any). +} + +bool ChromeProxyFactory::ReleaseProxy(ChromeProxyDelegate* delegate, + const std::string& profile) { + AutoLock lock(lock_); + ProxyMap::iterator it = proxies_.find(profile); + if (it == proxies_.end()) + return false; + + if (0 == it->second->RemoveDelegate(delegate)) { + // This was the last delegate for this proxy. + // TODO(stoyan): Use ::CreateTimerQueueTimer to schedule destroy of + // the proxy in a reasonable timeout. + } + + return true; +} + +static CFProxyTraits g_default_traits; +ChromeProxy* ChromeProxyFactory::CreateProxy() { + ChromeProxy* p = new CFProxy(&g_default_traits); + return p; +} diff --git a/chrome_frame/cfproxy_private.h b/chrome_frame/cfproxy_private.h new file mode 100644 index 0000000..a9cc6dc --- /dev/null +++ b/chrome_frame/cfproxy_private.h @@ -0,0 +1,192 @@ +// 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. + +#ifndef CHROME_FRAME_CFPROXY_PRIVATE_H_ +#define CHROME_FRAME_CFPROXY_PRIVATE_H_ +#pragma once + +#include <map> +#include <set> +#include <string> +#include <vector> +#include "chrome_frame/cfproxy.h" +#include "base/thread.h" +// Since we can't forward declare IPC::Message::Sender or IPC::Channel::Listener +#include "ipc/ipc_message.h" +#include "ipc/ipc_channel.h" + +typedef std::map<int, ChromeProxyDelegate*> TabsMap; + +// This is the functions needed by CFProxy implementation. +// Extracted in separate class so we can mock it. +class CFProxyTraits { + public: + virtual IPC::Message::Sender* CreateChannel(const std::string& id, + IPC::Channel::Listener* l); + virtual void CloseChannel(IPC::Message::Sender* s); + virtual bool LaunchApp(const std::wstring& cmd_line); +}; + +// Holds a queue of sent synchronous IPC messages. Knows how to dispatch +// the replies. +class SyncMsgSender { + public: + explicit SyncMsgSender(TabsMap* tab2delegate); + void QueueSyncMessage(const IPC::SyncMessage* msg, + ChromeProxyDelegate* delegate, SyncMessageContext* ctx); + bool OnReplyReceived(const IPC::Message* reply_msg); + void OnChannelClosed(); + void Cancel(ChromeProxyDelegate* delegate); + private: + // sync_message_id -> (message_type, delegate, context) + struct SingleSentMessage { + SingleSentMessage() : type_(0), ctx_(NULL), delegate_(NULL) {} + SingleSentMessage(uint32 type, + ChromeProxyDelegate* delegate, + SyncMessageContext* ctx) + : type_(type), ctx_(ctx), delegate_(delegate) {} + ~SingleSentMessage() { delete ctx_; } + uint32 type_; + SyncMessageContext* ctx_; + ChromeProxyDelegate* delegate_; + }; + + SingleSentMessage* RemoveMessage(int id); + typedef std::map<int, SingleSentMessage*> SentMessages; + SentMessages messages_; + Lock messages_lock_; + TabsMap* tab2delegate_; +}; + +// Converts method call to an IPC message and then send it over Message::Sender +class Interface2IPCMessage : public ChromeProxy { + public: + Interface2IPCMessage() {} + + // General + virtual void RemoveBrowsingData(int mask); + virtual void SetProxyConfig(const std::string& json_encoded_proxy_cfg); + // Tab related. + virtual void Tab_PostMessage(int tab, const std::string& message, + const std::string& origin, const std::string& target); + virtual void Tab_Reload(int tab); + virtual void Tab_Stop(int tab); + virtual void Tab_SaveAs(int tab); + virtual void Tab_Print(int tab); + virtual void Tab_Cut(int tab); + virtual void Tab_Copy(int tab); + virtual void Tab_Paste(int tab); + virtual void Tab_SelectAll(int tab); + virtual void Tab_MenuCommand(int tab, int selected_command); + virtual void Tab_Zoom(int tab, PageZoom::Function zoom_level); + virtual void Tab_FontSize(int tab, enum AutomationPageFontSize font_size); + virtual void Tab_SetInitialFocus(int tab, bool reverse, + bool restore_focus_to_view); + virtual void Tab_SetParentWindow(int tab); + virtual void Tab_Resize(int tab); + virtual void Tab_ProcessAccelerator(int tab, const MSG& msg); + + // Misc. + virtual void Tab_OnHostMoved(int tab); + virtual void Tab_SetEnableExtensionAutomation(int tab, + const std::vector<std::string>& functions_enabled); + protected: + ~Interface2IPCMessage() {} + private: + IPC::Message::Sender* sender_; +}; + +// Simple class to keep a list of pointers to ChromeProxyDelegate for a +// specific proxy as well as mapping between tab_id -> ChromeProxyDelegate. +class DelegateHolder { + protected: + DelegateHolder() { + } + + void AddDelegate(ChromeProxyDelegate* p); + void RemoveDelegate(ChromeProxyDelegate* p); + // Helper + ChromeProxyDelegate* Tab2Delegate(int tab_handle); + TabsMap tab2delegate_; + typedef std::set<ChromeProxyDelegate*> DelegateList; + DelegateList delegate_list_; +}; + +// ChromeFrame Automation Proxy implementation. +class CFProxy : public Interface2IPCMessage, + public IPC::Channel::Listener, + public DelegateHolder { + public: + explicit CFProxy(CFProxyTraits* api); + ~CFProxy(); + + private: + virtual void Init(const ProxyParams& params); + virtual int AddDelegate(ChromeProxyDelegate* p); + virtual int RemoveDelegate(ChromeProxyDelegate* p); + + // Executed in IPC thread. + void AddDelegateOnIoThread(ChromeProxyDelegate* p); + void RemoveDelegateOnIoThread(ChromeProxyDelegate* p); + // Initialization that has to be mede in IPC thread. + void InitInIoThread(const ProxyParams& params); + // Cleanup that has to be made in IPC thread. + void CleanupOnIoThread(); + // IPC connection was not established in timely manner + void LaunchTimeOut(); + // Close channel, inform delegates. + void OnPeerLost(ChromeProxyDelegate::DisconnectReason reason); + // Queues message to be send in IPC thread. + void SendIpcMessage(IPC::Message* m); + // Same but in IPC thread. + void SendIpcMessageOnIoThread(IPC::Message* m); + + ////////////////////////////////////////////////////////////////////////// + // Sync messages. + virtual void InstallExtension(ChromeProxyDelegate* delegate, + const FilePath& crx_path, SyncMessageContext* ctx); + virtual void LoadExtension(ChromeProxyDelegate* delegate, + const FilePath& path, SyncMessageContext* ctx); + virtual void GetEnabledExtensions(ChromeProxyDelegate* delegate, + SyncMessageContext* ctx); + virtual void Tab_Find(int tab, const string16& search_string, + FindInPageDirection forward, FindInPageCase match_case, bool find_next); + virtual void Tab_OverrideEncoding(int tab, const char* encoding); + virtual void Tab_Navigate(int tab, const GURL& url, const GURL& referrer); + virtual void CreateTab(ChromeProxyDelegate* delegate, + const IPC::ExternalTabSettings& p); + virtual void ConnectTab(ChromeProxyDelegate* delegate, HWND hwnd, + uint64 cookie); + virtual void BlockTab(uint64 cookie); + virtual void Tab_RunUnloadHandlers(int tab); + + ////////////////////////////////////////////////////////////////////////// + // IPC::Channel::Listener + virtual void OnMessageReceived(const IPC::Message& message); + virtual void OnChannelConnected(int32 peer_pid); + virtual void OnChannelError(); + + bool CalledOnIpcThread() const { + return PlatformThread::CurrentId() == ipc_thread_.thread_id(); + } + + base::Thread ipc_thread_; + SyncMsgSender sync_dispatcher_; + IPC::Message::Sender* ipc_sender_; + CFProxyTraits* api_; + int delegate_count_; + bool is_connected_; +}; + +DISABLE_RUNNABLE_METHOD_REFCOUNT(CFProxy); + +// Support functions. +std::string GenerateChannelId(); +int IsTabMessage(const IPC::Message& message); +bool DispatchTabMessageToDelegate(ChromeProxyDelegate* delegate, + const IPC::Message& m); +std::wstring BuildCmdLine(const std::string& channel_id, + const FilePath& profile_path, + const std::wstring& extra_args); +#endif // CHROME_FRAME_CFPROXY_PRIVATE_H_ diff --git a/chrome_frame/cfproxy_proxy.cc b/chrome_frame/cfproxy_proxy.cc new file mode 100644 index 0000000..5a826c7 --- /dev/null +++ b/chrome_frame/cfproxy_proxy.cc @@ -0,0 +1,246 @@ +// 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/cfproxy_private.h" + +#include "base/tuple.h" +#include "ipc/ipc_sync_message.h" +#include "chrome/test/automation/automation_messages.h" + +CFProxy::CFProxy(CFProxyTraits* api) : ipc_thread_("ipc"), + sync_dispatcher_(&tab2delegate_), + ipc_sender_(NULL), + api_(api), + delegate_count_(0), + is_connected_(false) { +} + +CFProxy::~CFProxy() { + ipc_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this, + &CFProxy::CleanupOnIoThread)); + // ipc_thread destructor will do the Stop anyway. this is for debug :) + ipc_thread_.Stop(); +} + + +void CFProxy::Init(const ProxyParams& params) { + ipc_thread_.StartWithOptions(base::Thread::Options(MessageLoop::TYPE_IO, 0)); + ipc_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this, + &CFProxy::InitInIoThread, params)); +} + +int CFProxy::AddDelegate(ChromeProxyDelegate* proxy) { + ipc_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this, + &CFProxy::AddDelegateOnIoThread, proxy)); + return ++delegate_count_; +} + +int CFProxy::RemoveDelegate(ChromeProxyDelegate* proxy) { + ipc_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this, + &CFProxy::RemoveDelegateOnIoThread, proxy)); + return --delegate_count_; +} + +void CFProxy::AddDelegateOnIoThread(ChromeProxyDelegate* proxy) { + DCHECK(CalledOnIpcThread()); + DelegateHolder::AddDelegate(proxy); + if (is_connected_) { + proxy->Connected(this); + } +} + +void CFProxy::RemoveDelegateOnIoThread(ChromeProxyDelegate* proxy) { + DCHECK(CalledOnIpcThread()); + DelegateHolder::RemoveDelegate(proxy); + proxy->Disconnected(); +} + +void CFProxy::InitInIoThread(const ProxyParams& params) { + DCHECK(CalledOnIpcThread()); + std::string channel_id = GenerateChannelId(); + ipc_sender_ = api_->CreateChannel(channel_id, this); + std::wstring cmd_line = BuildCmdLine(channel_id, params.profile_path, + params.extra_params); + if (api_->LaunchApp(cmd_line)) { + CancelableTask* launch_timeout = NewRunnableMethod(this, + &CFProxy::LaunchTimeOut); + ipc_thread_.message_loop()->PostDelayedTask(FROM_HERE, launch_timeout, + params.timeout.InMilliseconds()); + } else { + OnPeerLost(ChromeProxyDelegate::CHROME_EXE_LAUNCH_FAILED); + } +} + +void CFProxy::CleanupOnIoThread() { + DCHECK(CalledOnIpcThread()); + if (ipc_sender_) { + api_->CloseChannel(ipc_sender_); + ipc_sender_ = NULL; + } + // TODO(stoyan): shall we notify delegates? + // The object is dying, so under normal circumstances there should be + // no delegates. + DCHECK_EQ(0, delegate_count_); + DCHECK_EQ(0u, delegate_list_.size()); + DCHECK_EQ(0u, tab2delegate_.size()); +} + +void CFProxy::LaunchTimeOut() { + DCHECK(CalledOnIpcThread()); + if (!is_connected_) { + OnPeerLost(ChromeProxyDelegate::CHROME_EXE_LAUNCH_TIMEOUT); + } +} + +void CFProxy::OnPeerLost(ChromeProxyDelegate::DisconnectReason reason) { + // Kill the channel. Inform delegates + DCHECK(CalledOnIpcThread()); + if (ipc_sender_) { + api_->CloseChannel(ipc_sender_); + ipc_sender_ = NULL; + } + + for (DelegateList::iterator it = delegate_list_.begin(); + it != delegate_list_.end(); ++it) { + (*it)->PeerLost(this, reason); + } +} + +void CFProxy::SendIpcMessage(IPC::Message* m) { + ipc_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this, + &CFProxy::SendIpcMessageOnIoThread, m)); +} + +void CFProxy::SendIpcMessageOnIoThread(IPC::Message* m) { + DCHECK(CalledOnIpcThread()); + if (ipc_sender_) { + ipc_sender_->Send(m); + } else { + delete m; + } +} + +////////////////////////////////////////////////////////////////////////// +// Sync messages. +void CFProxy::InstallExtension(ChromeProxyDelegate* delegate, + const FilePath& crx_path, + SyncMessageContext* ctx) { + IPC::SyncMessage* m = new AutomationMsg_InstallExtension(0, crx_path, NULL); + sync_dispatcher_.QueueSyncMessage(m, delegate, ctx); + SendIpcMessage(m); +} + +void CFProxy::LoadExtension(ChromeProxyDelegate* delegate, + const FilePath& path, SyncMessageContext* ctx) { + IPC::SyncMessage* m = new AutomationMsg_LoadExpandedExtension(0, path, 0); + sync_dispatcher_.QueueSyncMessage(m, delegate, ctx); + SendIpcMessage(m); +} + +void CFProxy::GetEnabledExtensions(ChromeProxyDelegate* delegate, + SyncMessageContext* ctx) { + IPC::SyncMessage* m = new AutomationMsg_GetEnabledExtensions(0, NULL); + sync_dispatcher_.QueueSyncMessage(m, delegate, ctx); + SendIpcMessage(m); +} + +void CFProxy::Tab_Find(int tab, const string16& search_string, + FindInPageDirection forward, FindInPageCase match_case, + bool find_next) { + AutomationMsg_Find_Params params; + params.unused = 0; + params.search_string = search_string; + params.find_next = find_next; + params.match_case = (match_case == CASE_SENSITIVE); + params.forward = (forward == FWD); + IPC::SyncMessage* m = new AutomationMsg_Find(0, tab, params, NULL, NULL); + // Not interested in result. + sync_dispatcher_.QueueSyncMessage(m, NULL, NULL); + SendIpcMessage(m); +} + +void CFProxy::Tab_OverrideEncoding(int tab, const char* encoding) { + IPC::SyncMessage* m = new AutomationMsg_OverrideEncoding(0, tab, encoding, + NULL); + // Not interested in result. + sync_dispatcher_.QueueSyncMessage(m, NULL, NULL); + SendIpcMessage(m); +} + +void CFProxy::Tab_Navigate(int tab, const GURL& url, const GURL& referrer) { + IPC::SyncMessage* m = new AutomationMsg_NavigateInExternalTab(0, + tab, url, referrer, NULL); + // We probably are not interested in result since provider just checks + // whether tab handle is valid. + sync_dispatcher_.QueueSyncMessage(m, NULL, NULL); + SendIpcMessage(m); +} + +void CFProxy::CreateTab(ChromeProxyDelegate* delegate, + const IPC::ExternalTabSettings& p) { + IPC::SyncMessage* m = new AutomationMsg_CreateExternalTab(0, p, 0, 0, 0); + sync_dispatcher_.QueueSyncMessage(m, delegate, NULL); + SendIpcMessage(m); +} + +void CFProxy::ConnectTab(ChromeProxyDelegate* delegate, HWND hwnd, + uint64 cookie) { + IPC::SyncMessage* m = new AutomationMsg_ConnectExternalTab(0, cookie, true, + hwnd, NULL, NULL, NULL); + sync_dispatcher_.QueueSyncMessage(m, delegate, NULL); + SendIpcMessage(m); +} + +void CFProxy::BlockTab(uint64 cookie) { + IPC::SyncMessage* m = new AutomationMsg_ConnectExternalTab(0, cookie, false, + NULL, NULL, NULL, NULL); + sync_dispatcher_.QueueSyncMessage(m, NULL, NULL); + SendIpcMessage(m); +} + +void CFProxy::Tab_RunUnloadHandlers(int tab) { + IPC::SyncMessage* m = new AutomationMsg_RunUnloadHandlers(0, tab, 0); + ChromeProxyDelegate* p = Tab2Delegate(tab); + sync_dispatcher_.QueueSyncMessage(m, p, NULL); + SendIpcMessage(m); +} + +// IPC::Channel::Listener +void CFProxy::OnMessageReceived(const IPC::Message& message) { + // Handle sync message reply. + bool done = sync_dispatcher_.OnReplyReceived(&message); + if (done) + return; + + // Handle tab related message. + int tab_handle = IsTabMessage(message); + if (tab_handle != 0) { + ChromeProxyDelegate* d = Tab2Delegate(tab_handle); + if (d) + DispatchTabMessageToDelegate(d, message); + return; + } + + DLOG(WARNING) << "Unknown message received!"; +} + +void CFProxy::OnChannelConnected(int32 peer_pid) { + is_connected_ = true; + // TODO(stoyan): May be we should wait for Hello message. + for (DelegateList::iterator it = delegate_list_.begin(); + it != delegate_list_.end(); ++it) { + (*it)->Connected(this); + } +} + +void CFProxy::OnChannelError() { + is_connected_ = false; + + // Inform the sync message callbacks that there are not going to see + // any reply. + sync_dispatcher_.OnChannelClosed(); + OnPeerLost(ChromeProxyDelegate::CHANNEL_ERROR); + + // TODO(stoyan): Relaunch? +} diff --git a/chrome_frame/cfproxy_support.cc b/chrome_frame/cfproxy_support.cc new file mode 100644 index 0000000..72454c0 --- /dev/null +++ b/chrome_frame/cfproxy_support.cc @@ -0,0 +1,450 @@ +// 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/cfproxy_private.h" + +#include "base/atomic_sequence_num.h" +#include "base/command_line.h" +#include "base/process_util.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/test/automation/automation_messages.h" +#include "chrome_frame/chrome_launcher_utils.h" +#include "chrome_frame/utils.h" // for IsHeadlessMode(); + + +namespace { +void DispatchReplyFail(uint32 type, + ChromeProxyDelegate* delegate, + SyncMessageContext* ctx) { + switch (type) { + case AutomationMsg_CreateExternalTab::ID: + delegate->Completed_CreateTab(false, NULL, NULL, NULL); + break; + case AutomationMsg_ConnectExternalTab::ID: + delegate->Completed_ConnectToTab(false, NULL, NULL, NULL); + break; + case AutomationMsg_InstallExtension::ID: + delegate->Completed_InstallExtension(false, + AUTOMATION_MSG_EXTENSION_INSTALL_FAILED, ctx); + break; + } +} + +bool DispatchReplyOk(const IPC::Message* reply_msg, uint32 type, + ChromeProxyDelegate* delegate, SyncMessageContext* ctx, + TabsMap* tab2delegate) { + void* iter = IPC::SyncMessage::GetDataIterator(reply_msg); + switch (type) { + case AutomationMsg_CreateExternalTab::ID: { + // Tuple3<HWND, HWND, int> out; + TupleTypes<AutomationMsg_CreateExternalTab::ReplyParam>::ValueTuple out; + if (ReadParam(reply_msg, &iter, &out)) { + DCHECK(tab2delegate->find(out.c) == tab2delegate->end()); + (*tab2delegate)[out.c] = delegate; + delegate->Completed_CreateTab(true, out.a, out.b, out.c); + } + return true; + } + + case AutomationMsg_ConnectExternalTab::ID: { + // Tuple3<HWND, HWND, int> out; + TupleTypes<AutomationMsg_ConnectExternalTab::ReplyParam>::ValueTuple out; + if (ReadParam(reply_msg, &iter, &out)) { + DCHECK(tab2delegate->find(out.c) == tab2delegate->end()); + (*tab2delegate)[out.c] = delegate; + delegate->Completed_ConnectToTab(true, out.a, out.b, out.c); + } + return true; + } + + case AutomationMsg_InstallExtension::ID: { + // Tuple1<AutomationMsg_ExtensionResponseValues> out; + TupleTypes<AutomationMsg_InstallExtension::ReplyParam>::ValueTuple out; + if (ReadParam(reply_msg, &iter, &out)) + delegate->Completed_InstallExtension(true, out.a, ctx); + return true; + } + + case AutomationMsg_LoadExpandedExtension::ID: { + // Tuple1<AutomationMsg_ExtensionResponseValues> out; + TupleTypes<AutomationMsg_LoadExpandedExtension::ReplyParam>::ValueTuple + out; + if (ReadParam(reply_msg, &iter, &out)) + delegate->Completed_LoadExpandedExtension(true, out.a, ctx); + break; + } + + case AutomationMsg_GetEnabledExtensions::ID: { + // Tuple1<std::vector<FilePath> > + TupleTypes<AutomationMsg_GetEnabledExtensions::ReplyParam>::ValueTuple + out; + if (ReadParam(reply_msg, &iter, &out)) + delegate->Completed_GetEnabledExtensions(true, &out.a); + break; + } + } // switch + + return false; +} +} // namespace + +// Itf2IPCMessage +// Converts and sends trivial messages. +void Interface2IPCMessage::RemoveBrowsingData(int mask) { + sender_->Send(new AutomationMsg_RemoveBrowsingData(0, mask)); +} + +void Interface2IPCMessage::SetProxyConfig( + const std::string& json_encoded_proxy_cfg) { + sender_->Send(new AutomationMsg_SetProxyConfig(0, json_encoded_proxy_cfg)); +} + +// Tab related. +void Interface2IPCMessage::Tab_PostMessage(int tab, const std::string& message, + const std::string& origin, const std::string& target) { + sender_->Send(new AutomationMsg_HandleMessageFromExternalHost( + 0, tab, message, origin, target)); +} + +void Interface2IPCMessage::Tab_Reload(int tab) { + sender_->Send(new AutomationMsg_ReloadAsync(0, tab)); +} + +void Interface2IPCMessage::Tab_Stop(int tab) { + sender_->Send(new AutomationMsg_StopAsync(0, tab)); +} + +void Interface2IPCMessage::Tab_SaveAs(int tab) { + sender_->Send(new AutomationMsg_SaveAsAsync(0, tab)); +} + +void Interface2IPCMessage::Tab_Print(int tab) { + sender_->Send(new AutomationMsg_PrintAsync(0, tab)); +} + +void Interface2IPCMessage::Tab_Cut(int tab) { + sender_->Send(new AutomationMsg_Cut(0, tab)); +} + +void Interface2IPCMessage::Tab_Copy(int tab) { + sender_->Send(new AutomationMsg_Copy(0, tab)); +} + +void Interface2IPCMessage::Tab_Paste(int tab) { + sender_->Send(new AutomationMsg_Paste(0, tab)); +} + +void Interface2IPCMessage::Tab_SelectAll(int tab) { + sender_->Send(new AutomationMsg_SelectAll(0, tab)); +} + +void Interface2IPCMessage::Tab_MenuCommand(int tab, int selected_command) { + sender_->Send(new AutomationMsg_ForwardContextMenuCommandToChrome( + 0, tab, selected_command)); +} + +void Interface2IPCMessage::Tab_Zoom(int tab, PageZoom::Function zoom_level) { + sender_->Send(new AutomationMsg_SetZoomLevel(0, tab, zoom_level)); +} + +void Interface2IPCMessage::Tab_FontSize(int tab, + enum AutomationPageFontSize font_size) { + sender_->Send(new AutomationMsg_SetPageFontSize(0, tab, font_size)); +} + +void Interface2IPCMessage::Tab_SetInitialFocus(int tab, bool reverse, + bool restore_focus_to_view) { + sender_->Send(new AutomationMsg_SetInitialFocus(0, tab, reverse, + restore_focus_to_view)); +} + +void Interface2IPCMessage::Tab_SetParentWindow(int tab) { + CHECK(0) << "Implement me"; + // AutomationMsg_TabReposition +} + +void Interface2IPCMessage::Tab_Resize(int tab) { + CHECK(0) << "Implement me"; + // AutomationMsg_TabReposition +} + +void Interface2IPCMessage::Tab_ProcessAccelerator(int tab, const MSG& msg) { + sender_->Send(new AutomationMsg_ProcessUnhandledAccelerator(0, tab, msg)); +} + +// Misc. +void Interface2IPCMessage::Tab_OnHostMoved(int tab) { + sender_->Send(new AutomationMsg_BrowserMove(0, tab)); +} + +void Interface2IPCMessage::Tab_SetEnableExtensionAutomation(int tab, + const std::vector<std::string>& functions_enabled) { + sender_->Send(new AutomationMsg_SetEnableExtensionAutomation(0, tab, + functions_enabled)); +} + +void DelegateHolder::AddDelegate(ChromeProxyDelegate* p) { + delegate_list_.insert(p); +} + +void DelegateHolder::RemoveDelegate(ChromeProxyDelegate* p) { + // DCHECK(CalledOnValidThread()); + int tab_handle = p->tab_handle(); // Could be 0. + delegate_list_.erase(p); + tab2delegate_.erase(tab_handle); +} + +ChromeProxyDelegate* DelegateHolder::Tab2Delegate(int tab_handle) { + TabsMap::const_iterator iter = tab2delegate_.find(tab_handle); + if (iter != tab2delegate_.end()) + return iter->second; + return NULL; +} + +SyncMsgSender::SyncMsgSender(TabsMap* tab2delegate) + : tab2delegate_(tab2delegate) { +} + +// The outgoing queue of sync messages must be locked. +// Case: ui thread is sending message and waits for event, that is going to be +// signaled by completion handler in ipc_thread. +// We must append the message to the outgoing queue in UI thread, +// otherwise if channel is disconnected before having a chance to +// send the message, the ChromeProxyDelegate::_Disconnect implementation +// shall know how to unblock arbitrary sync call. Instead +// ChromeProxyDelgate::Completed_XXXX knows how to unblock a specific one. +void SyncMsgSender::QueueSyncMessage(const IPC::SyncMessage* msg, + ChromeProxyDelegate* delegate, + SyncMessageContext* ctx) { + if (delegate) { + // We are interested of the result. + AutoLock lock(messages_lock_); + int id = IPC::SyncMessage::GetMessageId(*msg); + // A message can be sent only once. + DCHECK(messages_.end() == messages_.find(id)); + messages_[id] = new SingleSentMessage(msg->type(), delegate, ctx); + } +} + +void SyncMsgSender::Cancel(ChromeProxyDelegate* delegate) { + // TODO(stoyan): Cancel all outgoing calls for this delegate + // We may not need this. :) +} + +SyncMsgSender::SingleSentMessage* SyncMsgSender::RemoveMessage(int id) { + AutoLock lock(messages_lock_); + SentMessages::iterator it = messages_.find(id); + if (it == messages_.end()) { + // Delegate is not interested in this sync message response. + return NULL; + } + + // See what message is this. + SingleSentMessage* origin = it->second; + messages_.erase(it); + return origin; +} + +bool SyncMsgSender::OnReplyReceived(const IPC::Message* reply_msg) { + if (!reply_msg->is_reply()) + return false; // Not a reply to sync message. + + // Find message by id. + int id = IPC::SyncMessage::GetMessageId(*reply_msg); + SingleSentMessage* origin = RemoveMessage(id); + if (origin) { + DispatchReplyOk(reply_msg, origin->type_, origin->delegate_, origin->ctx_, + tab2delegate_); + delete origin; + } + + return true; +} + +void SyncMsgSender::OnChannelClosed() { + SentMessages messages_sent; + // Make a copy of the messages queue + { + AutoLock lock(messages_lock_); + messages_.swap(messages_sent); + } + + + SentMessages::reverse_iterator it = messages_sent.rbegin(); + for (; it != messages_sent.rend(); ++it) { + SingleSentMessage* origin = it->second; + DispatchReplyFail(origin->type_, origin->delegate_, origin->ctx_); + delete origin; + } + messages_sent.clear(); +} + +static base::AtomicSequenceNumber g_proxy_channel_id(base::LINKER_INITIALIZED); +std::string GenerateChannelId() { + return StringPrintf("ChromeTestingInterface:%u.%d", + base::GetCurrentProcId(), g_proxy_channel_id.GetNext() + 0xC000); +} + +std::wstring BuildCmdLine(const std::string& channel_id, + const FilePath& profile_path, + const std::wstring& extra_args) { + scoped_ptr<CommandLine> command_line( + chrome_launcher::CreateLaunchCommandLine()); + command_line->AppendSwitchASCII(switches::kAutomationClientChannelID, + 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); + +#ifndef NDEBUG + // Disable the "Whoa! Chrome has crashed." dialog, because that isn't very + // useful for Chrome Frame users. + 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); + + command_line->AppendSwitchPath(switches::kUserDataDir, profile_path); + + std::wstring command_line_string(command_line->command_line_string()); + if (!extra_args.empty()) { + command_line_string.append(L" "); + command_line_string.append(extra_args); + } + return command_line_string; +} + +int IsTabMessage(const IPC::Message& message) { + switch (message.type()) { + case AutomationMsg_NavigationStateChanged__ID: + case AutomationMsg_UpdateTargetUrl__ID: + case AutomationMsg_HandleAccelerator__ID: + case AutomationMsg_TabbedOut__ID: + case AutomationMsg_OpenURL__ID: + case AutomationMsg_NavigationFailed__ID: + case AutomationMsg_DidNavigate__ID: + case AutomationMsg_TabLoaded__ID: + case AutomationMsg_ForwardMessageToExternalHost__ID: + case AutomationMsg_ForwardContextMenuToExternalHost__ID: + case AutomationMsg_RequestStart__ID: + case AutomationMsg_RequestRead__ID: + case AutomationMsg_RequestEnd__ID: + case AutomationMsg_DownloadRequestInHost__ID: + case AutomationMsg_SetCookieAsync__ID: + case AutomationMsg_AttachExternalTab__ID: + case AutomationMsg_RequestGoToHistoryEntryOffset__ID: + case AutomationMsg_GetCookiesFromHost__ID: + case AutomationMsg_CloseExternalTab__ID: { + // Read tab handle from the message. + void* iter = NULL; + int tab_handle = 0; + message.ReadInt(&iter, &tab_handle); + return tab_handle; + } + } + + return 0; +} + +bool DispatchTabMessageToDelegate(ChromeProxyDelegate* delegate, + const IPC::Message& m) { + void* iter = 0; + switch (m.type()) { + case AutomationMsg_NavigationStateChanged__ID: { + // Tuple3<int, int, IPC::NavigationInfo> + AutomationMsg_NavigationStateChanged::Param params; + if (ReadParam(&m, &iter, ¶ms)) + delegate->NavigationStateChanged(params.b, params.c); + return true; + } + + case AutomationMsg_UpdateTargetUrl__ID: { + // Tuple2<int, std::wstring> + AutomationMsg_UpdateTargetUrl::Param params; + if (ReadParam(&m, &iter, ¶ms)) + delegate->UpdateTargetUrl(params.b); + return true; + } + + case AutomationMsg_HandleAccelerator__ID: { + AutomationMsg_HandleAccelerator::Param params; + return true; + } + + case AutomationMsg_TabbedOut__ID: { + AutomationMsg_TabbedOut::Param params; + return true; + } + + case AutomationMsg_OpenURL__ID: { + AutomationMsg_OpenURL::Param params; + return true; + } + + case AutomationMsg_NavigationFailed__ID: { + AutomationMsg_NavigationFailed::Param params; + return true; + } + + case AutomationMsg_DidNavigate__ID: { + AutomationMsg_DidNavigate::Param params; + return true; + } + + case AutomationMsg_TabLoaded__ID: { + return true; + } + case AutomationMsg_ForwardMessageToExternalHost__ID: { + return true; + } + + case AutomationMsg_ForwardContextMenuToExternalHost__ID: { + return true; + } + + case AutomationMsg_RequestStart__ID: { + return true; + } + + case AutomationMsg_RequestRead__ID: { + return true; + } + + case AutomationMsg_RequestEnd__ID: { + return true; + } + + case AutomationMsg_DownloadRequestInHost__ID: { + return true; + } + + case AutomationMsg_SetCookieAsync__ID: { + return true; + } + + case AutomationMsg_AttachExternalTab__ID: { + return true; + } + + case AutomationMsg_RequestGoToHistoryEntryOffset__ID: { + return true; + } + + case AutomationMsg_GetCookiesFromHost__ID: { + return true; + } + + case AutomationMsg_CloseExternalTab__ID: { + return true; + } + } + return true; +} diff --git a/chrome_frame/cfproxy_test.cc b/chrome_frame/cfproxy_test.cc new file mode 100644 index 0000000..a291103 --- /dev/null +++ b/chrome_frame/cfproxy_test.cc @@ -0,0 +1,316 @@ +// 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 <string> +#include "base/file_path.h" +#include "base/waitable_event.h" +#include "chrome_frame/cfproxy_private.h" +#include "chrome/test/automation/automation_messages.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gmock_mutant.h" + +using testing::_; +using testing::DoAll; +using testing::NotNull; +using testing::Return; +using testing::StrictMock; +using testing::InvokeWithoutArgs; +using testing::WithoutArgs; +using testing::CreateFunctor; + +// There is not much to test here since CFProxy is pretty dumb. +struct MockFactory : public ChromeProxyFactory { + MOCK_METHOD0(CreateProxy, ChromeProxy*()); +}; + +struct MockChromeProxyDelegate : public ChromeProxyDelegate { + MOCK_METHOD1(Connected, void(ChromeProxy* proxy)); + MOCK_METHOD2(PeerLost, void(ChromeProxy*, enum DisconnectReason reason)); + MOCK_METHOD0(Disconnected, void()); + MOCK_METHOD0(tab_handle, int()); + + MOCK_METHOD4(Completed_CreateTab, void(bool success, HWND chrome_wnd, + HWND tab_window, int tab_handle)); + MOCK_METHOD4(Completed_ConnectToTab, void(bool success, HWND chrome_window, + HWND tab_window, int tab_handle)); + MOCK_METHOD2(Completed_Navigate, void(bool success, + enum AutomationMsg_NavigationResponseValues res)); + MOCK_METHOD3(Completed_InstallExtension, void(bool success, + enum AutomationMsg_ExtensionResponseValues res, SyncMessageContext* ctx)); + MOCK_METHOD3(Completed_LoadExpandedExtension, void(bool success, + enum AutomationMsg_ExtensionResponseValues res, SyncMessageContext* ctx)); + MOCK_METHOD2(Completed_GetEnabledExtensions, void(bool success, + const std::vector<FilePath>* v)); + + // Network requests from Chrome. + MOCK_METHOD2(Network_Start, void(int request_id, + const IPC::AutomationURLRequest& request_info)); + MOCK_METHOD2(Network_Read, void(int request_id, int bytes_to_read)); + MOCK_METHOD2(Network_End, void(int request_id, const URLRequestStatus& s)); + MOCK_METHOD1(Network_DownloadInHost, void(int request_id)); + MOCK_METHOD2(GetCookies, void(const GURL& url, int cookie_id)); + MOCK_METHOD2(SetCookie, void(const GURL& url, const std::string& cookie)); + + // Navigation progress notifications. + MOCK_METHOD2(NavigationStateChanged, void(int flags, + const IPC::NavigationInfo& nav_info)); + MOCK_METHOD1(UpdateTargetUrl, void(const std::wstring& url)); + MOCK_METHOD2(NavigationFailed, void(int error_code, const GURL& gurl)); + MOCK_METHOD1(DidNavigate, void(const IPC::NavigationInfo& navigation_info)); + MOCK_METHOD1(TabLoaded, void(const GURL& url)); + + // + MOCK_METHOD3(OpenURL, void(const GURL& url_to_open, const GURL& referrer, + int open_disposition)); + MOCK_METHOD1(GoToHistoryOffset, void(int offset)); + MOCK_METHOD3(MessageToHost, void(const std::string& message, + const std::string& origin, const std::string& target)); + + // Misc. UI. + MOCK_METHOD1(HandleAccelerator, void(const MSG& accel_message)); + MOCK_METHOD1(TabbedOut, void(bool reverse)); + + // + MOCK_METHOD0(TabClosed, void()); + MOCK_METHOD1(AttachTab, + void(const IPC::AttachExternalTabParams& attach_params)); +}; + +struct MockSender : public IPC::Message::Sender { + MOCK_METHOD1(Send, bool(IPC::Message* m)); +}; + +struct MockCFProxyTraits : public CFProxyTraits { + MOCK_METHOD2(DoCreateChannel, IPC::Message::Sender*(const std::string& id, + IPC::Channel::Listener* l)); + MOCK_METHOD1(CloseChannel, void(IPC::Message::Sender* s)); + MOCK_METHOD1(LaunchApp, bool(const std::wstring& cmd_line)); + + // Forward the CreateChannel to DoCreateChannel, but save the ipc_thread + // and the listener (i.e. proxy implementation of Channel::Listener) + virtual IPC::Message::Sender* CreateChannel(const std::string& id, + IPC::Channel::Listener* l) { + ipc_loop = MessageLoop::current(); + listener = l; + return this->DoCreateChannel(id, l); + } + + // Simulate some activity in the IPC thread. + // You may find API_FIRE_XXXX macros (see below) handy instead. + void FireConnect(base::TimeDelta t) { + ASSERT_TRUE(ipc_loop != NULL); + ipc_loop->PostDelayedTask(FROM_HERE, NewRunnableMethod(listener, + &IPC::Channel::Listener::OnChannelConnected, 0), t.InMilliseconds()); + } + + void FireError(base::TimeDelta t) { + ASSERT_TRUE(ipc_loop != NULL); + ipc_loop->PostDelayedTask(FROM_HERE, NewRunnableMethod(listener, + &IPC::Channel::Listener::OnChannelError), t.InMilliseconds()); + } + + void FireMessage(const IPC::Message& m, base::TimeDelta t) { + ASSERT_TRUE(ipc_loop != NULL); + ipc_loop->PostDelayedTask(FROM_HERE, NewRunnableMethod(listener, + &IPC::Channel::Listener::OnMessageReceived, m), t.InMilliseconds()); + } + + MockCFProxyTraits() : ipc_loop(NULL) {} + MockSender sender; + private: + MessageLoop* ipc_loop; + IPC::Channel::Listener* listener; +}; + +// Handy macros when we want so similate something on the IPC thread. +#define API_FIRE_CONNECT(api, t) InvokeWithoutArgs(CreateFunctor(&api, \ + &MockCFProxyTraits::FireConnect, t)) +#define API_FIRE_ERROR(api, t) InvokeWithoutArgs(CreateFunctor(&api, \ + &MockCFProxyTraits::FireError, t)) +#define API_FIRE_MESSAGE(api, t) InvokeWithoutArgs(CreateFunctor(&api, \ + &MockCFProxyTraits::FireMessage, t)) +DISABLE_RUNNABLE_METHOD_REFCOUNT(IPC::Channel::Listener); + +TEST(ChromeProxy, DelegateAddRemove) { + StrictMock<MockCFProxyTraits> api; + StrictMock<MockChromeProxyDelegate> delegate; + StrictMock<MockFactory> factory; // to be destroyed before other mocks + CFProxy* proxy = new CFProxy(&api); + + + EXPECT_CALL(factory, CreateProxy()).WillOnce(Return(proxy)); + EXPECT_CALL(api, DoCreateChannel(_, proxy)).WillOnce(Return(&api.sender)); + EXPECT_CALL(api, LaunchApp(_)).WillOnce(Return(true)); + EXPECT_CALL(api, CloseChannel(&api.sender)); + + EXPECT_CALL(delegate, tab_handle()).WillRepeatedly(Return(0)); + EXPECT_CALL(delegate, Disconnected()); + + ProxyParams params; + params.profile = "Adam N. Epilinter"; + params.timeout = base::TimeDelta::FromSeconds(4); + factory.GetProxy(&delegate, params); + factory.ReleaseProxy(&delegate, params.profile); +} + +// Not very useful test. Just for illustration. :) +TEST(ChromeProxy, SharedProxy) { + base::WaitableEvent done1(false, false); + base::WaitableEvent done2(false, false); + StrictMock<MockCFProxyTraits> api; + StrictMock<MockChromeProxyDelegate> delegate1; + StrictMock<MockChromeProxyDelegate> delegate2; + StrictMock<MockFactory> factory; + CFProxy* proxy = new CFProxy(&api); + + EXPECT_CALL(factory, CreateProxy()).WillOnce(Return(proxy)); + EXPECT_CALL(api, DoCreateChannel(_, proxy)).WillOnce(Return(&api.sender)); + EXPECT_CALL(api, LaunchApp(_)).WillOnce(DoAll( + API_FIRE_CONNECT(api, base::TimeDelta::FromMilliseconds(150)), + Return(true))); + EXPECT_CALL(api, CloseChannel(&api.sender)); + + EXPECT_CALL(delegate1, tab_handle()).WillRepeatedly(Return(0)); + EXPECT_CALL(delegate2, tab_handle()).WillRepeatedly(Return(0)); + + EXPECT_CALL(delegate1, Connected(proxy)) + .WillOnce(InvokeWithoutArgs(&done1, &base::WaitableEvent::Signal)); + EXPECT_CALL(delegate2, Connected(proxy)) + .WillOnce(InvokeWithoutArgs(&done2, &base::WaitableEvent::Signal)); + + ProxyParams params; + params.profile = "Adam N. Epilinter"; + params.timeout = base::TimeDelta::FromSeconds(4); + + factory.GetProxy(&delegate1, params); + params.timeout = base::TimeDelta::FromSeconds(2); + factory.GetProxy(&delegate2, params); + + EXPECT_TRUE(done1.TimedWait(base::TimeDelta::FromSeconds(1))); + EXPECT_TRUE(done2.TimedWait(base::TimeDelta::FromSeconds(1))); + + EXPECT_CALL(delegate2, Disconnected()); + EXPECT_CALL(delegate1, Disconnected()); + + factory.ReleaseProxy(&delegate2, params.profile); + factory.ReleaseProxy(&delegate1, params.profile); +} + +TEST(ChromeProxy, LaunchTimeout) { + base::WaitableEvent done(true, false); + StrictMock<MockFactory> factory; + StrictMock<MockCFProxyTraits> api; + StrictMock<MockChromeProxyDelegate> delegate; + CFProxy* proxy = new CFProxy(&api); + + EXPECT_CALL(delegate, tab_handle()).WillRepeatedly(Return(0)); + EXPECT_CALL(factory, CreateProxy()).WillOnce(Return(proxy)); + EXPECT_CALL(api, DoCreateChannel(_, proxy)).WillOnce(Return(&api.sender)); + EXPECT_CALL(api, LaunchApp(_)).WillOnce(Return(true)); + EXPECT_CALL(api, CloseChannel(&api.sender)); + + EXPECT_CALL(delegate, PeerLost(_, + ChromeProxyDelegate::CHROME_EXE_LAUNCH_TIMEOUT)) + .WillOnce(InvokeWithoutArgs(&done, &base::WaitableEvent::Signal)); + ProxyParams params; + params.profile = "Adam N. Epilinter"; + params.timeout = base::TimeDelta::FromMilliseconds(300); + factory.GetProxy(&delegate, params); + EXPECT_TRUE(done.TimedWait(base::TimeDelta::FromSeconds(1))); + + EXPECT_CALL(delegate, Disconnected()); + factory.ReleaseProxy(&delegate, params.profile); +} + +TEST(ChromeProxy, LaunchChrome) { + base::WaitableEvent connected(false, false); + StrictMock<MockChromeProxyDelegate> delegate; + ChromeProxyFactory factory; + + ProxyParams params; + params.profile = "Adam N. Epilinter"; + params.timeout = base::TimeDelta::FromSeconds(10); + + EXPECT_CALL(delegate, tab_handle()).WillRepeatedly(Return(0)); + EXPECT_CALL(delegate, Connected(NotNull())) + .WillOnce(InvokeWithoutArgs(&connected, &base::WaitableEvent::Signal)); + + factory.GetProxy(&delegate, params); + EXPECT_TRUE(connected.TimedWait(base::TimeDelta::FromSeconds(15))); + + EXPECT_CALL(delegate, Disconnected()); + factory.ReleaseProxy(&delegate, params.profile); +} + +/////////////////////////////////////////////////////////////////////////////// +namespace { +template <typename M, typename A> +inline IPC::Message* CreateReply(M* m, const A& a) { + IPC::Message* r = IPC::SyncMessage::GenerateReply(m); + if (r) { + M::WriteReplyParams(r, a); + } + return r; +} + +template <typename M, typename A, typename B> +inline IPC::Message* CreateReply(M* m, const A& a, const B& b) { + IPC::Message* r = IPC::SyncMessage::GenerateReply(m); + if (r) { + M::WriteReplyParams(r, a, b); + } + return r; +} + +template <typename M, typename A, typename B, typename C> +inline IPC::Message* CreateReply(M* m, const A& a, const B& b, const C& c) { + IPC::Message* r = IPC::SyncMessage::GenerateReply(m); + if (r) { + M::WriteReplyParams(r, a, b, c); + } + return r; +} +} // namespace + +DISABLE_RUNNABLE_METHOD_REFCOUNT(SyncMsgSender); +TEST(SyncMsgSender, Deserialize) { + // Note the ipc thread is not actually needed, but we try to be close + // to real-world conditions - that SyncMsgSender works from multiple threads. + base::Thread ipc("ipc"); + ipc.StartWithOptions(base::Thread::Options(MessageLoop::TYPE_IO, 0)); + + StrictMock<MockChromeProxyDelegate> d1; + TabsMap tab2delegate; + SyncMsgSender queue(&tab2delegate); + + // Create some sync messages and their replies. + AutomationMsg_InstallExtension m1(0, FilePath(L"c:\\awesome.x"), 0); + AutomationMsg_CreateExternalTab m2(0, IPC::ExternalTabSettings(), 0, 0, 0); + scoped_ptr<IPC::Message> r1(CreateReply(&m1, + AUTOMATION_MSG_EXTENSION_INSTALL_SUCCEEDED)); + scoped_ptr<IPC::Message> r2(CreateReply(&m2, (HWND)1, (HWND)2, 6)); + + queue.QueueSyncMessage(&m1, &d1, NULL); + queue.QueueSyncMessage(&m2, &d1, NULL); + + testing::InSequence s; + EXPECT_CALL(d1, Completed_InstallExtension(true, + AUTOMATION_MSG_EXTENSION_INSTALL_SUCCEEDED, NULL)); + EXPECT_CALL(d1, Completed_CreateTab(true, (HWND)1, (HWND)2, 6)); + + // Execute replies in a worker thread. + ipc.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(&queue, + &SyncMsgSender::OnReplyReceived, r1.get())); + ipc.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(&queue, + &SyncMsgSender::OnReplyReceived, r2.get())); + ipc.Stop(); + + // Expect that tab 6 has been associated with the delegate. + EXPECT_EQ(&d1, tab2delegate[6]); +} + +TEST(SyncMsgSender, OnChannelClosed) { + // TODO(stoyan): implement. +} diff --git a/chrome_frame/chrome_frame.gyp b/chrome_frame/chrome_frame.gyp index 5d3893e..dcffb1b 100644 --- a/chrome_frame/chrome_frame.gyp +++ b/chrome_frame/chrome_frame.gyp @@ -242,6 +242,7 @@ ], 'sources': [ '../base/test_suite.h', + 'cfproxy_test.cc', 'test/automation_client_mock.cc', 'test/automation_client_mock.h', 'test/chrome_frame_test_utils.cc', @@ -795,6 +796,11 @@ 'target_name': 'chrome_frame_common', 'type': 'static_library', 'sources': [ + 'cfproxy.h', + 'cfproxy_private.h', + 'cfproxy_factory.cc', + 'cfproxy_proxy.cc', + 'cfproxy_support.cc', 'chrome_frame_automation.h', 'chrome_frame_automation.cc', 'chrome_frame_delegate.h', @@ -803,10 +809,14 @@ 'chrome_launcher_utils.cc', 'chrome_launcher_utils.h', 'custom_sync_call_context.h', + 'external_tab.h', + 'external_tab.cc', 'plugin_url_request.h', 'plugin_url_request.cc', 'sync_msg_reply_dispatcher.h', 'sync_msg_reply_dispatcher.cc', + 'task_marshaller.h', + 'task_marshaller.cc', ] }, { diff --git a/chrome_frame/external_tab.cc b/chrome_frame/external_tab.cc new file mode 100644 index 0000000..ecf7927 --- /dev/null +++ b/chrome_frame/external_tab.cc @@ -0,0 +1,131 @@ +// 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/external_tab.h" +#include "base/tracked.h" +#include "base/task.h" +#include "base/waitable_event.h" +#include "chrome/test/automation/automation_messages.h" +#include "chrome_frame/utils.h" + +DISABLE_RUNNABLE_METHOD_REFCOUNT(ExternalTabProxy); +DISABLE_RUNNABLE_METHOD_REFCOUNT(UIDelegate); + +ExternalTabProxy::ExternalTabProxy() : state_(NONE), tab_(0), proxy_(NULL), + ui_delegate_(NULL) { +} + +ExternalTabProxy::~ExternalTabProxy() { + Destroy(); +} + +void ExternalTabProxy::Destroy() { + DCHECK(NULL == done_.get()); + done_.reset(new base::WaitableEvent(true, false)); + proxy_factory_->ReleaseProxy(this, tab_params_.profile); + done_->Wait(); + done_.reset(NULL); + + proxy_ = NULL; + tab_ = 0; +} + +void ExternalTabProxy::CreateTab(const CreateTabParams& create_params, + UIDelegate* delegate) { + DCHECK(ui_delegate_ != NULL); + DCHECK_EQ(NONE, state_); + ui_delegate_ = delegate; + tab_params_ = create_params; + state_ = INIT_IN_PROGRESS; + // TODO(stoyan): initialize ProxyParams from CreateTabParams. + ProxyParams p; + proxy_factory_->GetProxy(this, p); +} + +void ExternalTabProxy::Connected(ChromeProxy* proxy) { + // in ipc thread + ui_.PostTask(FROM_HERE, NewRunnableMethod(this, + &ExternalTabProxy::UiConnected, proxy)); +} +void ExternalTabProxy::Disconnected() { + // in ipc thread + DCHECK(done_.get() != NULL); + done_->Signal(); +} + +void ExternalTabProxy::PeerLost(ChromeProxy* proxy, DisconnectReason reason) { + ui_.PostTask(FROM_HERE, NewRunnableMethod(this, &ExternalTabProxy::UiPeerLost, + proxy, reason)); +} + +void ExternalTabProxy::Navigate(const std::string& url, + const std::string& referrer, bool is_privileged) { + // in ui thread + // Catch invalid URLs early. Can we allow this navigation to happen? + GURL parsed_url(url); + if (!CanNavigate(parsed_url, security_manager_, is_privileged)) { + DLOG(ERROR) << __FUNCTION__ << " Not allowing navigation to: " << url; + return; + } + + if (state_ == INIT_IN_PROGRESS) { + // TODO(stoyan): replace CreateTabParams with the new ones + } + + if (state_ == CREATE_TAB_IN_PROGRESS) { + // ah! too late. wait to get tab handle and then navigate + pending_navigation_.Set(parsed_url, GURL(referrer)); + } + + if (state_ == READY) { + proxy_->Tab_Navigate(tab_, parsed_url, GURL(referrer)); + } +} + +void ExternalTabProxy::Completed_CreateTab(bool success, HWND chrome_wnd, + HWND tab_window, int tab_handle) { + // in ipc_thread. +} + +void ExternalTabProxy::Completed_ConnectToTab(bool success, + HWND chrome_window, HWND tab_window, int tab_handle) { + CHECK(0); +} + +void ExternalTabProxy::Completed_Navigate(bool success, + enum AutomationMsg_NavigationResponseValues res) { + // ipc_thread; + CHECK(0); +} + +void ExternalTabProxy::Completed_InstallExtension(bool success, + enum AutomationMsg_ExtensionResponseValues res, SyncMessageContext* ctx) { + CHECK(0); +} + +void ExternalTabProxy::Completed_LoadExpandedExtension(bool success, + enum AutomationMsg_ExtensionResponseValues res, SyncMessageContext* ctx) { + CHECK(0); +} + +void ExternalTabProxy::Completed_GetEnabledExtensions(bool success, + const std::vector<FilePath>* extensions) { + CHECK(0); +} + +void ExternalTabProxy::NavigationStateChanged(int flags, + const IPC::NavigationInfo& nav_info) { + ui_.PostTask(FROM_HERE, NewRunnableMethod(ui_delegate_, + &UIDelegate::OnNavigationStateChanged, flags, nav_info)); +} + +void ExternalTabProxy::UpdateTargetUrl(const std::wstring& url) { + ui_.PostTask(FROM_HERE, NewRunnableMethod(ui_delegate_, + &UIDelegate::OnUpdateTargetUrl, url)); +} + +void ExternalTabProxy::TabLoaded(const GURL& url) { + ui_.PostTask(FROM_HERE, NewRunnableMethod(ui_delegate_, + &UIDelegate::OnLoad, url)); +} diff --git a/chrome_frame/external_tab.h b/chrome_frame/external_tab.h new file mode 100644 index 0000000..eba2965 --- /dev/null +++ b/chrome_frame/external_tab.h @@ -0,0 +1,213 @@ +// 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. + +#ifndef CHROME_FRAME_EXTERNAL_TAB_H_ +#define CHROME_FRAME_EXTERNAL_TAB_H_ +#pragma once + +#include <windows.h> +#include <atlbase.h> +#include <atlwin.h> +#include <vector> +#include <string> +#include "base/scoped_comptr_win.h" +#include "base/scoped_ptr.h" +#include "base/time.h" +#include "chrome/common//page_zoom.h" +#include "chrome/test/automation/automation_constants.h" +#include "chrome_frame/cfproxy.h" +#include "chrome_frame/task_marshaller.h" +#include "googleurl/src/gurl.h" + +class Task; +class CancelableTask; +namespace base { + class TimeDelta; + class WaitableEvent; +} + +namespace IPC { + struct NavigationInfo; + struct ContextMenuParams; +} + +// This is the delegate/callback interface that has to be implemented +// by the customers of ExternalTabProxy class. +class UIDelegate { + public: + virtual void OnNavigationStateChanged(int flags, + const IPC::NavigationInfo& nav_info) = 0; + virtual void OnUpdateTargetUrl(const std::wstring& new_target_url) = 0; + virtual void OnExtensionInstalled(const FilePath& path, void* user_data, + AutomationMsg_ExtensionResponseValues response) = 0; + virtual void OnAcceleratorPressed(const MSG& accel_message) = 0; + virtual void OnLoad(const GURL& url) = 0; + virtual void OnMessageFromChromeFrame(const std::string& message, + const std::string& origin, const std::string& target) = 0; + virtual void OnHandleContextMenu(HANDLE menu_handle, int align_flags, + const IPC::ContextMenuParams& params) = 0; + protected: + ~UIDelegate() {} +}; + +struct CreateTabParams { + std::string profile; + std::string extra_arguments; + bool is_incognito; + bool is_widget_mode; + GURL url; + GURL referrer; + base::TimeDelta launch_timeout; +}; + +///////////////////////////////////////////////////////////////////////// +// ExternalTabProxy is a mediator between ChromeProxy (which runs mostly in +// background IPC-channel thread and the UI object (ActiveX, NPAPI, +// ActiveDocument). +// The lifetime of ExternalTabProxy is determined by the UI object. +// +// When ExternalTabProxy dies: +// 1. Remove itself as a ChromeProxyDelegate. This blocks until _Disconnected() +// is received. +// 2. Kills all posted tasks to the UI thread. +// 3. Stop all network requests +// => It does not have to (and should not) be a refcount-ed object. + +// Non-public inheritance is not allowed by the style-guide. +class ExternalTabProxy : public CWindowImpl<ExternalTabProxy>, + public ChromeProxyDelegate { + public: + ExternalTabProxy(); + ~ExternalTabProxy(); + +#ifdef UNIT_TEST + void set_proxy_factory(ChromeProxyFactory* factory) { + proxy_factory_ = factory; + } +#endif + // + virtual void CreateTab(const CreateTabParams& create_params, + UIDelegate* delegate); + virtual void Navigate(const std::string& url, const std::string& referrer, + bool is_privileged); + virtual bool NavigateToIndex(int index); + virtual void ForwardMessageFromExternalHost(const std::string& message, + const std::string& origin, const std::string& target) { + proxy_->Tab_PostMessage(tab_, message, origin, target); + } + virtual void OnChromeFrameHostMoved(); + + virtual void SetEnableExtensionAutomation( + const std::vector<std::string>& functions_enabled); + virtual void InstallExtension(const FilePath& crx_path, void* user_data); + virtual void LoadExpandedExtension(const FilePath& path, void* user_data); + virtual void GetEnabledExtensions(void* user_data); + + // Attaches an existing external tab to this automation client instance. + virtual void AttachExternalTab(uint64 external_tab_cookie); + virtual void BlockExternalTab(uint64 cookie); + + void SetZoomLevel(PageZoom::Function zoom_level) { + proxy_->Tab_Zoom(tab_, zoom_level); + } + + private: + ////////////////////////////////////////////////////////////////////////// + // ChromeProxyDelegate implementation + virtual int tab_handle() { + return tab_; + } + virtual void Connected(ChromeProxy* proxy); + virtual void PeerLost(ChromeProxy* proxy, DisconnectReason reason); + virtual void Disconnected(); + + + // Sync message responses. + virtual void Completed_CreateTab(bool success, HWND chrome_wnd, + HWND tab_window, int tab_handle) = 0; // TODO(stoyan): Error_code + virtual void Completed_ConnectToTab(bool success, HWND chrome_window, + HWND tab_window, int tab_handle) = 0; + virtual void Completed_Navigate(bool success, + enum AutomationMsg_NavigationResponseValues res); + virtual void Completed_InstallExtension(bool success, + enum AutomationMsg_ExtensionResponseValues res, SyncMessageContext* ctx); + virtual void Completed_LoadExpandedExtension(bool success, + enum AutomationMsg_ExtensionResponseValues res, SyncMessageContext* ctx); + virtual void Completed_GetEnabledExtensions(bool success, + const std::vector<FilePath>* extensions); + + // Network requests from Chrome. + virtual void Network_Start(int request_id, + const IPC::AutomationURLRequest& request_info); + virtual void Network_Read(int request_id, int bytes_to_read); + virtual void Network_End(int request_id, const URLRequestStatus& s); + virtual void Network_DownloadInHost(int request_id); + virtual void GetCookies(const GURL& url, int cookie_id); + virtual void SetCookie(const GURL& url, const std::string& cookie); + + // Navigation progress notifications. + virtual void NavigationStateChanged(int flags, + const IPC::NavigationInfo& nav_info); + virtual void UpdateTargetUrl(const std::wstring& url); + virtual void NavigationFailed(int error_code, const GURL& gurl); + virtual void DidNavigate(const IPC::NavigationInfo& navigation_info); + virtual void TabLoaded(const GURL& url); + + virtual void OpenURL(const GURL& url_to_open, const GURL& referrer, + int open_disposition); + virtual void GoToHistoryOffset(int offset); + virtual void MessageToHost(const std::string& message, + const std::string& origin, const std::string& target); + + // Misc. UI. + virtual void HandleAccelerator(const MSG& accel_message); + virtual void TabbedOut(bool reverse); + + // Other + virtual void TabClosed(); + virtual void AttachTab(const IPC::AttachExternalTabParams& attach_params); + + // end of ChromeProxyDelegate methods + ////////////////////////////////////////////////////////////////////////// + + void Destroy(); + + // The UiXXXX are the ChromeProxyDelegate methods but on UI thread. + void UiConnected(ChromeProxy* proxy); + void UiPeerLost(ChromeProxy* proxy, DisconnectReason reason); + + // With the present state of affairs the only response we can possibly handle + // in the background IPC thread is Completed_CreateTab() where we can + // initiate a navigation (if there is a pending one). + // To simplify - will handle Completed_CreateTab in UI thread and avoid + // the need of lock when accessing members. + enum { + NONE, + INIT_IN_PROGRESS, + CREATE_TAB_IN_PROGRESS, + READY, + RELEASE_CF_PROXY_IN_PROGRESS + } state_; + int tab_; + ChromeProxyFactory* proxy_factory_; + ChromeProxy* proxy_; + UIDelegate* ui_delegate_; + TaskMarshallerThroughMessageQueue ui_; + + scoped_ptr<base::WaitableEvent> done_; + + CreateTabParams tab_params_; + struct PendingNavigation { + GURL url; + GURL referrer; + void Set(const GURL& gurl, const GURL& ref) { + url = gurl; + referrer = ref; + } + } pending_navigation_; + + ScopedComPtr<IInternetSecurityManager> security_manager_; +}; + +#endif // CHROME_FRAME_EXTERNAL_TAB_H_ diff --git a/chrome_frame/task_marshaller.cc b/chrome_frame/task_marshaller.cc new file mode 100644 index 0000000..4e89d06e --- /dev/null +++ b/chrome_frame/task_marshaller.cc @@ -0,0 +1,133 @@ +// 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/task_marshaller.h" +#include "base/task.h" + +TaskMarshallerThroughMessageQueue::TaskMarshallerThroughMessageQueue() { + wnd_ = NULL; + msg_ = 0xFFFF; +} + +TaskMarshallerThroughMessageQueue::~TaskMarshallerThroughMessageQueue() { + DeleteAll(); +} + +void TaskMarshallerThroughMessageQueue::PostTask( + const tracked_objects::Location& from_here, Task* task) { + task->SetBirthPlace(from_here); + lock_.Acquire(); + bool has_work = !pending_tasks_.empty(); + pending_tasks_.push(task); + lock_.Release(); + + // Don't post message if there is already one. + if (has_work) + return; + + if (!::PostMessage(wnd_, msg_, 0, 0)) { + DLOG(INFO) << "Dropping MSG_EXECUTE_TASK message for destroyed window."; + DeleteAll(); + } +} + +void TaskMarshallerThroughMessageQueue::PostDelayedTask( + const tracked_objects::Location& source, + Task* task, + base::TimeDelta& delay) { + AutoLock lock(lock_); + DelayedTask delayed_task(task, base::Time::Now() + delay); + delayed_tasks_.push(delayed_task); + // If we become the 'top' task - reschedule the timer. + if (delayed_tasks_.top().task == task) { + ::SetTimer(wnd_, reinterpret_cast<UINT_PTR>(this), + static_cast<DWORD>(delay.InMilliseconds()), NULL); + } +} + +Task* TaskMarshallerThroughMessageQueue::PopTask() { + AutoLock lock(lock_); + Task* task = NULL; + if (!pending_tasks_.empty()) { + task = pending_tasks_.front(); + pending_tasks_.pop(); + } + return task; +} + +void TaskMarshallerThroughMessageQueue::ExecuteQueuedTasks() { + DCHECK(CalledOnValidThread()); + Task* task; + while ((task = PopTask()) != NULL) { + RunTask(task); + } +} + +void TaskMarshallerThroughMessageQueue::ExecuteDelayedTasks() { + DCHECK(CalledOnValidThread()); + ::KillTimer(wnd_, reinterpret_cast<UINT_PTR>(this)); + while (1) { + lock_.Acquire(); + + if (delayed_tasks_.empty()) { + lock_.Release(); + return; + } + + base::Time now = base::Time::Now(); + DelayedTask next_task = delayed_tasks_.top(); + base::Time next_run = next_task.run_at; + if (next_run > now) { + int64 delay = (next_run - now).InMillisecondsRoundedUp(); + ::SetTimer(wnd_, reinterpret_cast<UINT_PTR>(this), + static_cast<DWORD>(delay), NULL); + lock_.Release(); + return; + } + + delayed_tasks_.pop(); + lock_.Release(); + + // Run the task outside the lock. + RunTask(next_task.task); + } +} + +void TaskMarshallerThroughMessageQueue::DeleteAll() { + AutoLock lock(lock_); + DLOG_IF(INFO, !pending_tasks_.empty()) << + "Destroying " << pending_tasks_.size() << " pending tasks."; + while (!pending_tasks_.empty()) { + Task* task = pending_tasks_.front(); + pending_tasks_.pop(); + delete task; + } + + while (!delayed_tasks_.empty()) { + delete delayed_tasks_.top().task; + delayed_tasks_.pop(); + } +} + +void TaskMarshallerThroughMessageQueue::RunTask(Task* task) { + ++invoke_task_; + task->Run(); + --invoke_task_; + delete task; +} + +bool TaskMarshallerThroughMessageQueue::DelayedTask::operator<( + const DelayedTask& other) const { + // Since the top of a priority queue is defined as the "greatest" element, we + // need to invert the comparison here. We want the smaller time to be at the + // top of the heap. + if (run_at < other.run_at) + return false; + + if (run_at > other.run_at) + return true; + + // If the times happen to match, then we use the sequence number to decide. + // Compare the difference to support integer roll-over. + return (seq - other.seq) > 0; +} diff --git a/chrome_frame/task_marshaller.h b/chrome_frame/task_marshaller.h new file mode 100644 index 0000000..b3f87b8 --- /dev/null +++ b/chrome_frame/task_marshaller.h @@ -0,0 +1,63 @@ +// 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. + +#ifndef CHROME_FRAME_TASK_MARSHALLER_H_ +#define CHROME_FRAME_TASK_MARSHALLER_H_ +#pragma once + +#include <windows.h> +#include <deque> +#include <queue> +#include "base/lock.h" +#include "base/non_thread_safe.h" +#include "base/time.h" +class Task; +namespace tracked_objects { + class Location; +} + +// TaskMarshallerThroughMessageQueue is similar to base::MessageLoopForUI +// in cases where we do not control the thread lifetime and message retrieval +// and dispatching. It uses a HWND to ::PostMessage to it as a signal that +// the task queue is not empty. +class TaskMarshallerThroughMessageQueue : public NonThreadSafe { + public: + TaskMarshallerThroughMessageQueue(); + ~TaskMarshallerThroughMessageQueue(); + + void SetWindow(HWND wnd, UINT msg) { + wnd_ = wnd; + msg_ = msg; + } + + virtual void PostTask(const tracked_objects::Location& from_here, + Task* task); + virtual void PostDelayedTask(const tracked_objects::Location& source, + Task* task, + base::TimeDelta& delay); + private: + void DeleteAll(); + inline Task* PopTask(); + inline void ExecuteQueuedTasks(); + void ExecuteDelayedTasks(); + void RunTask(Task* task); + + struct DelayedTask { + DelayedTask(Task* task, base::Time at) : run_at(at), task(task), seq(0) {} + base::Time run_at; + Task* task; + int seq; + // To support sorting based on time in priority_queue. + bool operator<(const DelayedTask& other) const; + }; + + std::priority_queue<DelayedTask> delayed_tasks_; + std::queue<Task*> pending_tasks_; + Lock lock_; + HWND wnd_; + UINT msg_; + int invoke_task_; +}; + +#endif // CHROME_FRAME_TASK_MARSHALLER_H_ |