diff options
Diffstat (limited to 'ceee')
-rw-r--r-- | ceee/ie/broker/broker.gyp | 1 | ||||
-rw-r--r-- | ceee/ie/broker/broker_module.cc | 9 | ||||
-rw-r--r-- | ceee/ie/ie.gyp | 21 | ||||
-rw-r--r-- | ceee/ie/plugin/bho/browser_helper_object.cc | 29 | ||||
-rw-r--r-- | ceee/ie/plugin/bho/browser_helper_object.h | 11 | ||||
-rw-r--r-- | ceee/ie/plugin/bho/browser_helper_object.rgs | 1 | ||||
-rw-r--r-- | ceee/ie/plugin/bho/browser_helper_object_unittest.cc | 7 | ||||
-rw-r--r-- | ceee/ie/plugin/bho/executor.cc | 233 | ||||
-rw-r--r-- | ceee/ie/plugin/bho/executor.h | 107 | ||||
-rw-r--r-- | ceee/ie/plugin/bho/executor_com_unittest.cc | 762 | ||||
-rw-r--r-- | ceee/ie/plugin/bho/testing_invoke_executor.cc | 96 | ||||
-rw-r--r-- | ceee/ie/plugin/toolband/resource.h | 2 | ||||
-rw-r--r-- | ceee/ie/plugin/toolband/toolband.gyp | 30 | ||||
-rw-r--r-- | ceee/ie/plugin/toolband/toolband.idl | 19 | ||||
-rwxr-xr-x | ceee/ie/plugin/toolband/toolband.rc | 1 | ||||
-rw-r--r-- | ceee/ie/plugin/toolband/toolband_module.cc | 36 | ||||
-rw-r--r-- | ceee/ie/plugin/toolband/toolband_proxy.cc | 150 | ||||
-rw-r--r-- | ceee/ie/plugin/toolband/toolband_proxy.h | 29 | ||||
-rw-r--r-- | ceee/ie/plugin/toolband/toolband_proxy.rgs | 11 |
19 files changed, 1529 insertions, 26 deletions
diff --git a/ceee/ie/broker/broker.gyp b/ceee/ie/broker/broker.gyp index e669013..f1f3220 100644 --- a/ceee/ie/broker/broker.gyp +++ b/ceee/ie/broker/broker.gyp @@ -133,6 +133,7 @@ '../common/common.gyp:ie_common_settings', '../common/common.gyp:ie_guids', '../plugin/toolband/toolband.gyp:toolband_idl', + '../plugin/toolband/toolband.gyp:toolband_proxy_lib', '../../../base/base.gyp:base', '../../../breakpad/breakpad.gyp:breakpad_handler', '../../../ceee/common/common.gyp:ceee_common', diff --git a/ceee/ie/broker/broker_module.cc b/ceee/ie/broker/broker_module.cc index 0d7a0eb..252e6a4 100644 --- a/ceee/ie/broker/broker_module.cc +++ b/ceee/ie/broker/broker_module.cc @@ -18,6 +18,7 @@ #include "ceee/ie/broker/executors_manager.h" #include "ceee/ie/broker/resource.h" #include "ceee/ie/broker/window_events_funnel.h" +#include "ceee/ie/plugin/toolband/toolband_proxy.h" #include "ceee/ie/common/crash_reporter.h" #include "ceee/common/com_utils.h" #include "chrome/common/url_constants.h" @@ -153,6 +154,14 @@ HRESULT CeeeBrokerModule::InitializeCom() { DLOG_IF(WARNING, FAILED(hr)) << "IGlobalOptions::Set failed " << com::LogHr(hr); + // Register the executor proxy/stubs. + // Note that this registers the proxy/stub class objects for all threads + // in the multithreaded apartment. + if (!RegisterProxyStubs(NULL)) { + LOG(ERROR) << "Failed to register executor proxy/stubs"; + return E_UNEXPECTED; + } + // The above is best-effort, don't bail on error. return S_OK; } diff --git a/ceee/ie/ie.gyp b/ceee/ie/ie.gyp index 91e7ebf..3a04651 100644 --- a/ceee/ie/ie.gyp +++ b/ceee/ie/ie.gyp @@ -18,6 +18,23 @@ ] }, { + 'target_name': 'testing_invoke_executor', + 'type': 'executable', + 'sources': [ + 'plugin/bho/testing_invoke_executor.cc', + ], + 'dependencies': [ + '../../base/base.gyp:base', + '../common/common.gyp:ceee_common', + 'common/common.gyp:ie_guids', + 'plugin/toolband/toolband.gyp:toolband_idl', + 'plugin/toolband/toolband.gyp:toolband_proxy_lib', + ], + 'libraries': [ + 'rpcrt4.lib', + ], + }, + { 'target_name': 'ie_unittests', 'type': 'executable', 'sources': [ @@ -41,6 +58,7 @@ 'plugin/bho/dom_utils_unittest.cc', 'plugin/bho/events_funnel_unittest.cc', 'plugin/bho/executor_unittest.cc', + 'plugin/bho/executor_com_unittest.cc', 'plugin/bho/extension_port_manager.cc', 'plugin/bho/frame_event_handler_unittest.cc', 'plugin/bho/infobar_events_funnel_unittest.cc', @@ -78,6 +96,7 @@ }, }, 'dependencies': [ + 'testing_invoke_executor', 'common/common.gyp:ie_common', 'common/common.gyp:ie_common_settings', 'common/common.gyp:ie_guids', @@ -89,6 +108,7 @@ 'plugin/toolband/toolband.gyp:ceee_ie_lib', 'plugin/toolband/toolband.gyp:ie_toolband_common', 'plugin/toolband/toolband.gyp:toolband_idl', + 'plugin/toolband/toolband.gyp:toolband_proxy_lib', '../../base/base.gyp:base', '../../breakpad/breakpad.gyp:breakpad_handler', '../testing/sidestep/sidestep.gyp:sidestep', @@ -131,6 +151,7 @@ 'plugin/bho/bho.gyp:bho', 'plugin/scripting/scripting.gyp:scripting', 'plugin/toolband/toolband.gyp:toolband_idl', + 'plugin/toolband/toolband.gyp:toolband_proxy_lib', '../../base/base.gyp:base', '../../testing/gmock.gyp:gmock', '../../testing/gtest.gyp:gtest', diff --git a/ceee/ie/plugin/bho/browser_helper_object.cc b/ceee/ie/plugin/bho/browser_helper_object.cc index e237442..d0f5cdc 100644 --- a/ceee/ie/plugin/bho/browser_helper_object.cc +++ b/ceee/ie/plugin/bho/browser_helper_object.cc @@ -27,6 +27,7 @@ #include "ceee/ie/plugin/bho/cookie_accountant.h" #include "ceee/ie/plugin/bho/http_negotiate.h" #include "ceee/ie/plugin/scripting/script_host.h" +#include "ceee/ie/plugin/toolband/toolband_proxy.h" #include "chrome/browser/automation/extension_automation_constants.h" #include "chrome/browser/extensions/extension_tabs_module_constants.h" #include "chrome/common/automation_constants.h" @@ -151,14 +152,25 @@ STDMETHODIMP BrowserHelperObject::SetSite(IUnknown* site) { // We're being initialized. hr = Initialize(site); - // Release the site in case of failure. - if (FAILED(hr)) + // Release the site, and tear down our own state in case of failure. + if (FAILED(hr)) { + TearDown(); SuperSite::SetSite(NULL); + } } return hr; } +HRESULT BrowserHelperObject::RegisterProxies() { + return RegisterProxyStubs(&proxy_stub_cookies_) ? S_OK : E_UNEXPECTED; +} + +void BrowserHelperObject::UnregisterProxies() { + UnregisterProxyStubs(proxy_stub_cookies_); + proxy_stub_cookies_.clear(); +} + HRESULT BrowserHelperObject::GetParentBrowser(IWebBrowser2* browser, IWebBrowser2** parent_browser) { DCHECK(browser != NULL); @@ -245,13 +257,18 @@ HRESULT BrowserHelperObject::Initialize(IUnknown* site) { DCHECK(SUCCEEDED(hr)) << "InitializeChromeFrameHost failed " << com::LogHr(hr); if (FAILED(hr)) { - TearDown(); return hr; } // Initialize the extension port manager. extension_port_manager_.Initialize(chrome_frame_host_); + // Register the proxy/stubs for the executor. + // Note the proxy registration function will have logged what occurred. + hr = RegisterProxies(); + if (FAILED(hr)) + return hr; + // Preemptively feed the broker with an executor in our thread. hr = GetBrokerRegistrar(&broker_registrar_); LOG_IF(ERROR, FAILED(hr)) << "Failed to create broker, hr=" << @@ -280,21 +297,18 @@ HRESULT BrowserHelperObject::Initialize(IUnknown* site) { CComQIPtr<IServiceProvider> service_provider(site); DCHECK(service_provider); if (service_provider == NULL) { - TearDown(); return E_FAIL; } hr = ConnectSinks(service_provider); DCHECK(SUCCEEDED(hr)) << "ConnectSinks failed " << com::LogHr(hr); if (FAILED(hr)) { - TearDown(); return hr; } hr = GetTabWindow(service_provider); DCHECK(SUCCEEDED(hr)) << "GetTabWindow failed " << com::LogHr(hr); if (FAILED(hr)) { - TearDown(); return hr; } @@ -363,6 +377,9 @@ HRESULT BrowserHelperObject::TearDown() { } chrome_frame_host_.Release(); + // Unregister the proxy/stubs for the executor. + UnregisterProxies(); + return S_OK; } diff --git a/ceee/ie/plugin/bho/browser_helper_object.h b/ceee/ie/plugin/bho/browser_helper_object.h index 039b305..8f79453 100644 --- a/ceee/ie/plugin/bho/browser_helper_object.h +++ b/ceee/ie/plugin/bho/browser_helper_object.h @@ -163,6 +163,11 @@ class ATL_NO_VTABLE BrowserHelperObject // @} protected: + // Register proxy/stubs for executor interfaces. + virtual HRESULT RegisterProxies(); + // Unregister proxy/stubs for executor interfaces. + virtual void UnregisterProxies(); + typedef base::win::ScopedComPtr<IContentScriptNativeApi, &GUID_NULL> ScopedContentScriptNativeApiPtr; typedef base::win::ScopedComPtr<IDispatch> ScopedDispatchPtr; @@ -220,6 +225,8 @@ class ATL_NO_VTABLE BrowserHelperObject // Initializes the BHO to the given site. // Called from SetSite. + // @note On failure the state of the BHO may be inconsistent, + // so a TearDown may be needed. HRESULT Initialize(IUnknown* site); // Tears down an initialized bho. @@ -371,6 +378,10 @@ class ATL_NO_VTABLE BrowserHelperObject BrokerRpcClient broker_rpc_; private: + // The BHO registers proxy/stubs for the CEEE executor on initialization. + // These are the cookies to allow us to unregister then on teardown. + std::vector<DWORD> proxy_stub_cookies_; + // Used during initialization to get the tab information from Chrome and // register ourselves with the broker. HRESULT RegisterTabInfo(); diff --git a/ceee/ie/plugin/bho/browser_helper_object.rgs b/ceee/ie/plugin/bho/browser_helper_object.rgs index f7331a6..fd794ab 100644 --- a/ceee/ie/plugin/bho/browser_helper_object.rgs +++ b/ceee/ie/plugin/bho/browser_helper_object.rgs @@ -4,7 +4,6 @@ NoRemove CLSID { InprocServer32 = s '%MODULE%' { val ThreadingModel = s 'Apartment' } - 'TypeLib' = s '{7C09079D-F9CB-4E9E-9293-D224B071D8BA}' } } } diff --git a/ceee/ie/plugin/bho/browser_helper_object_unittest.cc b/ceee/ie/plugin/bho/browser_helper_object_unittest.cc index 8bbac5c..be970e8 100644 --- a/ceee/ie/plugin/bho/browser_helper_object_unittest.cc +++ b/ceee/ie/plugin/bho/browser_helper_object_unittest.cc @@ -133,6 +133,13 @@ class TestingBrowserHelperObject MOCK_METHOD2(GetParentBrowser, HRESULT(IWebBrowser2*, IWebBrowser2**)); + // Neuter the proxy registration/unregistration. + HRESULT RegisterProxies() { + return S_OK; + } + void UnregisterProxies() { + } + // Pulicize using BrowserHelperObject::HandleNavigateComplete; diff --git a/ceee/ie/plugin/bho/executor.cc b/ceee/ie/plugin/bho/executor.cc index d32410f..a6e6f7b 100644 --- a/ceee/ie/plugin/bho/executor.cc +++ b/ceee/ie/plugin/bho/executor.cc @@ -36,7 +36,6 @@ #include "base/json/json_writer.h" #include "base/logging.h" #include "base/values.h" -#include "base/scoped_ptr.h" #include "base/stringprintf.h" #include "base/utf_string_conversions.h" #include "ceee/common/com_utils.h" @@ -46,6 +45,7 @@ #include "ceee/ie/plugin/bho/frame_event_handler.h" #include "ceee/ie/plugin/bho/infobar_manager.h" #include "ceee/ie/plugin/bho/tab_window_manager.h" +#include "ceee/ie/plugin/toolband/toolband_proxy.h" #include "chrome_frame/utils.h" #include "broker_lib.h" // NOLINT @@ -146,6 +146,14 @@ LRESULT CeeeExecutorCreator::GetMsgProc(int code, WPARAM wparam, return 0; } + // Register the proxy/stubs for the executor. + // We don't make arrangements to unregister them here as we don't + // expect we'll ever unload from the process. + // TODO(siggi@chromium.org): Is it worth arranging for unregistration + // in this case? + if (!RegisterProxyStubs(NULL)) + LOG(ERROR) << "Executor Creator failed to register proxy/stubs"; + CComPtr<ICeeeWindowExecutor> executor; HRESULT hr = executor.CoCreateInstance(CLSID_CeeeExecutor); LOG_IF(ERROR, FAILED(hr)) << "Failed to create Executor, hr=" << @@ -179,6 +187,229 @@ LRESULT CeeeExecutorCreator::GetMsgProc(int code, WPARAM wparam, return ::CallNextHookEx(NULL, code, wparam, lparam); } +AsyncTabCall::AsyncTabCall() : task_hr_(E_PENDING), task_(NULL) { + VLOG(1) << "AsyncTabCall " << this << " created"; +} + +AsyncTabCall::~AsyncTabCall() { + VLOG(1) << "AsyncTabCall " << this << " deleted"; +} + +HRESULT AsyncTabCall::CreateInitialized(ICeeeTabExecutor* executor, + IUnknown* outer, + IUnknown** async_call) { + CComPolyObject<AsyncTabCall>* async_tab_call = NULL; + HRESULT hr = CComPolyObject<AsyncTabCall>::CreateInstance( + outer, &async_tab_call); + + if (FAILED(hr)) { + LOG(ERROR) << "Failed to create async tab call " << com::LogHr(hr); + return hr; + } + DCHECK(async_tab_call != NULL); + + hr = async_tab_call->m_contained.Initialize(executor); + if (FAILED(hr)) { + delete async_tab_call; + LOG(ERROR) << "Async tab call initialization failed " << com::LogHr(hr); + } + + return async_tab_call->QueryInterface(async_call); +} + +HRESULT AsyncTabCall::Initialize(ICeeeTabExecutor* executor) { + DCHECK(executor != NULL); + executor_ = executor; + return S_OK; +} + +STDMETHODIMP AsyncTabCall::Begin_Initialize(CeeeWindowHandle hwnd) { + DCHECK(task_hr_ == E_PENDING && task_ == NULL); + task_hr_ = executor_->Initialize(hwnd); + return Signal(); +} + +STDMETHODIMP AsyncTabCall::Finish_Initialize() { + return task_hr_; +} + +// A noop function. +static void Noop() { +} + +STDMETHODIMP AsyncTabCall::Begin_GetTabInfo() { + DCHECK(task_hr_ == E_PENDING && task_ == NULL); + + // We do all the work on Finish_GetTabInfo, so schedule only a noop + // invocation. Alternatively we could schedule NULL here, though that + // would make it more difficult to distinguish error cases. + return ScheduleTask(NewRunnableFunction(Noop)); +} + +STDMETHODIMP AsyncTabCall::Finish_GetTabInfo(CeeeTabInfo *tab_info) { + return executor_->GetTabInfo(tab_info); +} + +STDMETHODIMP AsyncTabCall::Begin_Navigate(BSTR url, long flags, BSTR target) { + DCHECK(task_hr_ == E_PENDING && task_ == NULL); + + if (!ScheduleTask(NewRunnableMethod(this, + &AsyncTabCall::NavigateImpl, + CComBSTR(url), + flags, + CComBSTR(target)))) { + LOG(ERROR) << "Failed to schedule navigation task"; + return E_OUTOFMEMORY; + } + + return S_OK; +} + +STDMETHODIMP AsyncTabCall::Finish_Navigate() { + return task_hr_; +} + +STDMETHODIMP AsyncTabCall::Begin_InsertCode(BSTR code, + BSTR file, + BOOL all_frames, + CeeeTabCodeType type) { + DCHECK(task_hr_ == E_PENDING && task_ == NULL); + + if (!ScheduleTask(NewRunnableMethod(this, + &AsyncTabCall::InsertCodeImpl, + CComBSTR(code), + CComBSTR(file), + all_frames, + type))) { + LOG(ERROR) << "Failed to schedule insert code task"; + return E_OUTOFMEMORY; + } + + return S_OK; +} + +STDMETHODIMP AsyncTabCall::Finish_InsertCode() { + return task_hr_; +} + +void AsyncTabCall::NavigateImpl(const CComBSTR& url, + long flags, + const CComBSTR& target) { + task_hr_ = executor_->Navigate(url, flags, target); +} + +void AsyncTabCall::InsertCodeImpl(const CComBSTR& code, + const CComBSTR& file, + BOOL all_frames, + CeeeTabCodeType type) { + task_hr_ = executor_->InsertCode(code, file, all_frames, type); +} + +int AsyncTabCall::OnCreate(LPCREATESTRUCT lpCreateStruct) { + // Our window maintains a self-reference to our object. + GetUnknown()->AddRef(); + return 1; +} + +LRESULT AsyncTabCall::OnExecuteTask( + UINT msg, WPARAM wparam, LPARAM lparam, BOOL& handled) { + VLOG(1) << "OnExecuteTask for " << this; + + if (task_.get() != NULL) { + task_->Run(); + task_.reset(); + } + + // Signal the call done, this'll cause the Finish_ call to execute. + Signal(); + + DestroyWindow(); + return 1; +} + +void AsyncTabCall::OnFinalMessage(HWND window) { + // Release our window's self-reference. + GetUnknown()->Release(); +} + +bool AsyncTabCall::ScheduleTask(Task* task) { + DCHECK(task != NULL); + DCHECK(m_hWnd == NULL); + + if (Create(HWND_MESSAGE) == NULL) { + LOG(ERROR) << "Failed to create window"; + + delete task; + return false; + } + DCHECK(m_hWnd != NULL); + + // Schedule the task for later by posting a message. + task_.reset(task); + PostMessage(kExecuteTaskMessage); + + return true; +} + +HRESULT AsyncTabCall::Signal() { + // We need to explicitly query for ISynchronize, because we're + // most likely aggregated, and the implementation is on our + // controlling outer. + CComPtr<ISynchronize> sync; + HRESULT hr = GetUnknown()->QueryInterface(&sync); + if (sync == NULL) { + LOG(ERROR) << "Failed to retrieve ISynchronize " << com::LogHr(hr); + return hr; + } + DCHECK(sync != NULL); + + hr = sync->Signal(); + if (FAILED(hr)) + LOG(ERROR) << "Failed to signal " << com::LogHr(hr); + + return hr; +} + +CeeeExecutor::CeeeExecutor() : hwnd_(NULL) { +} + +CeeeExecutor::~CeeeExecutor() { +} + +HRESULT CeeeExecutor::CreateTabCall(ICeeeTabExecutor* executor, + IUnknown *outer, + REFIID riid2, + IUnknown **out) { + CComPtr<IUnknown> tab_call; + HRESULT hr = AsyncTabCall::CreateInitialized(executor, outer, &tab_call); + if (SUCCEEDED(hr)) + hr = tab_call->QueryInterface(riid2, reinterpret_cast<void**>(out)); + + if (FAILED(hr)) { + delete tab_call; + LOG(ERROR) << "Async executor initialization failed " << com::LogHr(hr); + } + + return hr; +} + +STDMETHODIMP CeeeExecutor::CreateCall(REFIID async_iid, + IUnknown *outer, + REFIID requested_iid, + IUnknown **out) { + DCHECK(outer != NULL); + DCHECK(out != NULL); + // Null for safety. + *out = NULL; + + if (async_iid == IID_AsyncICeeeTabExecutor) { + return CreateTabCall(this, outer, requested_iid, out); + } else { + LOG(ERROR) << "Unexpected IID to CreateCall"; + return E_NOINTERFACE; + } +} + HRESULT CeeeExecutor::Initialize(CeeeWindowHandle hwnd) { DCHECK(hwnd); hwnd_ = reinterpret_cast<HWND>(hwnd); diff --git a/ceee/ie/plugin/bho/executor.h b/ceee/ie/plugin/bho/executor.h index 379ba32..eeea590 100644 --- a/ceee/ie/plugin/bho/executor.h +++ b/ceee/ie/plugin/bho/executor.h @@ -14,6 +14,7 @@ #include <string.h> #include "base/scoped_ptr.h" +#include "base/task.h" #include "ceee/ie/plugin/bho/infobar_manager.h" #include "ceee/ie/plugin/toolband/resource.h" @@ -69,12 +70,98 @@ class ATL_NO_VTABLE CeeeExecutorCreator long current_thread_id_; }; +// Implements an asynchronous call object for ICeeeTabExecutor. +// This is instantiated through the executor's ICallFactory::CreateCall method. +// For asynchronous methods, it posts a window message and processes the +// method completion when the message is dispatched. This is done to avoid +// performing significant operations inside IE, reentrantly during an +// outgoing out-of-apartment call. During those times, IE and the various +// third-party addons performing such outcalls tend to be in a fragile state. +// @note the COM marshaling machinery will instantiate a new one of these for +// each call it handles, so these are not implemented to cope with reuse. +class AsyncTabCall + : public CComObjectRootEx<CComSingleThreadModel>, + public CComCoClass<AsyncTabCall>, + public CWindowImpl<AsyncTabCall>, + public AsyncICeeeTabExecutor { + public: + DECLARE_GET_CONTROLLING_UNKNOWN() + + BEGIN_COM_MAP(AsyncTabCall) + COM_INTERFACE_ENTRY(AsyncICeeeTabExecutor) + COM_INTERFACE_ENTRY_AUTOAGGREGATE(IID_ISynchronize, + synchronize_.p, + CLSID_ManualResetEvent) + END_COM_MAP() + + // The window message we post to ourselves. + static const uint32 kExecuteTaskMessage = WM_USER; + + BEGIN_MSG_MAP(AsyncTabCall) + MSG_WM_CREATE(OnCreate) + MESSAGE_HANDLER(kExecuteTaskMessage, OnExecuteTask) + END_MSG_MAP() + + AsyncTabCall(); + ~AsyncTabCall(); + + // Creates an initialized instance. Aggregated if outer is non-NULL. + // @param executor the executor instance we invoke on. + // @param outer the controlling outer or NULL. + // @param async_call on success, returns the new AsyncTabCall. + static HRESULT CreateInitialized(ICeeeTabExecutor* executor, + IUnknown* outer, + IUnknown** async_call); + + static bool ImplementsThreadSafeReferenceCounting() { + return false; + } + + // @name AsyncICeeeTabExecutor implementation. + // @{ + STDMETHOD(Begin_Initialize)(CeeeWindowHandle hwnd); + STDMETHOD(Finish_Initialize)(); + STDMETHOD(Begin_GetTabInfo)(); + STDMETHOD(Finish_GetTabInfo)(CeeeTabInfo *tab_info); + STDMETHOD(Begin_Navigate)(BSTR url, long flags, BSTR target); + STDMETHOD(Finish_Navigate)(); + STDMETHOD(Begin_InsertCode)(BSTR code, BSTR file, BOOL all_frames, + CeeeTabCodeType type); + STDMETHOD(Finish_InsertCode)(); + // @} + + // Initialize a newly constructed async tab call. + // This is public only for unittesting. + HRESULT Initialize(ICeeeTabExecutor* executor); + + private: + void NavigateImpl(const CComBSTR& url, long flags, const CComBSTR& target); + void InsertCodeImpl(const CComBSTR& code, + const CComBSTR& file, + BOOL all_frames, + CeeeTabCodeType type); + + int OnCreate(LPCREATESTRUCT lpCreateStruct); + LRESULT OnExecuteTask(UINT msg, WPARAM wparam, LPARAM lparam, BOOL& handled); + virtual void OnFinalMessage(HWND window); + bool ScheduleTask(Task* task); + HRESULT Signal(); + + CComPtr<IUnknown> synchronize_; + CComPtr<ICeeeTabExecutor> executor_; + + // The task we've scheduled for execution. + scoped_ptr<Task> task_; + HRESULT task_hr_; +}; + // The executor object that is instantiated in the destination thread and // then called to... execute stuff... class ATL_NO_VTABLE CeeeExecutor : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CeeeExecutor, &CLSID_CeeeExecutor>, public IObjectWithSiteImpl<CeeeExecutor>, + public ICallFactory, public ICeeeWindowExecutor, public ICeeeTabExecutor, public ICeeeCookieExecutor, @@ -85,6 +172,7 @@ class ATL_NO_VTABLE CeeeExecutor DECLARE_NOT_AGGREGATABLE(CeeeExecutor) BEGIN_COM_MAP(CeeeExecutor) COM_INTERFACE_ENTRY(IObjectWithSite) + COM_INTERFACE_ENTRY(ICallFactory) COM_INTERFACE_ENTRY(ICeeeWindowExecutor) COM_INTERFACE_ENTRY(ICeeeTabExecutor) COM_INTERFACE_ENTRY(ICeeeCookieExecutor) @@ -93,6 +181,17 @@ class ATL_NO_VTABLE CeeeExecutor DECLARE_PROTECT_FINAL_CONSTRUCT() DECLARE_CLASSFACTORY() + CeeeExecutor(); + ~CeeeExecutor(); + + // @name ICallFactory implementation. + // @{ + STDMETHOD(CreateCall)(REFIID async_iid, + IUnknown *outer, + REFIID requested_iid, + IUnknown **out); + // @} + // @name ICeeeWindowExecutor implementation. // @{ STDMETHOD(Initialize)(CeeeWindowHandle hwnd); @@ -131,9 +230,13 @@ class ATL_NO_VTABLE CeeeExecutor STDMETHOD(OnTopFrameBeforeNavigate)(BSTR url); // @} - CeeeExecutor() : hwnd_(NULL) {} - protected: + // Unittest seam. + virtual HRESULT CreateTabCall(ICeeeTabExecutor* executor, + IUnknown *outer, + REFIID riid2, + IUnknown **out); + // Get the IWebBrowser2 interface of the // frame event host that was set as our site. virtual HRESULT GetWebBrowser(IWebBrowser2** browser); diff --git a/ceee/ie/plugin/bho/executor_com_unittest.cc b/ceee/ie/plugin/bho/executor_com_unittest.cc new file mode 100644 index 0000000..c1379a8 --- /dev/null +++ b/ceee/ie/plugin/bho/executor_com_unittest.cc @@ -0,0 +1,762 @@ +// 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. +// +// Executor COM implementation unit tests. + +#include "ceee/ie/plugin/bho/executor.h" + +#include <shlobj.h> +#include <atlbase.h> +#include "base/command_line.h" +#include "base/file_path.h" +#include "base/message_loop.h" +#include "base/path_service.h" +#include "base/process_util.h" +#include "base/thread.h" +#include "base/win/registry.h" +#include "base/win/windows_version.h" +#include "ceee/common/initializing_coclass.h" +#include "ceee/ie/plugin/toolband/toolband_proxy.h" +#include "ceee/ie/testing/mock_broker_and_friends.h" +#include "ceee/testing/utils/instance_count_mixin.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace { + +using testing::_; +using testing::DoAll; +using testing::Invoke; +using testing::InSequence; +using testing::IsNull; +using testing::NotNull; +using testing::Return; +using testing::StrEq; +using testing::StrictMock; +using testing::InstanceCountMixin; +using testing::InstanceCountMixinBase; + +class ISynchronizeMock: public ISynchronize { + public: + MOCK_METHOD2_WITH_CALLTYPE(__stdcall, Wait, + HRESULT(DWORD flags, DWORD milliseconds)); + MOCK_METHOD0_WITH_CALLTYPE(__stdcall, Signal, HRESULT()); + MOCK_METHOD0_WITH_CALLTYPE(__stdcall, Reset, HRESULT()); +}; + +class TestISynchronize + : public CComObjectRootEx<CComMultiThreadModel>, + public InitializingCoClass<TestISynchronize>, + public InstanceCountMixin<TestISynchronize>, + public StrictMock<ISynchronizeMock> { + public: + DECLARE_GET_CONTROLLING_UNKNOWN() + BEGIN_COM_MAP(TestISynchronize) + COM_INTERFACE_ENTRY(ISynchronize) + COM_INTERFACE_ENTRY_AGGREGATE_BLIND(async_call_.p) + END_COM_MAP() + + TestISynchronize() { + } + + ~TestISynchronize() { + } + + HRESULT Initialize(TestISynchronize** self) { + *self = this; + return S_OK; + } + + void set_async_call(IUnknown* async_call) { async_call_ = async_call; } + + private: + // This is the async call instance we aggregate. + CComPtr<IUnknown> async_call_; +}; + +class MockCeeeExecutor + : public CComObjectRootEx<CComSingleThreadModel>, + public InitializingCoClass<MockCeeeExecutor>, + public InstanceCountMixin<MockCeeeExecutor>, + public StrictMock<testing::MockCeeeWindowExecutorImpl>, + public StrictMock<testing::MockCeeeTabExecutorImpl>, + public StrictMock<testing::MockCeeeCookieExecutorImpl>, + public StrictMock<testing::MockCeeeInfobarExecutorImpl> { + public: + BEGIN_COM_MAP(MockCeeeExecutor) + COM_INTERFACE_ENTRY(ICeeeWindowExecutor) + COM_INTERFACE_ENTRY(ICeeeTabExecutor) + COM_INTERFACE_ENTRY(ICeeeCookieExecutor) + COM_INTERFACE_ENTRY(ICeeeInfobarExecutor) + END_COM_MAP() + + HRESULT Initialize(MockCeeeExecutor** self) { + *self = this; + return S_OK; + } +}; + +class TestAsyncTabCall + : public AsyncTabCall, + public InitializingCoClass<TestAsyncTabCall>, + public InstanceCountMixin<TestAsyncTabCall> { + public: +}; + +// A test subclass of CeeeExecutor to allow testing +// the async behavior of ICeeeTabExecutor. +class TestCeeeExecutor + : public CeeeExecutor, + public InitializingCoClass<TestCeeeExecutor>, + public InstanceCountMixin<TestCeeeExecutor> { + public: + HRESULT Initialize(TestCeeeExecutor** self) { + *self = this; + return S_OK; + } + + // Mock out the ICeeeTabExecutor interface implementation. + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, Initialize, + HRESULT(CeeeWindowHandle hwnd)); + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, GetTabInfo, + HRESULT(CeeeTabInfo* tab_info)); + MOCK_METHOD3_WITH_CALLTYPE(__stdcall, Navigate, + HRESULT(BSTR url, long flags, BSTR target)); + MOCK_METHOD4_WITH_CALLTYPE(__stdcall, InsertCode, + HRESULT(BSTR code, BSTR file, BOOL all_frames, CeeeTabCodeType type)); + + MOCK_METHOD4(CreateTabCall, HRESULT(ICeeeTabExecutor* executor, + IUnknown *outer, + REFIID riid2, + IUnknown **out)); +}; + +class RemoteObjectHost; +DISABLE_RUNNABLE_METHOD_REFCOUNT(RemoteObjectHost); + +class RemoteObjectHost { + public: + RemoteObjectHost() : remote_thread_("RemoteObjectHost") { + } + + ~RemoteObjectHost() { + remote_git_ptr_.Revoke(); + remote_object_.Release(); + } + + void HostObject(IUnknown* object) { + // Spin up our thread and initialize it. + ASSERT_TRUE(remote_thread_.StartWithOptions( + base::Thread::Options(MessageLoop::TYPE_UI, 0))); + + // Marshal the object in the thread. + RunSync( + NewRunnableMethod(this, &RemoteObjectHost::InitRemoteThread, object)); + + // Marshal the contained object back as an IUnknown. + ASSERT_HRESULT_SUCCEEDED(remote_git_ptr_.CopyTo(&remote_object_)); + } + + void RunSync(Task* task) { + ScopedHandle event(::CreateEvent(NULL, TRUE, FALSE, NULL)); + + remote_thread_.message_loop()->PostTask(FROM_HERE, task); + remote_thread_.message_loop()->PostTask(FROM_HERE, + NewRunnableFunction(::SetEvent, event.Get())); + + ASSERT_EQ(WAIT_OBJECT_0, ::WaitForSingleObject(event.Get(), INFINITE)); + } + + void InitRemoteThread(IUnknown* object) { + EXPECT_HRESULT_SUCCEEDED(::CoInitialize(NULL)); + EXPECT_HRESULT_SUCCEEDED(remote_git_ptr_.Attach(object)); + } + + public: + // The git pointer is created in the remote_thread_. + CComGITPtr<IUnknown> remote_git_ptr_; + // And this is a local proxy to the remote object. + CComPtr<IUnknown> remote_object_; + base::Thread remote_thread_; +}; + +ACTION_P(QuitMessageLoop, message_loop) { + message_loop->PostTask(FROM_HERE, new MessageLoop::QuitTask()); +} + +static const HRESULT E_CAFEBABE = 0xCAFEBABE; + +class ExecutorComTest: public testing::Test { + public: + ExecutorComTest() + : executor_(NULL), + synchronize_(NULL), + loop_(MessageLoop::TYPE_UI) { + } + + static bool ImplementsThreadSafeReferenceCounting() { + return true; + } + + virtual void SetUp() { + ASSERT_HRESULT_SUCCEEDED(::CoInitialize(NULL)); + + ASSERT_HRESULT_SUCCEEDED(TestCeeeExecutor::CreateInitialized( + &executor_, &executor_keeper_)); + ASSERT_HRESULT_SUCCEEDED(TestISynchronize::CreateInitialized( + &synchronize_, &synchronize_keeper_)); + } + + virtual void TearDown() { + executor_ = NULL; + executor_keeper_.Release(); + + synchronize_->set_async_call(NULL); + synchronize_ = NULL; + synchronize_keeper_.Release(); + + UnregisterProxies(); + + // We want everything unregistered before we CoUninitialize. + ::CoUninitialize(); + + EXPECT_EQ(0, InstanceCountMixinBase::all_instance_count()); + } + + void RegisterProxies() { + ASSERT_TRUE(RegisterProxyStubs(&proxy_cookies_)); + } + + void UnregisterProxies() { + if (!proxy_cookies_.empty()) { + UnregisterProxyStubs(proxy_cookies_); + proxy_cookies_.clear(); + } + } + + static void CreateTestTabCallImpl(ICeeeTabExecutor* executor, + IUnknown *outer, + REFIID riid2, + IUnknown **out) { + CComPolyObject<TestAsyncTabCall>* async_tab_call = NULL; + ASSERT_HRESULT_SUCCEEDED( + CComPolyObject<TestAsyncTabCall>::CreateInstance( + outer, &async_tab_call)); + ASSERT_TRUE(async_tab_call != NULL); + ASSERT_HRESULT_SUCCEEDED(async_tab_call->m_contained.Initialize(executor)); + ASSERT_HRESULT_SUCCEEDED( + async_tab_call->QueryInterface(riid2, reinterpret_cast<void**>(out))); + } + + void ExpectCreateTabCall(TestCeeeExecutor* executor) { + EXPECT_CALL(*executor, CreateTabCall(executor, _, _, _)) + .WillOnce( + DoAll( + Invoke(&ExecutorComTest::CreateTestTabCallImpl), + Return(S_OK))); + } + + void CreateTabExecutor(AsyncICeeeTabExecutor** executor_call) { + // Retrieve the call factory. + CComPtr<ICallFactory> call_factory; + ASSERT_HRESULT_SUCCEEDED(executor_keeper_->QueryInterface(&call_factory)); + ASSERT_TRUE(call_factory != NULL); + + // Create the call object. + ASSERT_NO_FATAL_FAILURE(ExpectCreateTabCall(executor_)); + CComPtr<IUnknown> async_unknown; + ASSERT_HRESULT_SUCCEEDED( + call_factory->CreateCall(IID_AsyncICeeeTabExecutor, + synchronize_->GetControllingUnknown(), + IID_IUnknown, + &async_unknown)); + synchronize_->set_async_call(async_unknown); + async_unknown.Release(); + + ASSERT_HRESULT_SUCCEEDED( + synchronize_keeper_->QueryInterface(executor_call)); + } + + void RunShortMessageLoop() { + // Set up the expectations we have for invocations + // during the brief message loop. + EXPECT_CALL(*synchronize_, Signal()) + .WillOnce( + DoAll( + QuitMessageLoop(&loop_), + Return(S_OK))); + + // And run the loop. + loop_.Run(); + } + + protected: + // This test executor is created on setup. + TestCeeeExecutor* executor_; + CComPtr<ICeeeTabExecutor> executor_keeper_; + + TestISynchronize* synchronize_; + CComPtr<ISynchronize> synchronize_keeper_; + + MessageLoop loop_; + + std::vector<DWORD> proxy_cookies_; +}; + +DISABLE_RUNNABLE_METHOD_REFCOUNT(ExecutorComTest); + +static const wchar_t* kUrl = L"http://www.google.com/"; +static long kFlags = 74565; +static const wchar_t* kTarget = L"_blank"; + +TEST_F(ExecutorComTest, MarshalingFailsWithNoProxy) { + RemoteObjectHost host; + ASSERT_NO_FATAL_FAILURE(host.HostObject(executor_keeper_)); + + // We expect failure, unmarshaling shouldn't be possible + // without the proxies registered in our (source) thread. + CComPtr<ICeeeTabExecutor> executor; + ASSERT_HRESULT_FAILED(host.remote_object_.QueryInterface(&executor)); +} + +TEST_F(ExecutorComTest, MarshalingFailsWithNoLocalProxy) { + RemoteObjectHost host; + ASSERT_NO_FATAL_FAILURE(host.HostObject(executor_keeper_)); + + std::vector<DWORD> invoker_cookies; + host.RunSync(NewRunnableFunction(RegisterProxyStubs, &invoker_cookies)); + + // We expect failure, unmarshaling shouldn't be possible + // without the proxies registered in our (source) thread. + CComPtr<ICeeeTabExecutor> executor; + ASSERT_HRESULT_FAILED(host.remote_object_.QueryInterface(&executor)); +} + +TEST_F(ExecutorComTest, MarshalingSucceedsWithProxiesRegistered) { + RemoteObjectHost host; + ASSERT_NO_FATAL_FAILURE(host.HostObject(executor_keeper_)); + + std::vector<DWORD> invoker_cookies; + host.RunSync(NewRunnableFunction(RegisterProxyStubs, &invoker_cookies)); + + // Register proxies in our thread. + RegisterProxies(); + + // We expect success as both home and away proxies + // are now registered. + CComPtr<ICeeeTabExecutor> executor; + ASSERT_HRESULT_SUCCEEDED(host.remote_object_.QueryInterface(&executor)); +} + +TEST_F(ExecutorComTest, AllProxiesAreRegistered) { + // Create the mock executor. + CComPtr<IUnknown> mock_keeper; + MockCeeeExecutor* mock_executor = NULL; + ASSERT_HRESULT_SUCCEEDED( + MockCeeeExecutor::CreateInitialized(&mock_executor, &mock_keeper)); + + RemoteObjectHost host; + ASSERT_NO_FATAL_FAILURE(host.HostObject(mock_keeper)); + + // Register proxies on both sides. + std::vector<DWORD> invoker_cookies; + host.RunSync(NewRunnableFunction(RegisterProxyStubs, &invoker_cookies)); + RegisterProxies(); + + CComPtr<ICeeeWindowExecutor> window_executor; + EXPECT_HRESULT_SUCCEEDED( + host.remote_object_.QueryInterface(&window_executor)); + + CComPtr<ICeeeTabExecutor> tab_executor; + EXPECT_HRESULT_SUCCEEDED( + host.remote_object_.QueryInterface(&tab_executor)); + + CComPtr<ICeeeCookieExecutor> cookie_executor; + EXPECT_HRESULT_SUCCEEDED( + host.remote_object_.QueryInterface(&cookie_executor)); + + CComPtr<ICeeeInfobarExecutor> infobar_executor; + EXPECT_HRESULT_SUCCEEDED( + host.remote_object_.QueryInterface(&infobar_executor)); +} + +TEST_F(ExecutorComTest, UnregisterProxiesWorks) { + RemoteObjectHost host; + ASSERT_NO_FATAL_FAILURE(host.HostObject(executor_keeper_)); + + std::vector<DWORD> invoker_cookies; + host.RunSync(NewRunnableFunction(RegisterProxyStubs, &invoker_cookies)); + + // Register proxies in our thread. + RegisterProxies(); + + // We expect success as both home and away proxies + // are now registered. + CComPtr<ICeeeTabExecutor> executor; + ASSERT_HRESULT_SUCCEEDED(host.remote_object_.QueryInterface(&executor)); + executor.Release(); + + // Now unregister the proxies on our side and try again. + // We need to release the local proxy, as it'll cache any interfaces + // we've already queried. + UnregisterProxies(); + host.remote_object_.Release(); + ASSERT_HRESULT_SUCCEEDED(host.remote_git_ptr_.CopyTo(&host.remote_object_)); + ASSERT_HRESULT_FAILED(host.remote_object_.QueryInterface(&executor)); +} + +TEST_F(ExecutorComTest, ImplementsCallFactory) { + CComPtr<ICallFactory> call_factory; + ASSERT_HRESULT_SUCCEEDED(executor_keeper_->QueryInterface(&call_factory)); + ASSERT_TRUE(call_factory != NULL); +} + +TEST_F(ExecutorComTest, SyncInitialize) { + CComPtr<AsyncICeeeTabExecutor> executor_call; + ASSERT_NO_FATAL_FAILURE(CreateTabExecutor(&executor_call)); + + static const CeeeWindowHandle kWindow = 0xF1F2F3F4; + // We expect this to run synchronously at begin time. + EXPECT_CALL(*synchronize_, Signal()); + EXPECT_CALL(*executor_, Initialize(kWindow)) + .WillOnce(Return(E_CAFEBABE)); + + ASSERT_HRESULT_SUCCEEDED(executor_call->Begin_Initialize(kWindow)); + + // Verify that we return the exepected HRESULT. + ASSERT_EQ(E_CAFEBABE, executor_call->Finish_Initialize()); +} + +TEST_F(ExecutorComTest, AsyncGetTabInfo) { + CComPtr<AsyncICeeeTabExecutor> executor_call; + ASSERT_NO_FATAL_FAILURE(CreateTabExecutor(&executor_call)); + ASSERT_HRESULT_SUCCEEDED(executor_call->Begin_GetTabInfo()); + + ASSERT_NO_FATAL_FAILURE(RunShortMessageLoop()); + + // We expect the call after the message loop, as there's an out + // param we have to cater to. + CeeeTabInfo tab_info = {}; + EXPECT_CALL(*executor_, GetTabInfo(&tab_info)) + .WillOnce(Return(E_CAFEBABE)); + + // Verify that we return the expected HRESULT. + ASSERT_EQ(E_CAFEBABE, executor_call->Finish_GetTabInfo(&tab_info)); +} + +TEST_F(ExecutorComTest, AsyncNavigate) { + CComPtr<AsyncICeeeTabExecutor> executor_call; + ASSERT_NO_FATAL_FAILURE(CreateTabExecutor(&executor_call)); + + ASSERT_HRESULT_SUCCEEDED(executor_call->Begin_Navigate( + CComBSTR(kUrl), kFlags, CComBSTR(kTarget))); + + // We expect the call to from within the message loop. + EXPECT_CALL(*executor_, Navigate(StrEq(kUrl), kFlags, StrEq(kTarget))) + .WillOnce(Return(E_CAFEBABE)); + + ASSERT_NO_FATAL_FAILURE(RunShortMessageLoop()); + + // Verify that we return the exepected HRESULT. + ASSERT_EQ(E_CAFEBABE, executor_call->Finish_Navigate()); +} + +TEST_F(ExecutorComTest, AsyncInsertCode) { + CComPtr<AsyncICeeeTabExecutor> executor_call; + ASSERT_NO_FATAL_FAILURE(CreateTabExecutor(&executor_call)); + const wchar_t* kCode = L"window.alert(\"You've been had.\")"; + const wchar_t* kFile = L"CONSOLE"; + ASSERT_HRESULT_SUCCEEDED(executor_call->Begin_InsertCode( + CComBSTR(kCode), CComBSTR(kFile), TRUE, kCeeeTabCodeTypeJs)); + + // Now set up the expectations we have for invocations + // during the brief message loop. + EXPECT_CALL(*executor_, + InsertCode(StrEq(kCode), StrEq(kFile), TRUE, kCeeeTabCodeTypeJs)) + .WillOnce(Return(E_CAFEBABE)); + + ASSERT_NO_FATAL_FAILURE(RunShortMessageLoop()); + + // Verify that we return the expected HRESULT. + ASSERT_EQ(E_CAFEBABE, executor_call->Finish_InsertCode()); +} + +// This unittest fails due to a bug in COM, whereby asynchronous calls across +// apartments in the same process cause an access violation on freeing an +// invalid pointer. +// This is true as of 15 Nov 2010, leaving this around in hopes Microsoft +// comes through with a fix. +TEST_F(ExecutorComTest, DISABLED_CrossApartmentCall) { + RemoteObjectHost host; + ASSERT_NO_FATAL_FAILURE(host.HostObject(executor_keeper_)); + + // Register home and away proxies. + std::vector<DWORD> invoker_cookies; + host.RunSync(NewRunnableFunction(RegisterProxyStubs, &invoker_cookies)); + RegisterProxies(); + + CComPtr<ICeeeTabExecutor> executor; + ASSERT_HRESULT_SUCCEEDED(host.remote_object_.QueryInterface(&executor)); + + // A navigation call should go through with no trouble. + EXPECT_CALL(*executor_, Navigate(StrEq(kUrl), 0, StrEq(kTarget))) + .WillOnce(Return(S_OK)); + + EXPECT_HRESULT_SUCCEEDED( + executor->Navigate(CComBSTR(kUrl), 0, CComBSTR(kTarget))); +} + +class TestClassFactory + : public CComObjectRootEx<CComSingleThreadModel>, + public InitializingCoClass<TestClassFactory>, + public IClassFactory { + public: + BEGIN_COM_MAP(TestClassFactory) + COM_INTERFACE_ENTRY(IClassFactory) + END_COM_MAP() + + TestClassFactory() : executor_(NULL) { + } + + HRESULT Initialize(TestCeeeExecutor* executor) { + executor_ = executor; + return S_OK; + } + + STDMETHOD(CreateInstance)(IUnknown *outer, REFIID riid, void **out) { + if (outer != NULL) { + ADD_FAILURE() << "Attempted aggregation in CreateInstance"; + return CLASS_E_NOAGGREGATION; + } + + return executor_->QueryInterface(riid, out); + } + + STDMETHOD(LockServer)(BOOL lock) { + ADD_FAILURE() << "Unexpected call to LockServer"; + return E_NOTIMPL; + } + + TestCeeeExecutor* executor_; +}; + +const wchar_t* kHKCUReplacement = + L"Software\\Google\\ExecutorComInvocationTest\\HKCU"; + +// Registry script to register iid->async iid mapping. +const wchar_t* kRegScript = + L"%ROOT% {\n" + L" Software {\n" + L" Classes {\n" + L" NoRemove 'Interface' {\n" + L" '%IID%' = s '%NAME%' {\n" + L" AsynchronousInterface = s '%ASYNC_IID%'\n" + L" }\n" + L" }\n" + L" }\n" + L" }\n" + L"}\n"; + + + +// This fixture assists in launching a sub-process to invoke on our executor. +// We can't do this from another thread in this process because of a bug +// in the COM marshaling machinery that causes an access violation after +// an asynchronous invocation across single-threaded apartments in the +// same process. +class ExecutorComInvocationTest: public ExecutorComTest { + public: + // We register in HKCU if the user is non-admin, otherwise in + // HKLM, because on UAC-enabled systems, HKCU is ignored for + // administrative users. + ExecutorComInvocationTest() + : root_key_(::IsUserAnAdmin() ? L"HKLM" : L"HKCU"), + clsid_(GUID_NULL), + cookie_(0), + invoker_(base::kNullProcessHandle) { + } + + void UpdateRegistry(bool reg) { + // Register or unregister the sync->async IID mapping in our HKCR. + // Unfortunately there's no way to do this without changing machine-global + // state. The COM marshaling machinery does lookups in HKCR, which seems + // to be amalgamated from HKCU and HKLM in kernel-land, so + // RegOverridePredefKey doesn't help. + CRegObject reg_obj; + ASSERT_HRESULT_SUCCEEDED(reg_obj.FinalConstruct()); + ASSERT_HRESULT_SUCCEEDED( + reg_obj.AddReplacement(L"IID", CComBSTR(IID_ICeeeTabExecutor))); + ASSERT_HRESULT_SUCCEEDED( + reg_obj.AddReplacement(L"ASYNC_IID", + CComBSTR(IID_AsyncICeeeTabExecutor))); + ASSERT_HRESULT_SUCCEEDED( + reg_obj.AddReplacement(L"NAME", L"ICeeeTabExecutor")); + ASSERT_HRESULT_SUCCEEDED( + reg_obj.AddReplacement(L"ROOT", root_key_)); + + if (reg) { + ASSERT_HRESULT_SUCCEEDED(reg_obj.StringRegister(kRegScript)); + } else { + ASSERT_HRESULT_SUCCEEDED(reg_obj.StringUnregister(kRegScript)); + } + } + + virtual void SetUp() { + ASSERT_NO_FATAL_FAILURE(ExecutorComTest::SetUp()); + ASSERT_NO_FATAL_FAILURE(UpdateRegistry(true)); + ASSERT_NO_FATAL_FAILURE(RegisterProxies()); + + // Register a class factory with a new clsid. + ASSERT_HRESULT_SUCCEEDED(::CoCreateGuid(&clsid_)); + ASSERT_HRESULT_SUCCEEDED( + TestClassFactory::CreateInitialized(executor_, &class_factory_)); + ASSERT_HRESULT_SUCCEEDED(::CoRegisterClassObject(clsid_, + class_factory_, + CLSCTX_LOCAL_SERVER, + REGCLS_MULTIPLEUSE, + &cookie_)); + } + + virtual void TearDown() { + // Wait for the invoker and assert it returned E_CAFEBABE. + if (invoker_ != base::kNullProcessHandle) { + int exit_code = -1; + EXPECT_TRUE(base::WaitForExitCode(invoker_, &exit_code)); + EXPECT_EQ(E_CAFEBABE, exit_code); + } + + EXPECT_HRESULT_SUCCEEDED(::CoRevokeClassObject(cookie_)); + EXPECT_NO_FATAL_FAILURE(UpdateRegistry(false)); + ExecutorComTest::TearDown(); + } + + template <size_t N> + void InvokeExecutor(const char* func, const char* (&args)[N]) { + FilePath testing_invoke_executor; + ASSERT_TRUE(PathService::Get(base::DIR_EXE, &testing_invoke_executor)); + testing_invoke_executor = + testing_invoke_executor.Append(L"testing_invoke_executor.exe"); + + wchar_t clsid_str[40] = {}; + ASSERT_NE(0, ::StringFromGUID2(clsid_, clsid_str, arraysize(clsid_str))); + CommandLine cmd_line(testing_invoke_executor); + cmd_line.AppendSwitchNative("class_id", clsid_str); + cmd_line.AppendSwitchASCII("func", func); + + ASSERT_TRUE(N % 2 == 0); + for (size_t i = 0; i < N; i += 2) { + cmd_line.AppendSwitchASCII(args[i], args[i + 1]); + } + + ASSERT_TRUE(base::LaunchApp(cmd_line, false, true, &invoker_)); + } + + protected: + GUID clsid_; + DWORD cookie_; + CComPtr<IClassFactory> class_factory_; + const wchar_t* root_key_; + base::ProcessHandle invoker_; +}; + + +TEST_F(ExecutorComInvocationTest, SyncInitialize) { + const char* args[] = { + "hwnd", "1457", + }; + ASSERT_NO_FATAL_FAILURE(InvokeExecutor("Initialize", args)); + + { + InSequence sequence; + + // We expect the invocation to do a create tab call, + // followed by a navigate. + ExpectCreateTabCall(executor_); + EXPECT_CALL(*executor_, Initialize(static_cast<CeeeWindowHandle>(1457))) + .WillOnce( + DoAll( + QuitMessageLoop(&loop_), + Return(E_CAFEBABE))); + } + + loop_.Run(); +} + +TEST_F(ExecutorComInvocationTest, AsyncGetTabInfo) { + const char* args[] = { + "ignore", "me", + }; + ASSERT_NO_FATAL_FAILURE(InvokeExecutor("GetTabInfo", args)); + + { + InSequence sequence; + + // We expect the invocation to do a create tab call, + // followed by a navigate. + ExpectCreateTabCall(executor_); + EXPECT_CALL(*executor_, GetTabInfo(NotNull())) + .WillOnce( + DoAll( + QuitMessageLoop(&loop_), + Return(E_CAFEBABE))); + } + + loop_.Run(); +} + +TEST_F(ExecutorComInvocationTest, AsyncNavigate) { + const char* args[] = { + "url", "http://www.google.com/", + "flags", "74565", + "target", "_blank", + }; + ASSERT_NO_FATAL_FAILURE(InvokeExecutor("Navigate", args)); + + { + InSequence sequence; + + // We expect the invocation to do a create tab call, + // followed by a navigate. + ExpectCreateTabCall(executor_); + EXPECT_CALL(*executor_, Navigate(StrEq(L"http://www.google.com/"), + kFlags, + StrEq(kTarget))) + .WillOnce( + DoAll( + QuitMessageLoop(&loop_), + Return(E_CAFEBABE))); + } + + loop_.Run(); +} + +TEST_F(ExecutorComInvocationTest, AsyncInsertCode) { + const char* args[] = { + "code", "alert('you\'ve been had')", + "file", "CONSOLE", + "all_frames", "true", + "type", "1", // kCeeeTabCodeTypeJs + }; + ASSERT_NO_FATAL_FAILURE(InvokeExecutor("InsertCode", args)); + + { + InSequence sequence; + + // We expect the invocation to do a create tab call, + // followed by a navigate. + ExpectCreateTabCall(executor_); + EXPECT_CALL(*executor_, InsertCode(StrEq(L"alert('you\'ve been had')"), + StrEq(L"CONSOLE"), + TRUE, + kCeeeTabCodeTypeJs)) + .WillOnce( + DoAll( + QuitMessageLoop(&loop_), + Return(E_CAFEBABE))); + } + + loop_.Run(); +} + +} // namespace diff --git a/ceee/ie/plugin/bho/testing_invoke_executor.cc b/ceee/ie/plugin/bho/testing_invoke_executor.cc new file mode 100644 index 0000000..f56de47 --- /dev/null +++ b/ceee/ie/plugin/bho/testing_invoke_executor.cc @@ -0,0 +1,96 @@ +// 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. +// +// Implements a command-line executable that assists in testing the executor +// by invoking on it from across a process boundary. +#include <atlbase.h> +#include <string> +#include "base/at_exit.h" +#include "base/command_line.h" +#include "base/logging.h" +#include "ceee/ie/plugin/toolband/toolband_proxy.h" + +#include "toolband.h" // NOLINT + +HRESULT DoInitialize(ICeeeTabExecutor* executor, CommandLine* cmd_line) { + CeeeWindowHandle hwnd = static_cast<CeeeWindowHandle>( + atoi(cmd_line->GetSwitchValueASCII("hwnd").c_str())); + + return executor->Initialize(hwnd); +} + +HRESULT DoGetTabInfo(ICeeeTabExecutor* executor, CommandLine* cmd_line) { + CeeeTabInfo info = {}; + return executor->GetTabInfo(&info); +} + +HRESULT DoNavigate(ICeeeTabExecutor* executor, CommandLine* cmd_line) { + std::wstring url = cmd_line->GetSwitchValueNative("url"); + long flags = atoi(cmd_line->GetSwitchValueASCII("flags").c_str()); + std::wstring target = cmd_line->GetSwitchValueNative("target"); + + return executor->Navigate(CComBSTR(url.c_str()), + flags, + CComBSTR(target.c_str())); +} + +HRESULT DoInsertCode(ICeeeTabExecutor* executor, CommandLine* cmd_line) { + std::wstring code = cmd_line->GetSwitchValueNative("code"); + std::wstring file = cmd_line->GetSwitchValueNative("file"); + bool all_frames = cmd_line->GetSwitchValueASCII("all_frames") == "true"; + CeeeTabCodeType type = static_cast<CeeeTabCodeType>( + atoi(cmd_line->GetSwitchValueASCII("type").c_str())); + + return executor->InsertCode(CComBSTR(code.c_str()), + CComBSTR(file.c_str()), + all_frames, + type); +} + +int main(int argc, char** argv) { + ::CoInitialize(NULL); + + base::AtExitManager at_exit; + CommandLine::Init(argc, argv); + + CommandLine* cmd_line = CommandLine::ForCurrentProcess(); + std::wstring clsid = cmd_line->GetSwitchValueNative("class_id"); + + // Register the proxy/stubs for the exector interfaces. + RegisterProxyStubs(NULL); + + CLSID executor_clsid = {}; + HRESULT hr = ::CLSIDFromString(clsid.c_str(), &executor_clsid); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to read class_id argument"; + exit(hr); + } + + CComPtr<ICeeeTabExecutor> executor; + hr = executor.CoCreateInstance(executor_clsid); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to create tab executor."; + exit(hr); + } + + std::string func = cmd_line->GetSwitchValueASCII("func"); + if (func == "Initialize") { + hr = DoInitialize(executor, cmd_line); + } else if (func == "GetTabInfo") { + hr = DoGetTabInfo(executor, cmd_line); + } else if (func == "Navigate") { + hr = DoNavigate(executor, cmd_line); + } else if (func == "InsertCode") { + hr = DoInsertCode(executor, cmd_line); + } else { + hr = E_UNEXPECTED; + } + + // We exit, because we don't want to trust the + // server to be around for teardown and goodbyes. + exit(hr); + + NOTREACHED(); + return hr; +} diff --git a/ceee/ie/plugin/toolband/resource.h b/ceee/ie/plugin/toolband/resource.h index d090e40..1d27001 100644 --- a/ceee/ie/plugin/toolband/resource.h +++ b/ceee/ie/plugin/toolband/resource.h @@ -18,7 +18,7 @@ #define IDR_GREASEMONKEY_API_JS 105 #define IDR_EXECUTOR 106 #define IDR_EXECUTOR_CREATOR 107 -#define IDR_NO_EXTENSION 108 +#define IDR_TOOLBAND_PROXY 108 // Next default values for new objects // diff --git a/ceee/ie/plugin/toolband/toolband.gyp b/ceee/ie/plugin/toolband/toolband.gyp index 7fb5529bfc..63686f1 100644 --- a/ceee/ie/plugin/toolband/toolband.gyp +++ b/ceee/ie/plugin/toolband/toolband.gyp @@ -41,6 +41,7 @@ '../../broker/broker.gyp:broker_rpc_lib', 'ceee_ie_lib', 'ie_toolband_common', + 'toolband_proxy_lib', 'toolband_idl', '../bho/bho.gyp:bho', '../scripting/scripting.gyp:scripting', @@ -66,8 +67,8 @@ '../scripting/content_script_manager.rc', ], 'libraries': [ - 'oleacc.lib', 'iepmapi.lib', + 'oleacc.lib', 'rpcrt4.lib', ], 'include_dirs': [ @@ -120,5 +121,32 @@ 'include_dirs': ['<(SHARED_INTERMEDIATE_DIR)'], }, }, + { + # This target builds a library out of the toolband proxy/stubs. + # TODO(siggi): Rename the IDL and move it to ie/plugin/common, as + # it's defining interfaces that are common across the the broker + # and bho/executor. + 'target_name': 'toolband_proxy_lib', + 'type': 'static_library', + 'sources': [ + 'toolband_proxy.h', + 'toolband_proxy.cc', + 'toolband_proxy.rgs', + '<(SHARED_INTERMEDIATE_DIR)/toolband_p.c', + '<(SHARED_INTERMEDIATE_DIR)/toolband_dlldata.c', + ], + 'dependencies': [ + '../../../../base/base.gyp:base', + 'toolband_idl', + ], + 'defines': [ + # This define adds ToolbandProxy as a prefix to the generated + # proxy/stub entrypoint routine names. + 'ENTRY_PREFIX=ToolbandProxy', + # The proxy stub code defines _purecall, which conflicts with our + # CRT, so we neuter this here. + '_purecall=ToolbandPureCall', + ], + }, ] } diff --git a/ceee/ie/plugin/toolband/toolband.idl b/ceee/ie/plugin/toolband/toolband.idl index 4164945..f85fff9 100644 --- a/ceee/ie/plugin/toolband/toolband.idl +++ b/ceee/ie/plugin/toolband/toolband.idl @@ -4,6 +4,12 @@ // // @file // Interface and object declarations for CEEE IE toolband. +// +// Note: We hand-register the proxy/stubs for the marshalable interfaces +// in this file in each COM apartment that needs to use them. +// If you add new marshalable interfaces to this file be sure to update +// the list of interface proxy/stubs in toolband_proxy.cc, and to +// call RegisterProxyStubs and UnregisterProxyStubs appropriately. import "oaidl.idl"; import "ocidl.idl"; @@ -102,11 +108,10 @@ typedef struct { [ object, - uuid(8DEEECC5-7B49-482d-99F8-2109EA5F2618), + uuid(66CDB7E2-B326-493e-B469-16234426C89B), nonextensible, helpstring("ICeeeWindowExecutor Interface"), pointer_default(unique), - oleautomation ] // Object provided to the broker to execute code in a given window's thread. interface ICeeeWindowExecutor : IUnknown { @@ -176,11 +181,11 @@ interface ICeeeWindowExecutor : IUnknown { [ object, - uuid(C7FF41BA-72D5-4086-8B5A-2EF4FD12E0FE), + uuid(1F509E26-002A-461b-9083-ABD1002BD4E2), + async_uuid(EDF51257-5A41-4391-8186-44DB3E84FBE6), nonextensible, helpstring("ICeeeTabExecutor Interface"), pointer_default(unique), - oleautomation ] // Object provided to the broker to execute code in a given window's thread. interface ICeeeTabExecutor : IUnknown { @@ -228,11 +233,10 @@ interface ICeeeTabExecutor : IUnknown { [ object, - uuid(07630967-D7FB-4745-992F-28614930D9A3), + uuid(875DB992-6ADA-4330-AD3F-28B3E3B9DB01), nonextensible, helpstring("ICeeeCookieExecutor Interface"), pointer_default(unique), - oleautomation ] // Object provided to the broker to execute code in a given window's thread. interface ICeeeCookieExecutor : IUnknown { @@ -267,11 +271,10 @@ interface ICeeeCookieExecutor : IUnknown { [ object, - uuid(276D47E8-1692-4a21-907D-948D170E4330), + uuid(C79A1479-33F6-419f-970F-2FB3D1388922), nonextensible, helpstring("ICeeeInfobarExecutor Interface"), pointer_default(unique), - oleautomation ] // Object provided to the broker to execute code in a given window's thread. interface ICeeeInfobarExecutor : IUnknown { diff --git a/ceee/ie/plugin/toolband/toolband.rc b/ceee/ie/plugin/toolband/toolband.rc index 2f8c84a..d31f5b2 100755 --- a/ceee/ie/plugin/toolband/toolband.rc +++ b/ceee/ie/plugin/toolband/toolband.rc @@ -82,6 +82,7 @@ IDR_BROWSERHELPEROBJECT REGISTRY "ceee\\ie\\plugin\\bho\\browser_ IDR_EXECUTOR REGISTRY "ceee\\ie\\plugin\\bho\\executor.rgs" IDR_EXECUTOR_CREATOR REGISTRY "ceee\\ie\\plugin\\bho\\executor_creator.rgs" IDR_TOOL_BAND REGISTRY "tool_band.rgs" +IDR_TOOLBAND_PROXY REGISTRY "toolband_proxy.rgs" ///////////////////////////////////////////////////////////////////////////// // diff --git a/ceee/ie/plugin/toolband/toolband_module.cc b/ceee/ie/plugin/toolband/toolband_module.cc index 472bd30..6824542 100644 --- a/ceee/ie/plugin/toolband/toolband_module.cc +++ b/ceee/ie/plugin/toolband/toolband_module.cc @@ -16,6 +16,7 @@ #include "ceee/ie/plugin/bho/executor.h" #include "ceee/ie/plugin/toolband/toolband_module_reporting.h" #include "ceee/ie/plugin/toolband/tool_band.h" +#include "ceee/ie/plugin/toolband/toolband_proxy.h" #include "ceee/ie/plugin/scripting/script_host.h" #include "ceee/common/windows_constants.h" #include "chrome/common/url_constants.h" @@ -45,6 +46,8 @@ OBJECT_ENTRY_AUTO(CLSID_CeeeExecutor, CeeeExecutor) class ToolbandModule : public CAtlDllModuleT<ToolbandModule> { public: + typedef CAtlDllModuleT<ToolbandModule> Super; + ToolbandModule(); ~ToolbandModule(); @@ -59,8 +62,11 @@ class ToolbandModule : public CAtlDllModuleT<ToolbandModule> { return module_initialized_; } - private: + // We override reg/unregserver to register proxy/stubs. + HRESULT DllRegisterServer(); + HRESULT DllUnregisterServer(); + private: base::AtExitManager at_exit_; bool module_initialized_; bool crash_reporting_initialized_; @@ -108,7 +114,7 @@ ToolbandModule::~ToolbandModule() { } HRESULT ToolbandModule::DllCanUnloadNow() { - HRESULT hr = CAtlDllModuleT<ToolbandModule>::DllCanUnloadNow(); + HRESULT hr = Super::DllCanUnloadNow(); if (hr == S_OK) Term(); return hr; @@ -117,7 +123,26 @@ HRESULT ToolbandModule::DllCanUnloadNow() { HRESULT ToolbandModule::DllGetClassObject(REFCLSID clsid, REFIID iid, void** object) { Init(); - return CAtlDllModuleT<ToolbandModule>::DllGetClassObject(clsid, iid, object); + return Super::DllGetClassObject(clsid, iid, object); +} + +HRESULT ToolbandModule::DllRegisterServer() { + // No typelib registration. + HRESULT hr = Super::DllRegisterServer(); + if (SUCCEEDED(hr) && !RegisterAsyncProxies(true)) { + hr = SELFREG_E_CLASS; + } + return hr; +} + +HRESULT ToolbandModule::DllUnregisterServer() { + // No typelib registration. + HRESULT hr = Super::DllUnregisterServer(FALSE); + if (!RegisterAsyncProxies(false) && SUCCEEDED(hr)) { + // Note the error. + hr = SELFREG_E_CLASS; + } + return hr; } void ToolbandModule::Init() { @@ -162,8 +187,7 @@ LONG ceee_module_util::UnlockModule() { return module.Unlock(); } - -// DLL Entry Point +// DLL Entry Point. extern "C" BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved) { // Prevent us from being loaded by older versions of the shell. @@ -196,7 +220,7 @@ STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv) { // function, which keeps us safe from ever forgetting to check for // the --enable-ceee flag. STDAPI DllRegisterServerImpl(void) { - // registers object, typelib and all interfaces in typelib + // Registers objects. HRESULT hr = module.DllRegisterServer(); return hr; } diff --git a/ceee/ie/plugin/toolband/toolband_proxy.cc b/ceee/ie/plugin/toolband/toolband_proxy.cc new file mode 100644 index 0000000..7c1d2f1 --- /dev/null +++ b/ceee/ie/plugin/toolband/toolband_proxy.cc @@ -0,0 +1,150 @@ +// 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 "ceee/ie/plugin/toolband/toolband_proxy.h" + +#include <atlbase.h> +#include "base/logging.h" +#include "base/stringprintf.h" +#include "base/win/registry.h" +#include "ceee/common/com_utils.h" +#include "ceee/ie/plugin/toolband/resource.h" + +#include "toolband.h" // NOLINT + +extern "C" { +// Declare the entrypoint function(s) generated by MIDL for our proxy/stubs. +HRESULT STDAPICALLTYPE ToolbandProxyDllGetClassObject(REFCLSID clsid, + REFIID iid, + void** ppv); +} // extern "C" + +namespace { + +struct InterfaceInfo { + const wchar_t* name; + const IID* iid; + const IID* async_iid; +}; + +#define DECLARE_SYNC_INTERFACE(itf) \ + { L#itf, &IID_##itf, NULL }, +#define DECLARE_ASYNC_INTERFACE(itf) \ + { L#itf, &IID_##itf, &IID_Async##itf }, \ + DECLARE_SYNC_INTERFACE(Async##itf) + +// If you add new executor interfaces to toolband.idl, make sure to add +// their IIDs here, or you will not be able to marshal them. +const InterfaceInfo kInterfaceInfo[] = { + DECLARE_SYNC_INTERFACE(ICeeeWindowExecutor) + DECLARE_ASYNC_INTERFACE(ICeeeTabExecutor) + DECLARE_SYNC_INTERFACE(ICeeeCookieExecutor) + DECLARE_SYNC_INTERFACE(ICeeeInfobarExecutor) +}; + +#ifndef NDEBUG +void CheckAsyncIidRegistered(const IID& iid, const IID& async_iid) { + std::wstring iid_str; + bool success = com::GuidToString(iid, &iid_str); + DCHECK(success); + std::wstring key_name = base::StringPrintf( + L"Interface\\%ls\\AsynchronousInterface", iid_str); + + base::win::RegKey key; + if (key.Open(HKEY_CLASSES_ROOT, key_name.c_str(), KEY_READ)) { + // It's registered, the rest of this block is debug checking that + // the correct IID is indeed registered for the async interface. + std::wstring async_iid_str; + DCHECK(key.ReadValue(NULL, &async_iid_str)); + IID read_async_iid; + DCHECK(SUCCEEDED(::IIDFromString(async_iid_str.c_str(), &read_async_iid))); + DCHECK(read_async_iid == async_iid); + } else { + LOG(WARNING) << "Sync->Async IID not registered. Key=" << key_name; + } +} +#endif // NDEBUG + +} // namespace + +bool RegisterProxyStubs(std::vector<DWORD>* cookies) { + bool succeeded = true; + + for (size_t i = 0; i < arraysize(kInterfaceInfo); ++i) { + CComPtr<IUnknown> factory; + const InterfaceInfo& info = kInterfaceInfo[i]; + HRESULT hr = ToolbandProxyDllGetClassObject(*info.iid, IID_IUnknown, + reinterpret_cast<void**>(&factory)); + + DWORD cookie = 0; + if (SUCCEEDED(hr)) { + hr = ::CoRegisterClassObject(*info.iid, factory, CLSCTX_INPROC_SERVER, + REGCLS_MULTIPLEUSE, &cookie); + } + if (SUCCEEDED(hr)) { + // Proxy/stubs have their own IID as class id. + hr = ::CoRegisterPSClsid(*info.iid, *info.iid); + if (SUCCEEDED(hr) && cookies != NULL) { + cookies->push_back(cookie); + } + } + +#ifndef NDEBUG + // If there's a corresponding Async interface, check whether + // it's registered. This is a debugging aid only. + if (info.async_iid != NULL) + CheckAsyncIidRegistered(*info.iid, *info.async_iid); +#endif // NDEBUG + + if (FAILED(hr)) { + succeeded = false; + LOG(ERROR) << "Failed to register proxy " << com::LogHr(hr); + } + } + + VLOG(1) << "Registered toolband proxy/stubs in thread " + << ::GetCurrentThreadId(); + + return succeeded; +} + +void UnregisterProxyStubs(const std::vector<DWORD>& cookies) { + for (size_t i = 0; i < cookies.size(); ++i) { + HRESULT hr = ::CoRevokeClassObject(cookies[i]); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to revoke class object " << com::LogHr(hr); + } + } +} + +bool RegisterAsyncProxies(bool reg) { + for (size_t i = 0; i < arraysize(kInterfaceInfo); ++i) { + // Register the iid->async iid mapping for async interfaces. + if (kInterfaceInfo[i].async_iid != NULL) { + const InterfaceInfo& info = kInterfaceInfo[i]; + std::wstring iid_str; + bool success = com::GuidToString(*info.iid, &iid_str); + DCHECK(success) << "Failed to stringify GUID"; + std::wstring async_iid_str; + success = com::GuidToString(*info.async_iid, &async_iid_str); + DCHECK(success) << "Failed to stringify GUID"; + + _ATL_REGMAP_ENTRY entries[] = { + { L"IID", iid_str.c_str() }, + { L"ASYNC_IID", async_iid_str.c_str() }, + { L"NAME", info.name }, + { NULL, NULL }, + }; + + HRESULT hr = _pAtlModule->UpdateRegistryFromResource( + MAKEINTRESOURCE(IDR_TOOLBAND_PROXY), reg, entries); + if (FAILED(hr)) { + LOG(ERROR) << "Failed to register async interface for " << + info.name << com::LogHr(hr); + return false; + } + } + } + + return true; +} diff --git a/ceee/ie/plugin/toolband/toolband_proxy.h b/ceee/ie/plugin/toolband/toolband_proxy.h new file mode 100644 index 0000000..bbc2314 --- /dev/null +++ b/ceee/ie/plugin/toolband/toolband_proxy.h @@ -0,0 +1,29 @@ +// 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 CEEE_IE_PLUGIN_TOOLBAND_TOOLBAND_PROXY_H_ +#define CEEE_IE_PLUGIN_TOOLBAND_TOOLBAND_PROXY_H_ + +#include <windows.h> +#include <vector> + +// Registers all proxy/stubs in the current apartment. +// @param cookies if not NULL, then on return contains the registration +// cookies for all successful proxy/stub class object registrations. +// @returns true on success, false on failure. +// @note this function logs errors for all failures. +// @note this function will also create registry entries for the sync/async +// interface mapping for interfaces that have asynchronous variants. +bool RegisterProxyStubs(std::vector<DWORD>* cookies); + +// Unregisters proxy/stub class objects from this apartment. +// @param cookies the cookies returned from an earlier call to +// RegisterProxyStubs in this apartment. +void UnregisterProxyStubs(const std::vector<DWORD>& cookies); + +// Registers or unregisters the sync to async IID mapping in registry +// for any asynchronous interfaces we implement. +// @param reg true to register, false to unregister. +bool RegisterAsyncProxies(bool reg); + +#endif // CEEE_IE_PLUGIN_TOOLBAND_TOOLBAND_PROXY_H_ diff --git a/ceee/ie/plugin/toolband/toolband_proxy.rgs b/ceee/ie/plugin/toolband/toolband_proxy.rgs new file mode 100644 index 0000000..e6bbf04 --- /dev/null +++ b/ceee/ie/plugin/toolband/toolband_proxy.rgs @@ -0,0 +1,11 @@ +HKLM { + NoRemove Software { + NoRemove Classes { + NoRemove 'Interface' { + '%IID%' = s '%NAME%' { + AsynchronousInterface = s '%ASYNC_IID%' + } + } + } + } +} |