summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome_frame/cfproxy.h227
-rw-r--r--chrome_frame/cfproxy_factory.cc79
-rw-r--r--chrome_frame/cfproxy_private.h192
-rw-r--r--chrome_frame/cfproxy_proxy.cc246
-rw-r--r--chrome_frame/cfproxy_support.cc450
-rw-r--r--chrome_frame/cfproxy_test.cc316
-rw-r--r--chrome_frame/chrome_frame.gyp10
-rw-r--r--chrome_frame/external_tab.cc131
-rw-r--r--chrome_frame/external_tab.h213
-rw-r--r--chrome_frame/task_marshaller.cc133
-rw-r--r--chrome_frame/task_marshaller.h63
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, &params))
+ delegate->NavigationStateChanged(params.b, params.c);
+ return true;
+ }
+
+ case AutomationMsg_UpdateTargetUrl__ID: {
+ // Tuple2<int, std::wstring>
+ AutomationMsg_UpdateTargetUrl::Param params;
+ if (ReadParam(&m, &iter, &params))
+ 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_